14

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);
    });
}
vgr
  • 543
  • 1
  • 5
  • 13
  • Are you sure this doesn't raise an error. `NSURLConnection` doesn't conform the `NSCopying` protocol and hence can't be a valid key for the dictionary. – Deepak Danduprolu Jun 15 '11 at 22:00
  • 2
    Why do you say that `setObject` should be `setValue`? `-setObject:forKey:` is the canonical method in `NSMutableDictionary` to store objects in the dictionary, and the key doesn’t need to be a string if you use this method. As Deepak has said, it needs to conform to the `NSCopying` protocol. –  Jun 15 '11 at 22:13

4 Answers4

22

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:.

bbum
  • 162,346
  • 23
  • 271
  • 359
  • Thank you!! that is the next issue I had and was up all last night trying to figure it out. Once I saw your post this morning it solved my problem! By the way, what is the reason for making a copy? Isn't retain enough on the original instance since blocks are objc objects. – vgr Jun 16 '11 at 17:57
  • Blocks start out on the stack and need to be copied off the stack if they are to live beyond the stack frame... Having it done on `retain` would have changed the semantics of retain in a rather unpleasant fashion. – bbum Jun 16 '11 at 18:21
  • 1
    this is Objective-C, so you can use `[... copy]` and `[... release]` – newacct May 24 '12 at 17:53
  • 1
    You can, yes. I use Block_copy() and Block_release() because it gives a nice hook for searching for anywhere that I'm doing block memory management manually (which, no surprise, is often a source of unnecessary fragility or a sign that I'm re-inventing a wheel already offered by the system). – bbum May 24 '12 at 18:57
12

If you are using ARC, use -copy:

 void (^handlerCopy)(NSURLResponse*, NSData*, NSError*) = [handler copy];
 [dict setObject:handlerCopy forKey:@"foo"];
Johan Kool
  • 15,637
  • 8
  • 64
  • 81
  • Blocks are stack based, so you need to [block copy] before adding to the dictionary, if you want they work as expected. Further details: https://developer.apple.com/reference/foundation/nsmutabledictionary/1416335-setvalue?language=objc – Lubbo Nov 08 '16 at 16:11
5

"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];
newacct
  • 119,665
  • 29
  • 163
  • 224
4

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.

Binks
  • 1,796
  • 1
  • 13
  • 11
  • Blocks are stack based, so you need to [block copy] before adding to the dictionary, if you want they work as expected. Further details: https://developer.apple.com/reference/foundation/nsmutabledictionary/1416335-setvalue?language=objc – Lubbo Nov 08 '16 at 16:10