【问题标题】:Using self inside block crashes without error在块内使用 self 崩溃而没有错误
【发布时间】:2013-10-24 12:20:57
【问题描述】:

我有一个名为ServiceBrowser 的类。在这个类中,我有一个基于块的方法来搜索 NSNetServices。

我这样称呼方法:

[_serviceBrowser discoverServicesOfType:@"_theService._tcp."
                               inDomain:@"local."
                    didDiscoverServices:^(NSArray *services) {

                        NSLog(@"Services discovered %@", services);

                        [UIView fadeView:_button toAlpha:1.0 duration:0.5 completion:nil];

                    } didRemoveServices:^(NSArray *services) {

                        NSLog(@"Services removed %@", services);

                    } failure:^(NSString *message) {

                        NSLog(@"Failure %@", message);

                    }];

如果我删除对fadeView:toAlpha:duration:completion: 的调用,它会找到服务并将它们注销。仅当我在此块中使用 self 时,Xcode 才会崩溃,而控制台没有记录任何错误。

fadeView:toAlpha:duration:completion: 是 UIView 上的一个类别方法,它接受一个视图并将其淡入或淡出,这作为一个独立的方法工作得很好。问题是当我在块内使用_button 时它会崩溃。

我对此进行了调查,我认为这归结为保留周期。通过查看其他问题和博客文章,我应该在块内使用弱自我。

我尝试过使用__block id weakSelf = self;typeof(self) __weak w_self = self;,但都不起作用。

【问题讨论】:

  • 你确定你的对象在调用块时仍然存在吗?
  • 你的块是否在主线程上执行?
  • 我已经检查过了,它在主线程上。
  • “我已经对此进行了调查,我认为它归结为保留周期。”保持循环不会导致崩溃。它们与崩溃相反。当你过度释放时通常会发生崩溃,即没有足够的保留。

标签: ios objective-c objective-c-blocks


【解决方案1】:

您可以尝试修改您的类,以便将self 作为方法参数传递,并且该类将引用传递回块:

[_serviceBrowser discoverServicesOfType:@"_theService._tcp." inDomain:@"local." didDiscoverServices:^(NSArray *services, id sender) {
//                                                                                                                       ^^^^^^^^^ "sender" is "self"

                } didRemoveServices:^(NSArray *services) {

                } failure:^(NSString *message) {

                } fromSender:self];
//                ^^^^^^^^^^^^^^^ pass self here

类中的实现是这样的:

- (void)discoverServicesOfType:(NSString *)type inDomain:(NSString *)domain didDiscoverServices:^(NSArray *, id)discoveredBlock fromSender:(id)sender {
    NSMutableArray *services = [NSMutableArray array];
    // do some fancy stuff here
    discoveredBlock(services, sender);
}

Anc Ainu 也可能是对的。请检查对象是否仍然存在。

【讨论】:

  • 我也觉得有点 hacky。但实际上并非如此。你只是在传递指针......这没什么错。如果可行,请考虑接受:)
【解决方案2】:

您可以尝试以下代码,该代码在块中捕获self 的弱引用(Fooself 的类)。

该块现在也需要注意,UIKit 方法将在主线程上执行:

__weak Foo* weakSelf = self;
[_serviceBrowser discoverServicesOfType:@"_theService._tcp."
                               inDomain:@"local."
                    didDiscoverServices:^(NSArray *services) {
                        NSLog(@"Services discovered %@", services);
                        Foo* strongSelf = weakSelf;
                        if (strongSelf) {
                            dispatch_async(dispatch_get_main_queue(), ^{
                                [UIView fadeView:strongSelf.button 
                                         toAlpha:1.0 
                                        duration:0.5 completion:nil];
                            });
                        }
                    } didRemoveServices:^(NSArray *services) {
                         NSLog(@"Services removed %@", services);
                    } failure:^(NSString *message) {
                         NSLog(@"Failure %@", message);
                    }];

如果self在块执行时被释放,强引用strongSelf将是nil

编辑

为什么从 UI 元素中捕获弱指针优于捕获强指针?虽然这可能不是导致崩溃的原因,但它是一项重大改进。

下面的块强烈捕获self来证明这一点。请注意,当您直接引用 ivar 时,例如_button 而不是通过属性self.buttonself 将在块中隐式捕获,也就是说,它将一直保留到块完成后。

现在,块的效果是,UI 元素 self 将保持活动状态,直到块执行后,无论何时发生,无论视图是否可见:用户可能已切换到与此同时,还有许多其他视图和控制器。但是,self 在块完成之前不会被释放。这没有必要将内存中的资源(可能是大图像)保留到此时尚未释放。

[_serviceBrowser discoverServicesOfType:@"_theService._tcp."
                               inDomain:@"local."
                    didDiscoverServices:^(NSArray *services) {
                        NSLog(@"Services discovered %@", services);
                        dispatch_async(dispatch_get_main_queue(), ^{
                            [UIView fadeView:self.button 
                                     toAlpha:1.0 
                                    duration:0.5 completion:nil];
                        });
                    } didRemoveServices:^(NSArray *services) {
                         NSLog(@"Services removed %@", services);
                    } failure:^(NSString *message) {
                         NSLog(@"Failure %@", message);
                    }];

【讨论】:

  • strongSelf nil 还是它在崩溃时指向原始的 self?好吧,只有两种可能性:当块执行时 self 被释放:然后 strongSelf 将是 nil 并且您的块实际上什么都不做并且不应该崩溃。否则,strongSelf 不是nil,因此指向一个有效的Foo。那么,它为什么会崩溃呢?这表明您的问题在其他地方。您还可以通过在外部定义强指针来“保留”对象Foo 直到块完成,例如。 Foo* strongSelf = self; 然后在块内使用strongSelf
  • 为什么这会是相关的?该块目前对self 有很强的引用。所以不用担心self被释放。
  • @newacct 你是对的,_button 隐式捕获了self。但是,当视图在块执行之前消失时,执行动画是没有意义的。因此,weakSelf 指针应该避免这种情况。如果视图仍然可见,我上面的代码实际上是相同的。当它无论如何都崩溃时,问题就出在其他地方 - 可能不在主线程上执行块。
  • @StePrescott 我对代码进行了修改。如果块没有在主线程上执行,原始代码可能会因为调用 UIKit 方法而崩溃。现在,它在主线程上调度 UIKit 方法。