vgr
vgr

Reputation: 543

Keep blocks inside a dictionary

I have my own method that takes a block as an argument. I want to keep track of that block inside an NSDictionary. What is the best way to add the block to the dictionary?

I tried this code but after executing the line below (setObject...) the dictionary is still empty. I presume that is because the block is not of type NSObject. But what is the right way to do this?

- (void)startSomething:(NSURLRequest*)request block:(void (^)(NSURLResponse*, NSData*, NSError*))handler {

    NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];

    [pendingRequests setObject:handler forKey:connection];
}

EDIT:

Never mind. I don't know what I was thinking. 3 points:

  1. Blocks are objc objects
  2. Typo: setObject should be setValue
  3. forKey is a string so it should be [connection description] or something like that

Anyway I fixed my problem now like this:

- (void)startSomething:(NSURLRequest*)request block:(void (^)(NSURLResponse*, NSData*, NSError*))handler {

    NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
    [pendingRequests setValue:handler forKey:[connection description]];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {

        void (^handler)(NSURLResponse*, NSData*, NSError*);
        handler = [pendingRequests valueForKey:[connection description]];
        handler(nil, nil, nil);
    });
}

Upvotes: 14

Views: 12870

Answers (4)

Binks
Binks

Reputation: 1806

Actually with ARC you can simply add the block to the NSDictionary as you would with any other object. You do not need to do anything special like Block_copy or [block copy], and doing that would be wrong and will cause a leak.

Upvotes: 4

newacct
newacct

Reputation: 122489

"Typo: setObject should be setValue"

NO, you should always use setObject: instead of setValue:. setValue: is for key-value coding and coincidentally works similar to setObject: for a dictionary (even then, it's not the same, e.g. when the key is "@something"), while setObject: is the correct method for putting things in a dictionary, and which correctly takes all types as keys. (By the way I'm not sure you want to use connection as a key since it will copy it.)

The real problem is that blocks need to be copied before you store them away in something that might last longer than the original scope of the block (this is a special issue for blocks and not for other objects), since blocks (unlike other objects) are initially on the stack, and thus retaining it does not prevent it from becoming deallocated. Putting something in a dictionary normally retains it, but that is not enough in this case.

[pendingRequests setObject:[[handler copy] autorelease] forKey:connection];

Upvotes: 5

Johan Kool
Johan Kool

Reputation: 15927

If you are using ARC, use -copy:

 void (^handlerCopy)(NSURLResponse*, NSData*, NSError*) = [handler copy];
 [dict setObject:handlerCopy forKey:@"foo"];

Upvotes: 12

bbum
bbum

Reputation: 162722

That still isn't going to work or, at best, will only work coincidentally.

You need to copy the handler before shoving it in the dictionary. Something like:

void (^handlerCopy)(NSURLResponse*, NSData*, NSError*) = Block_copy(handler);
[dict setObject:handlerCopy forKey:@"foo"];
Block_release(handlerCopy); // dict will -retain/-release, this balances the copy.

And, yes, it should be setObject:forKey: and objectForKey:.

Upvotes: 22

Related Questions