Creating Delegates on the Spot with Blocks

Replacing Delegates with Blocks

If you are asking if an object conforming to a specific delegate protocol that has been set as a delegate in some other object, can be replaced with a block after some simple fiddling, i'd say the answer is negative.

Blocks are just some sort of enhanced function pointers that can be used as callbacks. There is no reciprocal concept in Java, not even java8's Function Objects, because they are actual Object with a single method (i'm sure you are familiar with the concept of functional interfaces).

But this doesn't mean that blocks cannot be used as callbacks to respond to events, but you'll need some sort of adapter that will forward the usual Delegate method specified by a specific delegate protocol to the callback block that you have configured.

An extremely nice example of building something like this is represented by the library ReactiveCocoa, using it you will be able to do something like this:

self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id _) {
NSLog(@"button was pressed!");
return [RACSignal empty];
}];

I'm not explaining here how it works (RACSignal is an abstraction that represent a stream of events), but i guess you can easily get the gist of what it does, really compact.

Update:

For more info on how blocks are implemented in the Foundation SDK, see this post. Also check out this post from Mike Ash with sample usage scenarios.

How to convert a delegate-based callback system into block-based?

Here is one way to do it. Use this RAExpendable class to dynamically build a delegate with a block based implementation.

Let's say your delegate is:

@protocol XDelegate
-(void) foo:(id)response;
@end

Add RAExpendable.h, RAExpendable.m from https://github.com/evadne/RAExpendable to your project. Dynamically add the delegate method:

    RAExpendable *expendable = [RAExpendable new];
[expendable addMethodForSelector:@selector(foo:) types:"v@:@" block:^(id x, SEL sel, id response){
NSLog(@"response is %@", response);
}];

And set the expendable class as your delegate:

    someObject.delegate = expendable;

Now, if you do this:

    [expendable performSelector:@selector(foo:) withObject:@"OK"];

You get the string response is OK. Replace NSLog with whatever success/failure implementation you see fit. From now on, when you call foo:, the block executes instead.

If you want to modify this for your use case, note that the parameters for this example were v@:@, which according to the Type Encoding guide of the runtime means: void return, self, SEL, object. self and SEL are the two hidden parameters present on every Objective-C methods, the third parameter is the first non hidden parameter of the method. The signature of the block has to match the signature of the method.

Is it possible to pass completion blocks to a delegate parameter in Objective-C?

Technically the answer is no, if some library or class works with delegates there is probably a good reason and the smart and easier thing to do is use them.

If for some reason you're really interested in using blocks because it's more natural for your problem domain you can implement a wrapper.

Super weird example ahead.

For instance, in UITableViewDataSource delegate you have a method to obtain the number of rows in each section:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

In order to use it you have to set the table dataSource property to some object that implements that method. The usual stuff.

You can create a data source wrapper tha implement the original protocol and a new one you define to use your desired blocks interface.

typedef NSUInteger (^RowsCounterBlock)(NSInteger section);

@protocol RowsCounter
- (void)tableViewController:(id)controller countRowsWithBlock:(RowsCounterBlock)block;
@end

// we implement both the original protocol and our block based protocol
@interface ComplexDataSource : NSObject <UITableViewDataSource, RowsCounter>
{
@property(strong) RowsCounterBlock counterBlock;
}

// save a block that knows how to count rows
- (void)tableViewController:(id)controller countRowsWithBlock:(RowsCounterBlock)block
{
controller.dataSource = self;
self.counterBlock = block;
}

// use the saved block to implement the method defined in the original delegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (!self.counterBlock) return 0;
return self.counterBlock(section);
}

@end

Then in your table controller:

self.source = [ComplexDataSource new]; // save the data source in a property
[self.source tableViewController:self
countRowsWithBlock:^NSUInteger(NSInteger section) {
// this will be called each time the table view needs to ask
// for the number of rows via the proxy delegate
<#do your magic#>
return <#rows#>;
}]

And the answer is still no, because you're using the original delegate...but under the hood your primary interface now is block based.

Creating anonymous delegate objects in Objective-C

Yes, the language features you mention are exposed via the objective-c runtime, although there's no built-in facility to dynamically create delegate classes and the runtime api is not the friendliest of things.

There is a library called A2DynamicDelegate, which does exactly what you're talking about. I haven't used it, but it may be worth investigating.

EDIT: A problem with this approach is that delegates are not retained, so you'll need to either keep a strong reference somewhere else, or an add an associative reference to the UIAlertView. You may find that all this fuss isn't worth it and just adding an extra method to your view controller works better (you can conform to the delegate protocol in a class extension to avoid polluting your interface).

How to declare a delegate in-line?

In Swift you cannot define a protocol in-line (inside another Type such as Class, Struct or Enum).

However there is a more modern solution that should be very close to what you are looking for: Closures.

Closures

class MyClass {

// define your closure, make it as complex as you want
typealias CompletionType = (Bool) -> ()

// this method accepts a closure as parameter and will call it
func doSomething(completion: @escaping CompletionType) {
completion(true)
}

}

Test

let myClass = MyClass()

myClass.doSomething { result in
print(result)
}

Output

true

Implement Delegate with Closure in Swift?

You can't define an anonymous class, but you can define a local class that works very similarly. I've actually migrated away from the inline class approach as the REPL seems to have problems with it even though it seems to be fine with the compiler. The approach I'm now using is to define a glue class which forwards methods to closures defined in the init, so it all feels very natural.

The URLConnectionDataDelegate is defined as:

class GreenUrlConnectionDataDelegate: NSObject, NSURLConnectionDataDelegate {
var didFinishLoading:()->()
var didReceiveResponse:((NSURLResponse!)->())?
var didReceiveData:((NSData!)->())?
var didFailWithError:((NSError!)->())?

func connectionDidFinishLoading(conn:NSURLConnection!) {
didFinishLoading()
}

func connection(conn:NSURLConnection!, didReceiveResponse response:NSURLResponse!) {
didReceiveResponse?(response)
}

func connection(conn:NSURLConnection!, didReceiveData data:NSData!) {
didReceiveData?(data)
}

func connection(conn:NSURLConnection!, didFailWithError error:NSError!) {
didFailWithError?(error)
}

init(
didFinishLoading:@escaping ()->(),
didReceiveResponse:@escaping ((NSURLResponse!)->())? = nil,
didReceiveData:@escaping ((NSData!)->())? = nil,
didFailWithError:@escaping ((NSError!)->())? = nil
) {
self.didFinishLoading = didFinishLoading
self.didReceiveResponse = didReceiveResponse
self.didReceiveData = didReceiveData
self.didFailWithError = didFailWithError
}
}

Which allows me to define a function with an inline delegate:

func downloadUrl(string:String, completion:(data:NSData?, error:NSError?) -> ()) {
let url = NSURL(string:string)
let request = NSURLRequest(URL: url)
var received:NSMutableData! = nil
let conn = NSURLConnection(request: request, delegate: GreenUrlConnectionDataDelegate(
didFinishLoading:{
completion(data:received, error:nil)
},
didReceiveResponse:{response in
if let capacity = response?.expectedContentLength {
if capacity > 0 {
received = NSMutableData(capacity: Int(capacity))
}
else {
received = NSMutableData()
}
}
},
didReceiveData:{data in
if data != nil {
received.appendData(data)
}
},
didFailWithError:{error in
completion(data:nil, error:error)
}
)
)
}

And the code to test it out in a playground:

downloadUrl("http://www.google.com") {
(data:NSData?, error:NSError?) -> () in
println("completion")
println("data.size: \(data?.length)")
println("error: \(error?.localizedDescription)")
}

XCPSetExecutionShouldContinueIndefinitely()

You could conceivably even embed the glue class into an extension to the class requiring the delegate, although I haven't tried that out yet.

IMP with unknown number of parameters

You can't create a function at runtime in C; the number of parameters has to be known at compile time.

You can use a variadic function to pretend that you have a function with any number of arguments, (I've included this usage in a recent project) but this may not be portable and is probably Undefined Behavior.

If you need to move arguments between functions where the signatures and arguments are not known until runtime, you almost certainly want to look into libffi.

Mike Ash has a few really useful posts about it: http://www.mikeash.com/pyblog/?tag=libffi
that's where I got started and learned most of what I know about it.

delegate for a singleton object

No, a delegate wouldn't fail, but consider using NSNotificationCenter instead:

static NSString *const kMyClassNotificationName = @"myClassNotificationName";

// where you would call a delegate method (e.g. [self.delegate doSomething])
[[NSNotificationCenter defaultCenter] postNotificationName:kMyClassNotificationName object:self userInfo: /* dictionary containing variables to pass to the delegate */];

// where you would set up a delegate (e.g. [Singleton instance].delegate = self)
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doSomething) name:kMyClassNotificationName object:[Singleton instance]];

Convert delegate to static function

It's not possible to do what you're literally asking; the NSURLConnction must have an object as its delegate, because it's going to send specific messages, from the delegate protocol, to that object. Functions can't respond to messages. (There's no reason you can't define your delegate class in this file, however.)

There are some options for creating on-the-spot delegates which you might find useful, and in this particular case, you could use +sendSynchronousRequest:returningResponse:error:



Related Topics



Leave a reply



Submit