I would like to know if there is any way to tell Xcode to run unit tests in a specified order. I mean not in a same XCTestCase class file, but between all the class file.
For example I want to run the SitchozrSDKSessionTest before running SitchozrSDKMessageTest.
I looked over few threads on stack or on Apple documentation and I haven't found something helpful.
Thanks for your help.
It's all sorted alphabetically (classes and methods). So when You need some tests running last, just change the name of Class or Method (for (nasty) example by prefixing 'z_').
So...You have Class names:
MyAppTest1
-testMethodA
-testMethodB
MyAppTest2
-testMethodC
-testMethodD
and they run in this order. If you need to run MyAppTest1 as second, just rename so it's name is alphabetically after MyAppTest2 (z_MyAppTest1) and it will run (same for method):
MyAppTest2
-a_testMethodD
-testMethodC
z_MyAppTest1
-testMethodA
-testMethodB
Also please take the naming as example :)
It is true that currently (as of Xcode 7), XCTestCase methods are run in alphabetical order, so you can force an order for them by naming them cleverly. However, this is an implementation detail and seems like it could change.
A (hopefully) less fragile way to do this is to override +[XCTestCase testInvocations] and return your own NSInvocation objects in the order you want the tests run.
Something like:
+ (NSArray *)testInvocations
{
NSArray *selectorStrings = #[#"testFirstThing",
#"testSecondThing",
#"testAnotherThing",
#"testLastThing"];
NSMutableArray *result = [NSMutableArray array];
for (NSString *selectorString in selectorStrings) {
SEL selector = NSSelectorFromString(selectorString);
NSMethodSignature *methodSignature = [self instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.selector = selector;
[result addObject:invocation];
}
return result;
}
Of course, the downside here is that you have to manually add each test method to this instead of them being picked up automatically. There are a few ways to improve this situation. If you only need to order some of your test methods, not all of them, in your override of +testInvocations, you could call through to super, filter out those methods that you've manually ordered, then tack the rest on to the end of the array you return. If you need to order all the test methods, you could still get the result of calling through to super and verify that all of the automatically picked up methods are covered by your manually created, ordered result. If not, you could assert, causing a failure if you've forgotten to add any methods.
I'm leaving aside the discussion of whether it's "correct" to write tests that must run in a certain order. I think there are rare scenarios where that makes sense, but others may disagree.
its ordered by function names letter orders, doesn't matter how you order it in your code.
e.g.:
-(void)testCFun(){};
-(void)testB2Fun(){};
-(void)testB1Fun(){};
the actual execute order is :
testB1Fun called
testB2Fun called
testCFun called
In addition to andrew-madsen's answer:
I created a 'smart' testInvocations class method which searches for selectors starting with "test" and sorts them alphabetically.
So you don't have to maintain an NSArray of selector names separately.
#import <objc/runtime.h>
+ (NSArray <NSInvocation *> *)testInvocations
{
// Get the selectors of this class
unsigned int mc = 0;
Method *mlist = class_copyMethodList(self.class, &mc);
NSMutableArray *selectorNames = [NSMutableArray array];
for (int i = 0; i < mc; i++) {
NSString *name = [NSString stringWithFormat:#"%s", sel_getName(method_getName(mlist[i]))];
if (name.length > 4
&& [[name substringToIndex:4] isEqualToString:#"test"]) {
[selectorNames addObject:name];
}
}
// Sort them alphabetically
[selectorNames sortUsingComparator:^NSComparisonResult(NSString * _Nonnull sel1, NSString * _Nonnull sel2) {
return [sel1 compare:sel2];
}];
// Build the NSArray with NSInvocations
NSMutableArray *result = [NSMutableArray array];
for (NSString *selectorString in selectorNames) {
SEL selector = NSSelectorFromString(selectorString);
NSMethodSignature *methodSignature = [self instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.selector = selector;
[result addObject:invocation];
}
return result;
}
Footnote: It's considered bad practice making your unit tests depend on eachother
For you exemple you can just rename the tests files on your project like this :
SitchozrSDKSessionTest -> t001_SitchozrSDKSessionTest
SitchozrSDKMessageTest -> t002_SitchozrSDKMessageTest
Xcode treat the files using alphabetic order.
Since Xcode 7, XCTestCase subclasses are alphabetically ordered. If you want to ensure that the test methods themselves are also run in alphabetical order, you must override the testInvocations class method and return the invocations sorted by selector.
#interface MyTestCase : XCTestCase
#end
#implementation MyTestCase
+ (NSArray<NSInvocation *> *) testInvocations
{
return [[super testInvocations] sortedArrayUsingComparator:^NSComparisonResult(NSInvocation *invocation1, NSInvocation *invocation2) {
return [NSStringFromSelector(invocation1.selector) compare:NSStringFromSelector(invocation2.selector)];
}];
}
- (void) test_01
{
}
- (void) test_02
{
}
#end
The OP did not mention whether CI is available or whether a command line answer would be sufficient. In those cases, I have enjoyed using xctool [1]. It has syntax to run tests down to just a single test method:
path/to/xctool.sh \
-workspace YourWorkspace.xcworkspace \
-scheme YourScheme \
test -only SomeTestTarget:SomeTestClass/testSomeMethod
I will do this to ensure we test the items we think are easiest first.
1.[] facebook/xctool: A replacement for Apple's xcodebuild that makes it easier to build and test iOS or OSX apps. ; ; https://github.com/facebook/xctool
Related
I'm writing unit test codes for an existing project. The project is in Objective-C and I have to test few functions with a number of inputs to the test cases. For example, I have a test case to test a function calculator where two parameters are inputted. Currently I create array to store the set of input values to run the test. The code used are as follows:
- (void)setUp {
[super setUp];
self.vcToTest = [[BodyMassIndexVC alloc] init];
input1 = [[NSMutableArray alloc] initWithObjects:#"193", #"192", #"192", #"165", #"155", #"154", nil];
input2 = [[NSMutableArray alloc] initWithObjects:#"37", #"37", #"36", #"80",#"120", #"120", nil];
}
- (void)testCalculatorSuccess {
for (int i=0; i<input1.count; i++) {
NSArray *expectedResult = [[NSArray alloc] initWithObjects: #"9.93", #"10.04", #"9.77", #"29.38", #"49.95", #"50.60", nil];
NSString *actualResult = [self.vcToTest calculateResult:input1[i] andInput2:input2[i]];
XCTAssertEqualObjects(actualResult, expectedResult[i]);
}
}
I searched for best practices online but was not able to find any. Can someone help me with this? Am I running the test in the right way? What is the best practice to be followed in such cases? Should I create a test case for every set of input?
In my experience, the key consideration should be how easy it will be to maintain the test suite over time. Your current approach will cause problems down the road in two ways:
If you want to use different numbers for you BMI calculation you need to use a different test class (because you're locking in the values in the setup method).
If you ever decide to use different math or multiple BMI equations you'll have to update the arrays anywhere you're checking those values.
What I would suggest is to instead create a CSV or plain text file that has the height, weight, and expected BMI values in it. That keeps the test data together. Then in your test methods, load the file and check your actual BMI against the expected BMI.
You have the flexibility here to mix and match test data, or use different test files for different BMI equations. Personally, I also like the fact that you can keep old data files around as you change things, in case you ever want to rollback or add legacy algorithm support.
A quick and dirty version would look something like this:
- (NSArray *)dataFromFileNamed:(NSString *)filename {
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:filename ofType:nil];
// load the data however its formatted
// return the data as an array
return loadedData;
}
- (void)testBMIFormulaInCmAndKgSuccess {
NSArray *testData = [self dataFromFileNamed:#"BMI_data_01.txt"];
for (int i=0; i < testData.count; i++) {
NSArray *dataSet = testData[i];
CGFloat height = dataSet[0];
CGFloat weight = dataSet[1];
CGFloat expectedBMI = dataSet[2];
NSString *actualResult = [self.vcToTest calculateBMIforHeight:height andWeight:weight];
XCTAssertEqual(actualResult, expectedBMI);
}
}
Your test class should be targeting one specific thing to test which is the sut (system under test). In your case the sut variable should be your vcToTest. The way I would go about testing is by dependency injection rather than storing all the arrays you are testing as instance variables. So you would create a test method that takes in the parameters that you want to test. This way you don't need to keep creating instance variables, you only create local variables that are relevant to the test method.
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
self.sut = [[BodyMassIndexVC alloc] init];
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
self.sut = nil;
[super tearDown];
}
- (void)testBMIFormulaInCmAndKgSuccess {
// local variables that are only seen in this method
NSArray *heightsInMetres = [[NSMutableArray alloc] initWithObjects:#"193", #"192", #"192", #"165", #"155", #"154", nil];
NSArray *weightsInKg = [[NSMutableArray alloc] initWithObjects:#"37", #"37", #"36", #"80",#"120", #"120", nil];
NSArray *expectedResults = [[NSArray alloc] initWithObjects: #"9.93", #"10.04", #"9.77", #"29.38", #"49.95", #"50.60", nil];
for (int i=0; i<heightsInMetres.count; i++) {
[self xTestHeightInMeters:heightsInMetres[i] weightInKg:weightsInKg[i] expecting:expectedResults[i]];
}
}
// the variables that are used to test are injected into this method
- (void)xTestHeightInMeters:(NSString *)height weightInKg:(NSString *)weight expecting:(NSString *)expectedResult {
NSString *result = [self.sut calculateBMIforHeight:height andWeight:weight];
XCTAssertEqual(result, expectedResult);
}
If I was you I wouldn't create arrays to run the tests. Arrays are messy and become hard to understand what is going on, and easy to make mistakes. I would create specific test methods that test one thing to make sure the sut is working properly. For example in TDD you create a test method that will fail, then modify your sut to fix this failure in the most simple way. Usually this means your fix will just return exactly what you are expecting. Then you make another test that tests the exact same thing with a different value, it should now fail because your sut is simply returning what they previous test was looking for. Now you modify your sut again to make both tests pass. After this in most situations you won't need any addition tests since it has proven to work in two unique ways.
I know you said you are testing software that was written already, but I strongly recommend you check out Test Driven Development. Even if you don't actually apply TDD, you will learn how to create meaningful tests. This helped me learn tdd
It's usually best to avoid for-loops in unit test code. This rule usually leads to separate assertions.
But in your case, you want to exercise a function with various inputs. Your approach is not bad at all. We can simplify the arrays by using literals:
NSArray<NSString *> *heightsInMetres = #[ #"193", #"192", #"192", #"165", #"155", #"154"];
NSArray<NSString *> *weightsInKg = #[ #"37", #"37", #"36", #"80", #"120", #"120"];
NSArray<NSString *> *expectedResults = #[#"9.93", #"10.04", #"9.77", #"29.38", #"49.95", #"50.60"];
I also normally avoid funny formatting. But aligning the columns helps us see the values in a table-like format.
Finally, I wouldn't put these values in setUp unless they're used across multiple tests.
If you need many tests like this, it may be worth exploring a test format that uses actual tables, like Fitnesse. Table data could be in spreadsheets or in wiki format, driving tests.
The title is the question's formulation - i.e. what are the patterns and anti-patterns of +initialize and +load class methods overriding?
Have you met particular examples? If yes - please describe.
P.S. There was some good Q&A on +load and +initialize here on StackOverflow but no one tells about the practical interest of these methods. Mechanisms were discussed.
+load is useful for setting up stuff needed for categories because all the +load methods are guaranteed to be called once each when the binary is loaded (even if there are multiple +load methods for the same class, which normally would replace one another). Inheritance is actually irrelevant to its functioning.
I almost never use +load, but +initialize is useful for all sorts of things... setting up static variables, dynamically loading libraries for plugin architectures... anything you want to do one time like printing version info, setting up a global instance to do something specialized, like for logging, crash reporter, signal handler, etc...
edit:
to prevent multiple initialize calls from messing stuff up (which happens to superclasses when the child class is used after a superclass): you can make it reentrant (this is a common pattern):
+(void) initialize {
static BOOL inited = NO;
if(!inited)
{
/*dostuff*/
inited=YES;
}
}
A good use of the load method is to initialize global variables that can't be initialized at compile-time.
Here is a made up example:
SomeClass.h
extern NSString *SomeGlobalConstant;
// Followed by some class interface stuff
SomeClass.m
#import "SomeClass.h"
NSString *SomeGlobalConstant = nil;
static NSArray *someFileStaticArray = nil;
#implementation SomeClass
+ (void)load {
if (self == [SomeClass class]) {
SomeGlobalConstant = #"SomeAppropriateValue";
someFileStaticArray = #[ #"A", #"B", #"C" ];
}
}
// and the rest of the class implementation
#end
Another possible use of +initialize is for Method Swizzling. Which you shouldn't really use unless you're sure you know what you're doing. Read this SO question for more details.
It allows you to substitute an existing method implementation with your own, and still be able to call the original one. For instance, for faking NSDate in unit tests you could write something like this (note, that there are other ways to do this (OCMock, etc.), this is just an example). This code allows you to set a program-wide fake NSDate, which will be returned whenever [NSDate date] is called. And if no fake date is set, then the original implementation is used.
#import "NSDate+UnitTest.h"
#import "MethodSwizzling.h"
#implementation NSDate(UnitTest)
static NSDate *fakeCurrentDate = nil;
+(void)setFakeCurrentDate:(NSDate *)date
{
fakeCurrentDate = date;
}
+(NSDate *)fakeCurrentDate
{
if (fakeCurrentDate) {
return fakeCurrentDate;
}
else {
NSDate *result = [self fakeCurrentDate];
return result;
}
}
+(void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(#"Swizzling...");
SwizzleClassMethod([self class], #selector(date), #selector(fakeCurrentDate));
});
}
#end
//MethodSwizzling.m:
void SwizzleMethod(Class c, SEL orig, SEL new, BOOL isClassMethod) {
NSLog(#"Swizzling %# method %# of class %# with fake selector %#.",
(isClassMethod ? #"a class" : #"an instance"),
NSStringFromSelector(orig),
NSStringFromClass(c),
NSStringFromSelector(new));
Method origMethod = isClassMethod ? class_getClassMethod(c, orig) : class_getInstanceMethod(c, orig);
Method newMethod = isClassMethod ? class_getClassMethod(c, new) : class_getInstanceMethod(c, new);
method_exchangeImplementations(origMethod, newMethod);
//Actually, it's better to do it using C-functions instead of Obj-C methods and
//methos_setImplementation instead of method_exchangeImplementation, but since this
//is not an open-source project and these components aren't going to be used by other people,
//it's fine. The problem is that method_exchangeImplementations will mess things up if
//the implementation relies on a fact that the selector passed as a _cmd parameter
//matches the function name.
//More about it: http://blog.newrelic.com/2014/04/16/right-way-to-swizzle/
}
void SwizzleClassMethod(Class c, SEL orig, SEL new) {
SwizzleMethod(c, orig, new, YES);
}
void SwizzleInstanceMethod(Class c, SEL orig, SEL new) {
SwizzleMethod(c, orig, new, NO);
}
I love blocks and it makes me sad when I can't use them. In particular, this happens mostly every time I use delegates (e.g.: with UIKit classes, mostly pre-block functionality).
So I wonder... Is it possible -using the crazy power of ObjC-, to do something like this?
// id _delegate; // Most likely declared as class variable or it will be released
_delegate = [DelegateFactory delegateOfProtocol:#protocol(SomeProtocol)];
_delegate performBlock:^{
// Do something
} onSelector:#selector(someProtocolMethod)]; // would execute the given block when the given selector is called on the dynamic delegate object.
theObject.delegate = (id<SomeProtocol>)_delegate;
// Profit!
performBlock:onSelector:
If YES, how? And is there a reason why we shouldn't be doing this as much as possible?
Edit
Looks like it IS possible. Current answers focus on the first part of the question, which is how. But it'd be nice to have some discussion on the "should we do it" part.
Okay, I finally got around to putting WoolDelegate up on GitHub. Now it should only take me another month to write a proper README (although I guess this is a good start).
The delegate class itself is pretty straightforward. It simply maintains a dictionary mapping SELs to Block. When an instance recieves a message to which it doesn't respond, it ends up in forwardInvocation: and looks in the dictionary for the selector:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = [anInvocation selector];
GenericBlock handler = [self handlerForSelector:sel];
If it's found, the Block's invocation function pointer is pulled out and passed along to the juicy bits:
IMP handlerIMP = BlockIMP(handler);
[anInvocation Wool_invokeUsingIMP:handlerIMP];
}
(The BlockIMP() function, along with other Block-probing code, is thanks to Mike Ash. Actually, a lot of this project is built on stuff I learned from his Friday Q&A's. If you haven't read those essays, you're missing out.)
I should note that this goes through the full method resolution machinery every time a particular message is sent; there's a speed hit there. The alternative is the path that Erik H. and EMKPantry each took, which is creating a new clas for each delegate object that you need, and using class_addMethod(). Since every instance of WoolDelegate has its own dictionary of handlers, we don't need to do that, but on the other hand there's no way to "cache" the lookup or the invocation. A method can only be added to a class, not to an instance.
I did it this way for two reasons: this was an excercise to see if I could work out the part that's coming next -- the hand-off from NSInvocation to Block invocation -- and the creation of a new class for every needed instance simply seemed inelegant to me. Whether it's less elegant than my solution, I will leave to each reader's judgement.
Moving on, the meat of this procedure is actually in the NSInvocation category that's found in the project. This utilizes libffi to call a function that's unknown until runtime -- the Block's invocation -- with arguments that are also unknown until runtime (which are accessible via the NSInvocation). Normally, this is not possible, for the same reason that a va_list cannot be passed on: the compiler has to know how many arguments there are and how big they are. libffi contains assembler for each platform that knows/is based on those platforms' calling conventions.
There's three steps here: libffi needs a list of the types of the arguments to the function that's being called; it needs the argument values themselves put into a particular format; then the function (the Block's invocation pointer) needs to be invoked via libffi and the return value put back into the NSInvocation.
The real work for the first part is handled largely by a function which is again written by Mike Ash, called from Wool_buildFFIArgTypeList. libffi has internal structs that it uses to describe the types of function arguments. When preparing a call to a function, the library needs a list of pointers to these structures. The NSMethodSignature for the NSInvocation allows access of each argument's encoding string; translating from there to the correct ffi_type is handled by a set of if/else lookups:
arg_types[i] = libffi_type_for_objc_encoding([sig getArgumentTypeAtIndex:actual_arg_idx]);
...
if(str[0] == #encode(type)[0]) \
{ \
if(sizeof(type) == 1) \
return &ffi_type_sint8; \
else if(sizeof(type) == 2) \
return &ffi_type_sint16; \
Next, libffi wants pointers to the argument values themselves. This is done in Wool_buildArgValList: get the size of each argument, again from the NSMethodSignature, and allocate a chunk of memory that size, then return the list:
NSUInteger arg_size;
NSGetSizeAndAlignment([sig getArgumentTypeAtIndex:actual_arg_idx],
&arg_size,
NULL);
/* Get a piece of memory that size and put its address in the list. */
arg_list[i] = [self Wool_allocate:arg_size];
/* Put the value into the allocated spot. */
[self getArgument:arg_list[i] atIndex:actual_arg_idx];
(An aside: there's several notes in the code about skipping over the SEL, which is the (hidden) second passed argument to any method invocation. The Block's invocation pointer doesn't have a slot to hold the SEL; it just has itself as the first argument, and the rest are the "normal" arguments. Since the Block, as written in client code, could never access that argument anyways (it doesn't exist at the time), I decided to ignore it.)
libffi now needs to do some "prep"; as long as that succeeds (and space for the return value can be allocated), the invocation function pointer can now be "called", and the return value can be set:
ffi_call(&inv_cif, (genericfunc)theIMP, ret_val, arg_vals);
if( ret_val ){
[self setReturnValue:ret_val];
free(ret_val);
}
There's some demonstrations of the functionality in main.m in the project.
Finally, as for your question of "should this be done?", I think the answer is "yes, as long as it makes you more productive". WoolDelegate is completely generic, and an instance can act like any fully written-out class. My intention for it, though, was to make simple, one-off delegates -- that only need one or two methods, and don't need to live past their delegators -- less work than writing a whole new class, and more legible/maintainable than sticking some delegate methods into a view controller because it's the easiest place to put them. Taking advantage of the runtime and the language's dynamism like this hopefully can increase your code's readability, in the same way, e.g., Block-based NSNotification handlers do.
I just put together a little project that lets you do just this...
#interface EJHDelegateObject : NSObject
+ (id)delegateObjectForProtocol:(Protocol*) protocol;
#property (nonatomic, strong) Protocol *protocol;
- (void)addImplementation:(id)blockImplementation forSelector:(SEL)selector;
#end
#implementation EJHDelegateObject
static NSInteger counter;
+ (id)delegateObjectForProtocol:(Protocol *)protocol
{
NSString *className = [NSString stringWithFormat:#"%s%#%i",protocol_getName(protocol),#"_EJH_implementation_", counter++];
Class protocolClass = objc_allocateClassPair([EJHDelegateObject class], [className cStringUsingEncoding:NSUTF8StringEncoding], 0);
class_addProtocol(protocolClass, protocol);
objc_registerClassPair(protocolClass);
EJHDelegateObject *object = [[protocolClass alloc] init];
object.protocol = protocol;
return object;
}
- (void)addImplementation:(id)blockImplementation forSelector:(SEL)selector
{
unsigned int outCount;
struct objc_method_description *methodDescriptions = protocol_copyMethodDescriptionList(self.protocol, NO, YES, &outCount);
struct objc_method_description description;
BOOL descriptionFound = NO;
for (int i = 0; i < outCount; i++){
description = methodDescriptions[i];
if (description.name == selector){
descriptionFound = YES;
break;
}
}
if (descriptionFound){
class_addMethod([self class], selector, imp_implementationWithBlock(blockImplementation), description.types);
}
}
#end
And using an EJHDelegateObject:
self.alertViewDelegate = [EJHDelegateObject delegateObjectForProtocol:#protocol(UIAlertViewDelegate)];
[self.alertViewDelegate addImplementation:^(id _self, UIAlertView* alertView, NSInteger buttonIndex){
NSLog(#"%# dismissed with index %i", alertView, buttonIndex);
} forSelector:#selector(alertView:didDismissWithButtonIndex:)];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Example" message:#"My delegate is an EJHDelegateObject" delegate:self.alertViewDelegate cancelButtonTitle:#"Cancel" otherButtonTitles:#"OK", nil];
[alertView show];
Edit: This is what I've come up after having understood your requirement. This is just a quick hack, an idea to get you started, it's not properly implemented, nor is it tested. It is supposed to work for delegate methods that take the sender as their only argument. It works It is supposed to work with normal and struct-returning delegate methods.
typedef void *(^UBDCallback)(id);
typedef void(^UBDCallbackStret)(void *, id);
void *UBDDelegateMethod(UniversalBlockDelegate *self, SEL _cmd, id sender)
{
UBDCallback cb = [self blockForSelector:_cmd];
return cb(sender);
}
void UBDelegateMethodStret(void *retadrr, UniversalBlockDelegate *self, SEL _cmd, id sender)
{
UBDCallbackStret cb = [self blockForSelector:_cmd];
cb(retaddr, sender);
}
#interface UniversalBlockDelegate: NSObject
- (BOOL)addDelegateSelector:(SEL)sel isStret:(BOOL)stret methodSignature:(const char *)mSig block:(id)block;
#end
#implementation UniversalBlockDelegate {
SEL selectors[128];
id blocks[128];
int count;
}
- (id)blockForSelector:(SEL)sel
{
int idx = -1;
for (int i = 0; i < count; i++) {
if (selectors[i] == sel) {
return blocks[i];
}
}
return nil;
}
- (void)dealloc
{
for (int i = 0; i < count; i++) {
[blocks[i] release];
}
[super dealloc];
}
- (BOOL)addDelegateSelector:(SEL)sel isStret:(BOOL)stret methodSignature:(const char *)mSig block:(id)block
{
if (count >= 128) return NO;
selectors[count] = sel;
blocks[count++] = [block copy];
class_addMethod(self.class, sel, (IMP)(stret ? UBDDelegateMethodStret : UBDDelegateMethod), mSig);
return YES;
}
#end
Usage:
UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero];
UniversalBlockDelegate *d = [[UniversalBlockDelegate alloc] init];
webView.delegate = d;
[d addDelegateSelector:#selector(webViewDidFinishLoading:) isStret:NO methodSignature:"v#:#" block:^(id webView) {
NSLog(#"Web View '%#' finished loading!", webView);
}];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://google.com"]]];
I'm not really sure exactly how to describe what I want to do - the best I can do is provide some code as an example:
- (void) doStuffInLoopForDataArray:(NSArray *)arr forObjectsOfClass:(NSString *)class
{
for ([class class] *obj in arr)
{
// Do stuff
}
}
So I might call this like
NSArray *arr = [NSArray arrayWithObjects:#"foo",#"bar", nil];
[self doStuffInLoopForDataArray:arr forObjectsOfClass:#"NSString"];
and I would expect the code to be executed as if I had wrote
- (void) doStuffInLoopForDataArrayOfStrings:(NSArray *)arr
{
for (NSString *obj in arr)
{
// Do KVC stuff
}
}
Is there a way to get this kind of behavior?
I don't see much point in passing the class to the method. Run your loop as:
for (id obj in arr) {
and check the methods you want to call exist. Passing the class is only really useful if you want to check that the objects in the array are actually of that class, but you couldn't then do much with that information.
Another approach would be to create a single superclass that all the classes I'd like to use this method for inherit from. I can then loop using that superclass.
So if I want to be able to loop for MyObject1 and MyObject2, I could create a BigSuperClass, where MyObject1 and MyObject2 are both subclasses of BigSuperClass.
- (void) doStuffInLoopForDataArray:(NSArray *)arr
{
for (BigSuperClass *obj in arr)
{
// Do stuff
}
}
This loop should work for arrays of MyObject1 objects, arrays of MyObject2 objects, or arrays of BigSuperClass objects.
The more I've been thinking about this, the more I'm leaning towards this being the best approach. Since I can setup my BigSuperClass with all the #propertys and methods I'd be interested in as part of my // Do Stuff, which means I won't have to check respondsToSelector as with the other answers. This way just doesn't feel quite as fragile.
I came up with an idea while I was typing up this question, figured I might as well finish it. I just need to change how I'm doing my loop, and I don't really need to send in the class.
- (void) doStuffInLoopForDataArray:(NSArray *)arr
{
for (int i=0; i < [arr count]; i++)
{
// Do stuff
}
}
I should note that part of my // Do stuff is checking to make sure if ([[arr objectAtIndex:i] respondsToSelector:...]) before I actually try to do anything with it - and from what I understand that should prevent any nasty crashes.
I love blocks and it makes me sad when I can't use them. In particular, this happens mostly every time I use delegates (e.g.: with UIKit classes, mostly pre-block functionality).
So I wonder... Is it possible -using the crazy power of ObjC-, to do something like this?
// id _delegate; // Most likely declared as class variable or it will be released
_delegate = [DelegateFactory delegateOfProtocol:#protocol(SomeProtocol)];
_delegate performBlock:^{
// Do something
} onSelector:#selector(someProtocolMethod)]; // would execute the given block when the given selector is called on the dynamic delegate object.
theObject.delegate = (id<SomeProtocol>)_delegate;
// Profit!
performBlock:onSelector:
If YES, how? And is there a reason why we shouldn't be doing this as much as possible?
Edit
Looks like it IS possible. Current answers focus on the first part of the question, which is how. But it'd be nice to have some discussion on the "should we do it" part.
Okay, I finally got around to putting WoolDelegate up on GitHub. Now it should only take me another month to write a proper README (although I guess this is a good start).
The delegate class itself is pretty straightforward. It simply maintains a dictionary mapping SELs to Block. When an instance recieves a message to which it doesn't respond, it ends up in forwardInvocation: and looks in the dictionary for the selector:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = [anInvocation selector];
GenericBlock handler = [self handlerForSelector:sel];
If it's found, the Block's invocation function pointer is pulled out and passed along to the juicy bits:
IMP handlerIMP = BlockIMP(handler);
[anInvocation Wool_invokeUsingIMP:handlerIMP];
}
(The BlockIMP() function, along with other Block-probing code, is thanks to Mike Ash. Actually, a lot of this project is built on stuff I learned from his Friday Q&A's. If you haven't read those essays, you're missing out.)
I should note that this goes through the full method resolution machinery every time a particular message is sent; there's a speed hit there. The alternative is the path that Erik H. and EMKPantry each took, which is creating a new clas for each delegate object that you need, and using class_addMethod(). Since every instance of WoolDelegate has its own dictionary of handlers, we don't need to do that, but on the other hand there's no way to "cache" the lookup or the invocation. A method can only be added to a class, not to an instance.
I did it this way for two reasons: this was an excercise to see if I could work out the part that's coming next -- the hand-off from NSInvocation to Block invocation -- and the creation of a new class for every needed instance simply seemed inelegant to me. Whether it's less elegant than my solution, I will leave to each reader's judgement.
Moving on, the meat of this procedure is actually in the NSInvocation category that's found in the project. This utilizes libffi to call a function that's unknown until runtime -- the Block's invocation -- with arguments that are also unknown until runtime (which are accessible via the NSInvocation). Normally, this is not possible, for the same reason that a va_list cannot be passed on: the compiler has to know how many arguments there are and how big they are. libffi contains assembler for each platform that knows/is based on those platforms' calling conventions.
There's three steps here: libffi needs a list of the types of the arguments to the function that's being called; it needs the argument values themselves put into a particular format; then the function (the Block's invocation pointer) needs to be invoked via libffi and the return value put back into the NSInvocation.
The real work for the first part is handled largely by a function which is again written by Mike Ash, called from Wool_buildFFIArgTypeList. libffi has internal structs that it uses to describe the types of function arguments. When preparing a call to a function, the library needs a list of pointers to these structures. The NSMethodSignature for the NSInvocation allows access of each argument's encoding string; translating from there to the correct ffi_type is handled by a set of if/else lookups:
arg_types[i] = libffi_type_for_objc_encoding([sig getArgumentTypeAtIndex:actual_arg_idx]);
...
if(str[0] == #encode(type)[0]) \
{ \
if(sizeof(type) == 1) \
return &ffi_type_sint8; \
else if(sizeof(type) == 2) \
return &ffi_type_sint16; \
Next, libffi wants pointers to the argument values themselves. This is done in Wool_buildArgValList: get the size of each argument, again from the NSMethodSignature, and allocate a chunk of memory that size, then return the list:
NSUInteger arg_size;
NSGetSizeAndAlignment([sig getArgumentTypeAtIndex:actual_arg_idx],
&arg_size,
NULL);
/* Get a piece of memory that size and put its address in the list. */
arg_list[i] = [self Wool_allocate:arg_size];
/* Put the value into the allocated spot. */
[self getArgument:arg_list[i] atIndex:actual_arg_idx];
(An aside: there's several notes in the code about skipping over the SEL, which is the (hidden) second passed argument to any method invocation. The Block's invocation pointer doesn't have a slot to hold the SEL; it just has itself as the first argument, and the rest are the "normal" arguments. Since the Block, as written in client code, could never access that argument anyways (it doesn't exist at the time), I decided to ignore it.)
libffi now needs to do some "prep"; as long as that succeeds (and space for the return value can be allocated), the invocation function pointer can now be "called", and the return value can be set:
ffi_call(&inv_cif, (genericfunc)theIMP, ret_val, arg_vals);
if( ret_val ){
[self setReturnValue:ret_val];
free(ret_val);
}
There's some demonstrations of the functionality in main.m in the project.
Finally, as for your question of "should this be done?", I think the answer is "yes, as long as it makes you more productive". WoolDelegate is completely generic, and an instance can act like any fully written-out class. My intention for it, though, was to make simple, one-off delegates -- that only need one or two methods, and don't need to live past their delegators -- less work than writing a whole new class, and more legible/maintainable than sticking some delegate methods into a view controller because it's the easiest place to put them. Taking advantage of the runtime and the language's dynamism like this hopefully can increase your code's readability, in the same way, e.g., Block-based NSNotification handlers do.
I just put together a little project that lets you do just this...
#interface EJHDelegateObject : NSObject
+ (id)delegateObjectForProtocol:(Protocol*) protocol;
#property (nonatomic, strong) Protocol *protocol;
- (void)addImplementation:(id)blockImplementation forSelector:(SEL)selector;
#end
#implementation EJHDelegateObject
static NSInteger counter;
+ (id)delegateObjectForProtocol:(Protocol *)protocol
{
NSString *className = [NSString stringWithFormat:#"%s%#%i",protocol_getName(protocol),#"_EJH_implementation_", counter++];
Class protocolClass = objc_allocateClassPair([EJHDelegateObject class], [className cStringUsingEncoding:NSUTF8StringEncoding], 0);
class_addProtocol(protocolClass, protocol);
objc_registerClassPair(protocolClass);
EJHDelegateObject *object = [[protocolClass alloc] init];
object.protocol = protocol;
return object;
}
- (void)addImplementation:(id)blockImplementation forSelector:(SEL)selector
{
unsigned int outCount;
struct objc_method_description *methodDescriptions = protocol_copyMethodDescriptionList(self.protocol, NO, YES, &outCount);
struct objc_method_description description;
BOOL descriptionFound = NO;
for (int i = 0; i < outCount; i++){
description = methodDescriptions[i];
if (description.name == selector){
descriptionFound = YES;
break;
}
}
if (descriptionFound){
class_addMethod([self class], selector, imp_implementationWithBlock(blockImplementation), description.types);
}
}
#end
And using an EJHDelegateObject:
self.alertViewDelegate = [EJHDelegateObject delegateObjectForProtocol:#protocol(UIAlertViewDelegate)];
[self.alertViewDelegate addImplementation:^(id _self, UIAlertView* alertView, NSInteger buttonIndex){
NSLog(#"%# dismissed with index %i", alertView, buttonIndex);
} forSelector:#selector(alertView:didDismissWithButtonIndex:)];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Example" message:#"My delegate is an EJHDelegateObject" delegate:self.alertViewDelegate cancelButtonTitle:#"Cancel" otherButtonTitles:#"OK", nil];
[alertView show];
Edit: This is what I've come up after having understood your requirement. This is just a quick hack, an idea to get you started, it's not properly implemented, nor is it tested. It is supposed to work for delegate methods that take the sender as their only argument. It works It is supposed to work with normal and struct-returning delegate methods.
typedef void *(^UBDCallback)(id);
typedef void(^UBDCallbackStret)(void *, id);
void *UBDDelegateMethod(UniversalBlockDelegate *self, SEL _cmd, id sender)
{
UBDCallback cb = [self blockForSelector:_cmd];
return cb(sender);
}
void UBDelegateMethodStret(void *retadrr, UniversalBlockDelegate *self, SEL _cmd, id sender)
{
UBDCallbackStret cb = [self blockForSelector:_cmd];
cb(retaddr, sender);
}
#interface UniversalBlockDelegate: NSObject
- (BOOL)addDelegateSelector:(SEL)sel isStret:(BOOL)stret methodSignature:(const char *)mSig block:(id)block;
#end
#implementation UniversalBlockDelegate {
SEL selectors[128];
id blocks[128];
int count;
}
- (id)blockForSelector:(SEL)sel
{
int idx = -1;
for (int i = 0; i < count; i++) {
if (selectors[i] == sel) {
return blocks[i];
}
}
return nil;
}
- (void)dealloc
{
for (int i = 0; i < count; i++) {
[blocks[i] release];
}
[super dealloc];
}
- (BOOL)addDelegateSelector:(SEL)sel isStret:(BOOL)stret methodSignature:(const char *)mSig block:(id)block
{
if (count >= 128) return NO;
selectors[count] = sel;
blocks[count++] = [block copy];
class_addMethod(self.class, sel, (IMP)(stret ? UBDDelegateMethodStret : UBDDelegateMethod), mSig);
return YES;
}
#end
Usage:
UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero];
UniversalBlockDelegate *d = [[UniversalBlockDelegate alloc] init];
webView.delegate = d;
[d addDelegateSelector:#selector(webViewDidFinishLoading:) isStret:NO methodSignature:"v#:#" block:^(id webView) {
NSLog(#"Web View '%#' finished loading!", webView);
}];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://google.com"]]];