iOS 5 Twitter框架和completionHandler块 - "在此块中强引用'self'可能会导致保留循环"

10
我刚刚开始学编程和Objective-C,试图找出代码的问题所在。我的代码使用了iOS 5 Twitter框架。我使用了苹果提供的大部分示例代码,所以一开始我并不知道我在使用一个块作为完成处理程序。
现在我收到来自Xcode 4的两条消息,分别是“1. 块将被强烈保留由捕获对象强烈保留”和“在此块中强烈捕获“self”可能会导致保留周期”。
基本上,我所做的就是删除苹果在他们的完成处理程序中使用的代码(switch语句与TWTweetComposeViewControllerResultCancelled&TWTweetComposeViewControllerResultDone),并使用我的if语句与[imagePickerController sourceType]
因此,在推文中添加图像后调用sendTweet
我希望有人能解释一下为什么会发生这种情况以及我如何解决它。此外:我可以将完成处理程序代码放入方法中而不是块中吗?
- (void)sendTweet:(UIImage *)image
{
    //adds photo to tweet
    [tweetViewController addImage:image];

    // Create the completion handler block.
    //Xcode: "1. Block will be retained by an object strongly retained by the captured object"
    [tweetViewController setCompletionHandler:
                             ^(TWTweetComposeViewControllerResult result) {
            NSString *alertTitle,
                     *alertMessage,
                     *otherAlertButtonTitle,
                     *alertCancelButtonTitle;

            if (result == TWTweetComposeViewControllerResultCancelled) 
            {
                //Xcode: "Capturing 'self' strongly in this block is likely to lead to a retain cycle"
                if ([imagePickerController sourceType])
                {
                    alertTitle = NSLocalizedString(@"TCA_TITLE", nil);
                    alertMessage = NSLocalizedString(@"TCA_MESSAGE", nil);
                    alertCancelButtonTitle = NSLocalizedString(@"NO", nil);
                    otherAlertButtonTitle = NSLocalizedString(@"YES", nil);

                    //user taps YES
                    UIAlertView *alert = [[UIAlertView alloc] 
                                             initWithTitle:alertTitle 
                                                   message:alertMessage 
                                                  delegate:self   // Note: self
                                         cancelButtonTitle:alertCancelButtonTitle 
                                         otherButtonTitles:otherAlertButtonTitle,nil];
                    alert.tag = 1;
                    [alert show];                
                }            
            }
4个回答

21

基本问题在于你在一个块中使用了self。该块被对象保留,而块本身也保留了对象。因此,你有一个保留循环,因此两者都可能永远不会被释放,因为它们都有一个指向它们的引用。幸运的是,这里有一个简单的解决方法:

通过使用所谓的弱引用来引用self,块将不再保留该对象。然后对象可以稍后被释放,这将释放块(将MyClass设置为适当的类型):

// before your block
__weak MyObject *weakSelf = self;

在你的代码块中,现在可以使用weakSelf代替self。需要注意的是,这只适用于使用ARC的iOS 5。

阅读这篇文章还有一个非常好的长篇解释:如何在实现API时避免在块中捕获self?该文章还介绍了如何在没有ARC和iOS 4上避免这种情况。


谢谢你的帮助!由于我是一个完全的初学者,我在如何真正实现这个方面遇到了问题。最后,我使用了__weak UIImagePickerController *weakSelf = imagePickerController;并将我的if语句更改为if([weakSelf sourceType])。Xcode 4不再显示任何错误,所以我想我做对了。(?) - iMaddin
+1 @Dennis 感谢您的回答。请也解释一下何时使用“__block”。例如,我有以下语法:__block HomeViewController *weakSelf = self; - HDdeveloper

3

你的代码块中保留了self,因为你将self作为UIAlertView的代理。如果self也保留了这个代码块,它们就会相互保留,形成保留环。

可以尝试

 __block WhateverTypeSelfIs *nonRetainedSelfForBlock = self;
[tweetViewController setCompletionHandler: 

并且。
UIAlertView *alert = [[UIAlertView alloc] 
                                 initWithTitle:alertTitle 
                                 message:alertMessage 
                                 delegate:nonRetainedSelfForBlock 
                                 cancelButtonTitle:alertCancelButtonTitle 
                                 otherButtonTitles:otherAlertButtonTitle,nil];

请查看苹果文档,部分对象和块变量。在块内引用的对象将被保留,除非您使用__block。


谢谢您指出我的块中有问题的UIAlertView。由于我正在使用ARC,因此我认为Xcode告诉我不能使用__block。我改用了TriPhoenix提到的__weak,效果很好。谢谢! - iMaddin
+1 @Terry Wilcox,能否解释一下"__block"和"__weak"之间的区别?谢谢。 - HDdeveloper
@HDdeveloper 在stackoverflow上的这个问题中,weak和block reference有什么区别? - Terry Wilcox

1

根据我在其他地方看到的内容,“weak”不能用于符合ARC标准的代码,必须使用“_unsafe_unretained”。这就是我为了解决苹果示例应用程序“AVPlayerDemo”中的“在此块中强烈捕获'self'可能会导致保留循环”的警告所做的事情:

__unsafe_unretained id unself = self;
mTimeObserver = [mPlayer addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(interval, NSEC_PER_SEC) 
                            queue:NULL /* If you pass NULL, the main queue is used. */
                            usingBlock:^(CMTime time) 
                                        {
                                            /* 'unself' replaced 'self' here: */
                                            [unself syncScrubber];
                                        }];

0

有一种相当令人惊讶的方法可以消除警告。请注意,只有在您确定不会创建保留循环或者确定稍后会自行打破它(例如,在完成处理程序时将其设置为nil)时才执行此操作。如果您控制界面,则重命名方法,使其不以“set”开头。编译器似乎只在方法名称以“set”开头时才发出此警告。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接