什么会导致块在 ARC 下不保留引用的 Objective-C 对象?

Posted

技术标签:

【中文标题】什么会导致块在 ARC 下不保留引用的 Objective-C 对象?【英文标题】:What can cause a block to not retain a referenced Objective-C object under ARC? 【发布时间】:2013-02-08 00:16:56 【问题描述】:

我收到了这样的崩溃报告:

*** 由于未捕获的异常“NSInvalidArgumentException”而终止应用程序,原因:“-[__NSMallocBlock__ CGImage]:无法识别的选择器已发送到实例 0x1fb17f90”

这通常发生在一个对象已被解除分配并且另一个对象现在与被解除分配的对象位于同一地址时。

这是我的代码(我的应用程序中唯一调用CGImage 方法的地方):

@implementation UIImageView (MyApp)

- (void) setImageWithObject:(id)object

    NSURLRequest *imageRequest = [NSURLRequest requestWithURL:[object URL]];
    __typeof__(self) __weak weakSelf = self;
    [self setImageWithURLRequest:imageRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) 
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
            CGImageRef imageRef = [image CGImage];
            CGRect rect = CGRectMake(0.f, 0.f, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef));
            CGContextRef bitmapContext = CGBitmapContextCreate(NULL, (size_t)roundf(CGRectGetWidth(rect)), (size_t)roundf(CGRectGetHeight(rect)), CGImageGetBitsPerComponent(imageRef), CGImageGetBytesPerRow(imageRef), CGImageGetColorSpace(imageRef), CGImageGetBitmapInfo(imageRef));
            UIImage *decompressedImage = image;
            if (bitmapContext)
            
                CGContextDrawImage(bitmapContext, rect, imageRef);
                CGImageRef decompressedImageRef = CGBitmapContextCreateImage(bitmapContext);
                decompressedImage = [UIImage imageWithCGImage:decompressedImageRef scale:image.scale orientation:image.imageOrientation];
                CGImageRelease(decompressedImageRef);
                CGContextRelease(bitmapContext);
            
            dispatch_async(dispatch_get_main_queue(), ^
                weakSelf.image = decompressedImage;
            );
        );
     failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) 
        NSLog(@"%@", error);
    ];


@end

我假设 image 对象应该由块自动保留(我正在使用 ARC),因此在使用它的代码在默认优先级队列上执行之前不能释放 image 对象。崩溃似乎意味着它没有被保留。

我的假设是错误的还是我遗漏了什么?

【问题讨论】:

这段代码是正确的。我只能想到两个可能性:1.崩溃发生在其他地方,而不是在这段代码中。 2. 你的应用程序中发生了某种内存损坏。 一些想法:1) 在 CGImage 调用周围放置一个 try/catch,看看你能不能捕捉到它? 2)直到weakSelf.image = assignment之后才释放decompressedImageRef。 我在 OS X 上看到了一些 CGImage 例程和位图上下文的错误,其中它很短,保留并且图像数据是共享的(我认为是写时复制语义)。这闻起来可能是相关的。 “这通常发生在一个对象已被解除分配并且另一个对象现在与被解除分配的对象位于同一地址时。”如果您怀疑这种情况正在发生,测试它的正确方法是启用僵尸。 你如何声明你的成功块?如果您存储它并稍后调用它,请确保该块是一个复制属性。 【参考方案1】:

这不是类似于 ARC 下的 UIColor 和 CGColorRef 的行为吗?此处详述:http://blog.bignerdranch.com/296-arc-gotcha-unexpectedly-short-lifetimes/

那么你在哪里:

CGImageRef imageRef = [image CGImage];

我认为您需要在块中显式保留它,然后显式释放它,这样 ARC 就不会释放图像引用。

所以:

CGImageRef imageRef = CGImageRetain([image CGImage]);
...
CGImageRelease(imageRef);

【讨论】:

我描述的问题是image被提前释放,而不是imageRef ios 5.1.x 后“内部指针问题”已得到修复:“NS_RETURNS_INNER_POINTER 方法返回指针(Objective C 对象类型除外)已使用 clang 编译器属性 objc_returns_inner_pointer 进行修饰(编译时使用clang) 以防止编译器主动释放那些似乎不再被引用的消息的接收者表达式,而返回的指针可能仍在使用中。”这也适用于从属性返回的 CGImageRef。【参考方案2】:

您的块本身可能没有问题。有人调用该块;调用者可能已经传递了一个已释放的对象。我会在块的第一行做一个 NSLog (@"%@", image) 并查看调用代码。

【讨论】:

以上是关于什么会导致块在 ARC 下不保留引用的 Objective-C 对象?的主要内容,如果未能解决你的问题,请参考以下文章

iOS 内存管理

容易导致循环引用的场景的解决方案

在启用 ARC 的代码中修复警告“在此块中强烈捕获 [an object] 可能会导致保留周期”

ARC圆形保留检测

这会导致保留周期吗

当没有其他引用保留对象时,如何将 id 对象安全地存储在 ARC 下的 C++ void* 成员中?