[iOS]保持类存活

99 阅读3分钟

说明

类初始化后,如果不被其他类持有,执行完就会被释放。大多数情况下使用属性持有就能保证存活,但是遇到一些无法被持有的类时,就需要一些特殊方法保证初始化后继续存活。

实践

一、代理属性声明为strong

git地址 github.com/zzqlplq/ZZQ…

以前写的头像选择的库,简化调用逻辑,只有一个接口获取选择后的图片

[ZZQAvatarPicker startSelected:^(UIImage * _Nonnull image) {
    [self.avatarBtn setImage:image forState:UIControlStateNormal];
}];

大概逻辑是ZZQAvatarPicker调用ZZQResourceSheetView弹出图片来源弹框,ZZQResourceSheetView通过代理传值,ZZQAvatarPicker则作为代理的接收者执行具体的图片选择操作,最后将选好的图片用block传递出去。

问题

因为ZZQAvatarPicker只有类方法,所以外部无法持有,加上内部也没有自己持有自己,一旦执行完弹框操作,就会直接释放,无法执行ZZQResourceSheetView的代理方法。

解决

作为单一的工具类,不想也不需要被外部持有,所以外部的类方法调用不想做改变,只能在内部做处理,内部要有类强引用ZZQAvatarPicker。然后想到ZZQResourceSheetView的代理,一般代理都是用weak修饰,防止循环引用,不过现在的情况就是需要有强引用,将weak改为strong后,因为强引用,ZZQAvatarPicker不会主动释放,可以正常执行代理方法。执行完代理方法后,要记得主动将代理置为nil,不然会导致内存泄漏。

- (void)clean {
    self.toolView.delegate = nil;
    self.toolView = nil;
}

二、使用block持有自己

项目中有个专门的Router类管理跳转,调用接口传参,然后根据接口的返回值动态跳转到不同的控制器,也可以根据id参数打开本地配置好的控制器,后来接入的CTMediator做组件化,这个类就不被直接持有,再然后项目中有多个地方需要跳转到系统功能,比如打电话,发邮件,发短信等,然后把系统跳转方法也统一封装到Router类。

// CTMediator (Router)
- (void)RouteOpenPCProgramWithId:(NSString *)id
- (void)RouteOpenMobileProgramWithId:(NSString *)id
- (void)RouteOpenNativeWithType:(NativeRouteType)type
// Target_Router
- (void)Action_RouteOpenPCProgram:(NSDictionary *)params;
- (void)Action_RouteOpenMobileProgram:(NSDictionary *)params;
- (void)Action_RouteOpenNative:(NSDictionary *)params;
// 调用
[[CTMediator sharedInstance] KRouteOpenNativeWithType:LKNativeRouteTypeEmail]

问题

因为跳转的逻辑处理全部在Router中处理,外部只需要在跳转的时候调用方法即可。一般情况下,Router会在跳转成功后直接释放。可是跳转系统功能时,需要在代理方法中做一些处理,但是会因为Router被释放导致代理方法无法执行。CTMediator支持Tagert的缓存,但是需要调用CTMediator的对象方法进行释放,Router不应该知道自己被持有,因为它自身的功能高度聚合,最好生命周期也能自己控制。

解决

因为系统的代理已经用weak修饰,所以无法通过代理相互引用保持存活。想到还有一种方式,那就是block和self相互引用

self.block = ^{
  self.xxx
}

为了保证能正常释放,可以加个中间值, self->block->wself->self

__block Target_Router *wself = self;
self.block = ^{
    wself = nil;
}

调用block后,将wself置为nil,无法构成循环引用,Router就会自动释放。
改个可读性高的名字,调整一下调用方式

@property (nonatomic, copy) void(^autoRelease)(void);

- (void)keepAlive {
    __block Target_LKRouter *wself = self;
    self.autoRelease = ^{
        wself = nil;
    };
}

具体使用

- (void)sendEmail:(NSString *)email {
    MFMailComposeViewController *mailPicker = [[MFMailComposeViewController alloc] init];
    mailPicker.mailComposeDelegate = self;
     //...
    [self keepAlive]; // 需要用到代理的地方,调用方法保证能存活
}

- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
    [controller dismissViewControllerAnimated:YES completion:^{
        self.autoRelease();  // 代理方法中调用block进行释放
    }];
}