Skip to content

Instantly share code, notes, and snippets.

@nilium
Last active August 29, 2015 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nilium/9440566 to your computer and use it in GitHub Desktop.
Save nilium/9440566 to your computer and use it in GitHub Desktop.
Switch-case-like test category for NSObject. Uses blocks. Probably not the fastest thing you could do, but handy nonetheless.
static
id
testObj(id obj)
{
return [NSObject testObject: obj,
NSNumber.class, ^{
return @"A number";
},
NSString.class, ^{
return @"A string";
},
NSData.class, ^{
return @"A data object";
},
QBlockTest, ^(id obj){ return [obj isKindOfClass:NSObject.class]; }, ^{
return @"It's an object, though.";
},
nil, ^{
return @"Not an accepted type";
}];
}
#ifndef NSObject_QTestObject_h_58095849_FEC4_4085_8A11_C6B2C7A2EBFB
#define NSObject_QTestObject_h_58095849_FEC4_4085_8A11_C6B2C7A2EBFB
#import <Foundation/Foundation.h>
extern id const QBlockTest;
typedef id (^QTestCase)();
@interface NSObject (QTestObject)
+ (id)testObject:(id)rhsObject, ...;
@end
#endif /* !defined NSObject_QTestObject_h_58095849_FEC4_4085_8A11_C6B2C7A2EBFB */
#import "NSObject+QTestObject.h"
#import <objc/runtime.h>
id const QBlockTest = @"__block_string__";
@implementation NSObject (QTestObject)
typedef BOOL (^QTestBlock)(id);
static
BOOL
objIsClass(id obj)
{
return class_isMetaClass(object_getClass(obj));
}
// Static method since it's nice to be able to test against nil on the rhs.
/*
testObject: tests whether an object is equal to other objects and returns the result of running a
case block. Equality is tested either in terms of its pointer, via isEqual:, isEqualToString: (if
the lhs responds to isEqualToString: and the rhs is a string -- it will not test against rhs's
description since rhs may be nil), or isKindOfClass: if lhs is a class.
Variadic arguments are received as a pair of (id, QTestCase) objects. If id is nil, it is the
default case and no further tests will occur. If the test case block for the lhsObject is nil, the
test ends and the result is nil. The default case may have a block associated with it.
Optionally, a case may be the object QBlockTest followed by a block of type BOOL ^test(id) which
returns true if the object it receives passes the test.
All lhsObjects must have an accompanying QTestCase block.
The list of tests must be terminated by a default case (the block may be nil).
The rhsObject is always the right-hand side of a comparison except when testing against a Class.
This is to avoid calling isEqual: or similar on a nil rhs object, which will always yield zero.
So, the comparison will always happen as [lhsObject isEqual:rhsObject] (or isEqualToString:).
When testing against a class, the rhsObject's isKindOfClass: method is called if and only if it is
not nil. Otherwise, the test fails.
Example:
NSString *closeitude = [NSObject testObject: @"foobar",
@"baz", ^{ return @"close to foobar"; },
@"bar", ^{ return @"closer to foobar"; },
@"foobar", ^{ return @"a foobar"; },
nil, ^{ return @"not a foobar"; }]; // last case tested since lhs is nil
*/
+ (id)testObject:(id)rhsObject, ...
{
va_list argv;
id result = nil;
va_start(argv, rhsObject);
id lhsObject;
QTestBlock lhsTest;
QTestCase block;
@try {
for (;;) {
BOOL isBlockTest = false;
lhsObject = va_arg(argv, id);
// Just test pointer identity here.
if ((isBlockTest = (lhsObject == QBlockTest))) {
lhsTest = va_arg(argv, QTestBlock);
}
block = va_arg(argv, QTestCase);
if (nil == block) {
break;
} else if (nil == lhsObject || lhsObject == rhsObject) {
// Pointer identity test / default case
goto q_matching_test_case;
} else if (rhsObject && objIsClass(lhsObject)) {
if ([rhsObject isKindOfClass:lhsObject]) {
goto q_matching_test_case;
}
} else if (isBlockTest) {
if (lhsTest(rhsObject)) {
goto q_matching_test_case;
}
} else if ([lhsObject respondsToSelector:@selector(isEqualToString:)] &&
[rhsObject isKindOfClass:NSString.class]) {
// Nested if to skip next condition
if ([lhsObject isEqualToString:rhsObject]) {
goto q_matching_test_case;
}
} else if ([lhsObject isEqual:rhsObject]) {
// Otherwise, try to compare the two objects
q_matching_test_case:
result = block();
break;
}
}
} @finally {
va_end(argv);
}
return result;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment