Related
I'm making an app for practice. This app shares with a simple model through AppDelegate. To manipulate the model, I got an NSDictionary object from the model and allocate it to a viewController property. but It seems too verbose.
// viewController.h
#property (nonatomic, strong) NSMutableDictionary *bookDetail;
#property (nonatomic, strong) bookModel *modelBook;
// viewController.m
- (void)setLabel {
self.label_name.text = self.bookDetail[#"name"];
self.label_author.text = self.bookDetail[#"author"];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
id appDelegate = [[UIApplication sharedApplication] delegate];
self.modelBook = [appDelegate modelBook];
self.bookDetail = self.modelBook.bookList[self.modelBook.selectedId];
[self setLabel];
self.editMod = NO;
}
- (IBAction)editSave:(id)sender {
if (self.editMod == NO) {
....
[self.nameField setText:self.bookDetail[#"name"]];
[self.authorField setText:self.bookDetail[#"author"]];
....
} else {
self.bookDetail = [#{#"name" : self.nameField.text,
#"author" : self.authorField.text} mutableCopy];
[self setLabel];
....
}
}
#end
*bookDetail work like a copy of self.modelBook.bookList[self.modelBook.selectedId] not a reference. Using self.modelBook.bookList[self.modelBook.selectedId] works well, but I don't want to. How Can I simplify this code?
*bookDetail work like a copy of self.modelBook.bookList[self.modelBook.selectedId] not a reference. Using self.modelBook.bookList[self.modelBook.selectedId] works well, but I don't want to.
Your question is not clear to me so this might be wrong, but hopefully it helps.
bookDetail is not a "copy" in the usual sense, rather it is a reference to the same dictionary that self.modelBook.bookList[self.modelBook.selectedId] references at the time the assignment to bookDetail is made.
Given that you say that using the latter "works well" is sounds as though self.modelBook.selectedId is changing and you expected bookDetail to automatically track that change and now refer to a different dictionary. That is not how assignment works.
How Can I simplify this code?
You could add a property to your modelBook class[1], say currentBook, which returns back bookList[selectedID] so each time it is called you get the current book. In your code above you then use self.modelBook.currentBook instead of self.bookDetail and can remove the property bookDetail as unused (and incorrect).
HTH
[1] Note: this should be called ModelBook to follow naming conventions. Have you noticed the syntax coloring is incorrect? That is because you haven't followed the convention.
Create the shared instance of BookModel then you can access it anywhere:
Write this in bookModel:
+ (instancetype)sharedInstance
{
static bookModel *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[bookModel alloc] init];
// Do any other initialisation stuff here
});
return sharedInstance;
}
Then you can access this like bookModel.sharedInstance.bookList
I was going over this example in which a selector is used. I have copied the code from there for convenience.
// MYTapGestureRecognizer.h
#interface MYTapGestureRecognizer : UITapGestureRecognizer
#property (nonatomic, strong) NSString *data;
#end
// MYTapGestureRecognizer.m
#implementation MYTapGestureRecognizer
#end
// =====================
....
MYTapGestureRecognizer *singleTap = [[MYTapGestureRecognizer alloc] initWithTarget:self action:#selector(tapDetected:)];
singleTap.data = #"Hello";
.....
// ====================
-(void)tapDetected:(UITapGestureRecognizer *)tapRecognizer {
MYTapGestureRecognizer *tap = (MYTapGestureRecognizer *)tapRecognizer;
NSLog(#"data : %#", tap.data);
}
My question is
1-When self calls the selector what parameter does it pass in the above case ?
2- Also if a selector (pointing to a method that requires parameters) is called (see example below) and no parameters are passed are there any defaults in that case ? If possible is there any documentation for that ?
Suppose the signature of MyTest is
- (void) MyTest : (NSString*) a;
Now constructing and calling a selector
SEL a = NSSelectorFromString(#"MyTest:");
[t performSelector:a]; //Works Fine and the call is made - However Notice no parameter is passed . In this case what would the value of the parameter be in the method ?
I checked the following but I could not find this information
Apple docs
Rys Tutorials
Answers to your questions:-
When self calls the selector what parameter does it pass in the above case ?
If a tap is detected and the selector is called, the parameter will be an object of UITapGestureRecognizer. This will be the same instance on which the tap gesture is detected.
Also if a selector (pointing to a method that requires parameters) is called (see example below) and no parameters are passed are there any defaults in that case ? If possible is there any documentation for that ?
Why do you want to call the method like that, is there any special purpose?. If not, you can call the method just like
[self tapDetected:nil];
or
[self performSelector:#selector(tapDetected:) withObject:nil];
If you call the method as provided in the question, most probably it will crash.
If you wish to call the method on self, pass nil parameter to it. But i do not understand what purpose is it serving you.
Also if you do not send parameters to your methods, it is going to fail at your builds. You have to pass either the parameter or nil.
Also if your method does not accept nil parameters it might cause an exception - 'NSInvalidArgumentException'
Normally if you want to access that selector via self, use it like :
[self tapDetected:nil];
You need to handle this case in your selector, like :
-(void)tapDetected:(UITapGestureRecognizer *)tapRecognizer {
if (tapRecognizer)
{
MYTapGestureRecognizer *tap = (MYTapGestureRecognizer *)tapRecognizer;
NSLog(#"data : %#", tap.data);
}
else
{
//Do your work
}
}
Also not only this, if you are not sure of parameter you are passing change your selector decalartion as id, like :
-(void)tapDetected:(id)sender {
NSString *className = NSStringFromClass([id class]);
NSLog(#"Object passed is of class : %#", className);
//And make check here
if ([id isKindOfClass:[MYTapGestureRecognizer class]])
{
//Do your work here
}
}
There are no default cases, you need to handle every case manually or else app will crash.
SEL a = NSSelectorFromString(#"MyTest:");
[t performSelector:a]; //Works Fine and the call is made - However Notice no parameter is passed . In this case what would the value of the parameter be in the method ?
It will be undefined junk. You have no guarantees about what it might contain. Most likely, it will be an invalid pointer. If you're unlucky, it might be a valid pointer to some arbitrary object and operating on it will corrupt your app's state. If you're lucky, it will crash so you can find the problem.
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"]]];
In the Facebook iOS SDK requests are returned with the following handler:
^(FBRequestConnection *connection,
NSDictionary<FBGraphUser> *user,
NSError *error) { }
The user variable can then be accessed with calls like these...
self.userNameLabel.text = user.name;
self.userProfileImage.profileID = user.id;
This syntax is somewhat similar to the syntax id <protocolDelegate> object syntax that is a common property declaration, except for that the NSDictionary is the id object explicitely, and that dictionary conforms to the protocol? But where does the dot syntax come from and how does one state that an arbitrary NSFoundation object corresponds to a protocol without subclassing the object itself and making it conform?
I did some additional research about dot notation and NSDictionary and it appears that it is not possible to use dot notation on a dictionary without adding a category to NSDictionary. However, I did not see any reference of the <> syntax in the Apple Documentation to indicate that this particular instance of NSDictionary conformed to that notation.
And the Facebook documentation is a little sparse on how this wrapping works:
The FBGraphUser protocol represents the most commonly used properties
of a Facebook user object. It may be used to access an NSDictionary
object that has been wrapped with an FBGraphObject facade.
If one follows this lead to the FBGraphObject documentation then there is methods that return dictionaries that conform to this "facade..." but no further explanation on how one goes about wrapping a dictionary.
So I guess my questions are a few:
What would the underlying code look like to make this sort of
syntax work?
Why does it exist?
Why would facebook implement it this way as opposed to just
making an object that they can convert the data into?
Any explanation or insight would be very appreciated!
Basically, NSDictionary<FBGraphUser> *user, implies an object that inherits from NSDictionary, adding functionality (specifically, typed access) declared by the FBGraphUser protocol.
The reasons behind this approach are described in quite a bit of detail in the FBGraphObject documentation (the FBGraphUser protocol extends the FBGraphObject protocol). What might be confusing you is that FBGraphObject is a protocol (described here) and a class (described here), which inherits from NSMutableDictionary.
In terms of inner implementation, it's some pretty advanced Objective-C dynamic magic, which you probably don't want to worry about. All you need to know is you can treat the object as a dictionary if you wish, or use the additional methods in the protocol. If you really want to know the details, you can look at the source code for FBGraphObject, in particular, these methods:
#pragma mark -
#pragma mark NSObject overrides
// make the respondsToSelector method do the right thing for the selectors we handle
- (BOOL)respondsToSelector:(SEL)sel
{
return [super respondsToSelector:sel] ||
([FBGraphObject inferredImplTypeForSelector:sel] != SelectorInferredImplTypeNone);
}
- (BOOL)conformsToProtocol:(Protocol *)protocol {
return [super conformsToProtocol:protocol] ||
([FBGraphObject isProtocolImplementationInferable:protocol
checkFBGraphObjectAdoption:YES]);
}
// returns the signature for the method that we will actually invoke
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
SEL alternateSelector = sel;
// if we should forward, to where?
switch ([FBGraphObject inferredImplTypeForSelector:sel]) {
case SelectorInferredImplTypeGet:
alternateSelector = #selector(objectForKey:);
break;
case SelectorInferredImplTypeSet:
alternateSelector = #selector(setObject:forKey:);
break;
case SelectorInferredImplTypeNone:
default:
break;
}
return [super methodSignatureForSelector:alternateSelector];
}
// forwards otherwise missing selectors that match the FBGraphObject convention
- (void)forwardInvocation:(NSInvocation *)invocation {
// if we should forward, to where?
switch ([FBGraphObject inferredImplTypeForSelector:[invocation selector]]) {
case SelectorInferredImplTypeGet: {
// property getter impl uses the selector name as an argument...
NSString *propertyName = NSStringFromSelector([invocation selector]);
[invocation setArgument:&propertyName atIndex:2];
//... to the replacement method objectForKey:
invocation.selector = #selector(objectForKey:);
[invocation invokeWithTarget:self];
break;
}
case SelectorInferredImplTypeSet: {
// property setter impl uses the selector name as an argument...
NSMutableString *propertyName = [NSMutableString stringWithString:NSStringFromSelector([invocation selector])];
// remove 'set' and trailing ':', and lowercase the new first character
[propertyName deleteCharactersInRange:NSMakeRange(0, 3)]; // "set"
[propertyName deleteCharactersInRange:NSMakeRange(propertyName.length - 1, 1)]; // ":"
NSString *firstChar = [[propertyName substringWithRange:NSMakeRange(0,1)] lowercaseString];
[propertyName replaceCharactersInRange:NSMakeRange(0, 1) withString:firstChar];
// the object argument is already in the right place (2), but we need to set the key argument
[invocation setArgument:&propertyName atIndex:3];
// and replace the missing method with setObject:forKey:
invocation.selector = #selector(setObject:forKey:);
[invocation invokeWithTarget:self];
break;
}
case SelectorInferredImplTypeNone:
default:
[super forwardInvocation:invocation];
return;
}
}
This syntax is somewhat similar to the syntax id object syntax
"Somewhat similar"? How'bout "identical"?
and that dictionary conforms to the protocol
Nah, the declaration says that you have to pass in an object of which the class is NSDictionary, which, at the same time, conforms to the FBGraphUser protocol.
But where does the dot syntax come from
I don't understand this. It comes from the programmer who wrote the piece of code in question. And it is possible because the FBGraphUser protocol declares some properties, which can then be accessed via dot notation.
and how does one state that an arbitrary NSFoundation object corresponds to a protocol without subclassing the object itself and making it conform?
It's not called "NSFoundation", just Foundation. And it's not the object that doesn't "correspond" (because it rather "conforms") to the protocol, but its class. And you just showed the syntax for that yourself.
And how is it implemented? Simple: a category.
#import <Foundation/Foundation.h>
#protocol Foo
#property (readonly, assign) int answer;
#end
#interface NSDictionary (MyCategory) <Foo>
#end
#implementation NSDictionary (MyCategory)
- (int)answer
{
return 42;
}
#end
int main()
{
NSDictionary *d = [NSDictionary dictionary];
NSLog(#"%d", d.answer);
return 0;
}
This is an SSCCE, i. e. it compiles and runs as-is, try it!
What would the underlying code look like to make this sort of syntax work?
Answered above.
Why does it exist?
Because the language is defined like so.
Why would facebook implement it this way as opposed to just making an object that they can convert the data into?
I don't know, ask the Facebook guys.
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"]]];