开发者

Strange crash with block and phantom block argument exchange

开发者 https://www.devze.com 2023-03-21 12:05 出处:网络
I\'m totally stumped with this one and hope someone could help me out here: Class A: - (void)setBlock:(BOOL(^)(id sender))block {

I'm totally stumped with this one and hope someone could help me out here:

Class A:

- (void)setBlock:(BOOL(^)(id sender))block {
    myBlock = Block_copy(block);
}

- (BOOL)runBlock:(id)sender {
    myBlock(sender);
}

Class B:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotificationx {
    //The outer block provides behaviour according to strategy pattern:
    [classAInstance setBlock:^BOOL(id sender) {
        NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
        //The inner block is a special case of behaviour where I want the task to run asynchronously:
        [queue addOperationWithBlock:^(void) {
            NSLog(@"sender: %@", [sender class]);
            [sender doSomething];
        }];
        return YES;
    }];
}

Then later when a GUI event causes classAInstance to call - (BOOL)runBlock; (which it is supposed to be) I get the following crash stac开发者_如何转开发k:

    0 objc_exception_throw
    3 __forwarding_prep_0___
    4 __58-[ClassB applicationDidFinishLaunching:]_block_invoke_037
    5 -[NSBlockOperation main]
    11 start_wqthread

And the very last debug log that I get is this:

    sender: __NSMallocBlock__
    -[__NSMallocBlock__ doSomething]: unrecognized selector sent to instance 0x10043f2a0

Now why did the block's argument suddenly turn into __NSMallocBlock__ in first place? I was clearly passing something else (namely sender) to it, wasn't I?


It looks like when you call runBlock you are passing a block as the sender. I was able to run your code just fine. Matt Gallagher has an article on How blocks are implemented which may help you debug your problem.

If you copy an NSStackBlock, it will return an NSMallocBlock (indicating its changed allocation location).


Found the reason. Duh, I feel stupid now.

The mistake was attaching the block (as well as a second one) to my object as associated objects via:

static NSString * const FirstBlockKey;
static NSString * const SecondBlockKey;

objc_setAssociatedObject(self, FirstBlockKey, blockA, OBJC_ASSOCIATION_COPY);
objc_setAssociatedObject(self, SecondBlockKey, blockB, OBJC_ASSOCIATION_COPY);

while I should clearly have used this:

objc_setAssociatedObject(self, (void *)&FirstBlockKey, block, OBJC_ASSOCIATION_COPY);

FirstBlockKey and SecondBlockKey themselves are both 0x0 obviously, while their own pointers are not.

This way it simply called the wrong block (as they both were assigned to the same `0x0' key). The blocks had different return and argument types, which seem to have caused the strange exchange of passed block arguments. Working fine now.


That being said: Joe, Ryan & wbyoung, thanks for your efforts guys!


Joe's answer seems pretty much like it's likely right. You'll have to share the code that calls runBlock as well for someone to know what's actually happening, but given the name of the method you've provided, it probably looks something like:

[classAInstance runBlock:^BOOL(id sender) {
   // some code
}];

Which would actually print NSStackBlock instead (unless of course the block was copied first in which case it would be an NSMallocBlock).

[classAInstance runBlock:@"Hello world"];

Would result in the output of:

sender: NSString
-[NSString doSomething]: unrecognized selector sent to instance

Without your runBlock call, though there's no way to know where things have gone wrong. It's also possible that you are calling the method with the right argument, but that you have a memory allocation issue which leads to a block being allocated in the area you expect another object to exist, but this is probably less likely.


This is probably your problem:

NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];

You're creating an NSOperationQueue, then autoreleasing it, then expecting it to do work for you. If you want to do your work asynchronously in a separate thread there, use dispatch_async() with a concurrent queue instead:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^() {
    NSLog(@"sender: %@", [sender class]);
    [sender doSomething];
});
0

精彩评论

暂无评论...
验证码 换一张
取 消