首页 > 代码库 > UIActionSheet关闭动画过程中调用delegate = nil 导致的内存泄露

UIActionSheet关闭动画过程中调用delegate = nil 导致的内存泄露

UIActionSheet在动画期间(ActionSheet button点击之后,到didDismissWithButtonIndex调用完成之前)设置delegate为空会导致delegate无法释放。

先来看个例子:

例子中创建一个UIActionSheet,并在按钮点击之后0.1秒(关闭动画结束前)设置delegate = nil。

#import "LIViewController.h"
@class UIActionSheetDelegateImpl;
static UIActionSheetDelegateImpl * delegateImpl; 

@interface UIActionSheetDelegateImpl : NSObject <UIActionSheetDelegate>

@end

@implementation UIActionSheetDelegateImpl
// Called when a button is clicked. The view will be automatically dismissed after this call returns
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
    NSLog(@"%s", __func__);
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        delegateImpl = nil;
    });
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        actionSheet.delegate = nil;
    });
}
- (void)actionSheetCancel:(UIActionSheet *)actionSheet
{
    NSLog(@"%s", __func__);
}

- (void)willPresentActionSheet:(UIActionSheet *)actionSheet
{
    NSLog(@"%s\n", __func__);
}
- (void)didPresentActionSheet:(UIActionSheet *)actionSheet
{
    NSLog(@"%s\n", __func__);
}
- (void)actionSheet:(UIActionSheet *)actionSheet willDismissWithButtonIndex:(NSInteger)buttonIndex
{
    NSLog(@"%s\n", __func__);
}
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    NSLog(@"%s\n", __func__);
}

- (void)dealloc
{
    NSLog(@"%s\n", __func__);
}
@end


@interface LIViewController ()

@end


@implementation LIViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
}

- (void)viewDidAppear:(BOOL)animated
{
    delegateImpl = [UIActionSheetDelegateImpl new];
    
    UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"测试" delegate:delegateImpl cancelButtonTitle:@"cancel" destructiveButtonTitle:@"ok" otherButtonTitles:nil, nil];
    [actionSheet showInView:self.view];
}
@end

输出为:

[UIActionSheetTest[62028:60b] -[UIActionSheetDelegateImpl willPresentActionSheet:]
[UIActionSheetTest[62028:60b] -[UIActionSheetDelegateImpl didPresentActionSheet:]
[UIActionSheetTest[62028:60b] -[UIActionSheetDelegateImpl actionSheet:clickedButtonAtIndex:]
[UIActionSheetTest[62028:60b] -[UIActionSheetDelegateImpl actionSheet:willDismissWithButtonIndex:]

可以看到 UIActionSheetDelegateImpl delloc和actionSheet:willDismissWithButtonIndex并未调用, 也就是说UIActionSheetDelegateImpl对象并未释放。


当去掉delegate = nil调用时输出结果如下:

[UIActionSheetTest[62086:60b] -[UIActionSheetDelegateImpl willPresentActionSheet:]
[UIActionSheetTest[62086:60b] -[UIActionSheetDelegateImpl didPresentActionSheet:]
[UIActionSheetTest[62086:60b] -[UIActionSheetDelegateImpl actionSheet:clickedButtonAtIndex:]
[UIActionSheetTest[62086:60b] -[UIActionSheetDelegateImpl actionSheet:willDismissWithButtonIndex:]
[UIActionSheetTest[62086:60b] -[UIActionSheetDelegateImpl actionSheet:didDismissWithButtonIndex:]
[UIActionSheetTest[62086:60b] -[UIActionSheetDelegateImpl dealloc]

或者延长delegate = nil 调用时间为0.5秒之后(UIActionSheet关闭动画结束)也会输出上面结果, 也就是delegate = nil在actionSheet:didDismissWithButtonIndex:之后调用也能释放delegate。

如果直接在actionSheet:clickedButtonAtIndex:调用delegate = nil(actionSheet:willDismissWithButtonIndex:和actionSheet:didDismissWithButtonIndex:不会调用),dealloc能正常调用, 输出结果如下:

[UIActionSheetTest[62086:60b] -[UIActionSheetDelegateImpl willPresentActionSheet:]
[UIActionSheetTest[62086:60b] -[UIActionSheetDelegateImpl didPresentActionSheet:]
[UIActionSheetTest[62086:60b] -[UIActionSheetDelegateImpl actionSheet:clickedButtonAtIndex:]
[UIActionSheetTest[62086:60b] -[UIActionSheetDelegateImpl dealloc]


问题分析, 对动画期间设置delegate = nil导致不能释放retain/release如下:

#   Event Type  ? RefCt RefCt   Timestamp   Responsible Library Responsible Caller
0   Malloc  +1  1   00:06.236.929   UIActionSheetTest   -[LIViewController viewDidAppear:]
1   Retain  +1  2   00:10.498.951   UIKit   +[UIView(UIViewAnimationWithBlocks) _setupAnimationWithDuration:delay:view:options:factory:animations:start:animationStateGenerator:completion:]
2   Release -1  1   00:11.286.073   libdispatch.dylib   _dispatch_client_callout

如果在动画之后调用delegate = nil

#   Event Type  ? RefCt RefCt   Timestamp   Responsible Library Responsible Caller
0   Malloc  +1  1   00:01.150.499   UIActionSheetTest   -[LIViewController viewDidAppear:]
1   Retain  +1  2   00:02.919.288   UIKit   +[UIView(UIViewAnimationWithBlocks) _setupAnimationWithDuration:delay:view:options:factory:animations:start:animationStateGenerator:completion:]
2   Release -1  1   00:03.328.439   UIKit   -[UIViewAnimationState sendDelegateAnimationDidStop:finished:]
3   Release -1  0   00:04.286.073   libdispatch.dylib   _dispatch_client_callout

动画之前调用delegate= nil,结果如下:

#   Event Type  ? RefCt RefCt   Timestamp   Responsible Library Responsible Caller
0   Malloc  +1  1   00:06.236.929   UIActionSheetTest   -[LIViewController viewDidAppear:]
1   Release -1  0   00:04.286.073   libdispatch.dylib   _dispatch_client_callout

从中可以看到, 当_setupAnimationWithDuration和sendDelegateAnimationDidStop不配对时delegate就不能释放。