Skip to content

Commit

Permalink
Traceable option added.
Browse files Browse the repository at this point in the history
  • Loading branch information
endSly committed Dec 17, 2013
1 parent 20236b8 commit 14bb395
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 23 deletions.
1 change: 1 addition & 0 deletions NullObjects/NONull.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

extern NSString * const NONullDummyMethodBlock;
extern NSString * const NONullBlackHole;
extern NSString * const NONullTraceable;

@interface NONull : NSObject

Expand Down
40 changes: 28 additions & 12 deletions NullObjects/NONull.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@

#import <objc/runtime.h>

NSString * const NONullDummyMethodBlock = @"NODummyMethodBlock";
NSString * const NONullDummyMethodBlock = @"NONullDummyMethodBlock";
NSString * const NONullBlackHole = @"NONullBlackHole";
NSString * const NONullTraceable = @"NONullTraceable";

static id dummyMethod(id self, SEL _cmd) {
return nil;
Expand All @@ -21,10 +22,26 @@ static id dummyMethodBlackhole(id self, SEL _cmd) {
return self;
}

static IMP getDummyMethodBlackhole(id self, SEL _cmd) {
return (IMP) dummyMethodBlackhole;
static id dummyMethodStacktrace(id self, SEL _cmd) {
NSArray *stacktrace = [NSThread callStackSymbols];
// Remove top object that is call to this block
stacktrace = [stacktrace subarrayWithRange:NSMakeRange(1, stacktrace.count - 1)];
NSLog(@"[NullObjects] Called to null object with selector: %s\n"
"Stacktrace:\n%@", sel_getName(_cmd), [stacktrace componentsJoinedByString:@"\n"]);

return nil;
}

#define IMP_getter(method) \
(imp_implementationWithBlock(^IMP(id self, SEL _cmd) { \
return (IMP) (method); \
}))

#define IMP_getterBlock(block) \
(imp_implementationWithBlock(^IMP(id self, SEL _cmd) { \
return imp_implementationWithBlock(block); \
}))

@implementation NONull

+ (instancetype)null
Expand Down Expand Up @@ -57,19 +74,18 @@ + (Class)nullClassWithOptions:(NSDictionary *)options;
// Build new class
NSString *newClassName = [NSString stringWithFormat:@"NONull$DynamicClass-%i", nullId++];
Class NullClass = objc_allocateClassPair(self, [newClassName UTF8String], 0);

if (options[NONullDummyMethodBlock]) {
const id dummyMethodBlock = options[NONullDummyMethodBlock];

IMP dummyMethodImpBuilder = imp_implementationWithBlock(^IMP(id self, SEL _cmd) {
return imp_implementationWithBlock(dummyMethodBlock);
});

class_addMethod(object_getClass(NullClass), @selector(dummyMethodIMP), dummyMethodImpBuilder, "^@:");
const id block = options[NONullDummyMethodBlock];
class_addMethod(object_getClass(NullClass), @selector(dummyMethodIMP), IMP_getterBlock(block), "^@:");
}

if ([options[NONullBlackHole] boolValue]) {
class_addMethod(object_getClass(NullClass), @selector(dummyMethodIMP), (IMP) getDummyMethodBlackhole, "^@:");
class_addMethod(object_getClass(NullClass), @selector(dummyMethodIMP), IMP_getter(dummyMethodBlackhole), "^@:");
}

if ([options[NONullTraceable] boolValue]) {
class_addMethod(object_getClass(NullClass), @selector(dummyMethodIMP), IMP_getter(dummyMethodStacktrace), "^@:");
}

objc_registerClassPair(NullClass);
Expand Down
21 changes: 10 additions & 11 deletions NullObjectsTests/NullObjectsTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,21 @@ - (void)testCustomDummyMethod
XCTAssertEqualObjects([customNull lastObject], @"CALLED", @"Dummy method return should work.");
}

- (void)testNullTraceable
{
id nullTraceable = (id) [NONull nullWithOptions:@{NONullTraceable: @YES}];

[nullTraceable stringByAppendingString:@"test"]; // Should display log
}

- (void)testNSNullActAsNullObject
{
[NSNull actAsNullObject];

id simpleNull = (id) [NSNull null];

XCTAssertNoThrow([simpleNull lastObject], @"[NSNull actAsNullObject] never should raise exception");
XCTAssertNoThrow([[simpleNull firstObject] stringValue], @"[NONull null] never should raise exception");
XCTAssertNoThrow([simpleNull objectForKey:@"key"], @"[NONull null] never should raise exception");
XCTAssertNoThrow([[simpleNull objectForKey:@"key"] stringValue], @"[NONull null] never should raise exception");

XCTAssertEqual((__bridge void *)[simpleNull firstObject], nil, @"[NONull null] should return nil for any method");
XCTAssertEqual((__bridge void *)[simpleNull objectForKey:@"key"], nil, @"[NONull null] should return nil for any method");
}

Expand All @@ -91,14 +95,9 @@ - (void)testNSNullActAsBlackholeNull

id blackhole = (id) [NSNull null];

XCTAssertNoThrow([blackhole lastObject], @"[NONull blackhole] never should raise exception");
XCTAssertNoThrow([[blackhole firstObject] stringValue], @"[NONull blackhole] never should raise exception");
XCTAssertNoThrow([blackhole objectForKey:@"key"], @"[NONull blackhole] never should raise exception");
XCTAssertNoThrow([[blackhole objectForKey:@"key"] stringValue], @"[NONull blackhole] never should raise exception");

XCTAssertEqual([blackhole firstObject], blackhole, @"[NONull null] should return self for any method");
XCTAssertEqual([[[blackhole firstObject] firstObject] string], blackhole, @"[NONull null] should return self for any method");
XCTAssertEqual([blackhole objectForKey:@"key"], blackhole, @"[NONull null] should return self for any method");
XCTAssertEqual(blackhole[@"key"], blackhole, @"[NONull null] should return self for any method");
XCTAssertEqual([[[blackhole objectForKey:@"key"] firstObject] string], blackhole, @"[NONull null] should return self for any method");
}


Expand Down

0 comments on commit 14bb395

Please sign in to comment.