dealloc in Objective-C该怎么写

深入理解代替单纯记忆

本文编写时间为:2023年10月07日,此时最新iOS 系统版本为iOS 17

最近发现出现了因为重写dealloc书写不规范导致的线上问题,于是想扫除一下该知识盲点。

关于dealloc的话题,网上资料有不少。本文是参考并将相关知识点进行汇总和列举,没有自己创造的新观点和知识

关于dealloc必须要了解的知识点

  • dealloc由系统调用,开发者禁止主动执行
  • dealloc可能执行在任何线程
  • 不能保证,在程序运行期间dealloc一定会被执行
    • 官方解释: When an application terminates, objects may not be sent a dealloc message. Because the process's memory is automatically cleared on exit, it is more efficient simply to allow the operating system to clean up resources than to invoke all the memory management methods.

dealloc中可以做什么 ✅

综合官方文档和Effective Objective-C 2.0,dealloc中能且仅能做的事有如下几个:

  • 释放当前对象持有的支持ARC对象的引用(ARC已自动处理,开发者无需添加逻辑)
  • 释放当前对象持有的不支持ARC的对象,比如CoreFoundation对象
  • 若当前对象对其他内容注册为了观察者,需要移除观察者,如KVO、NSNotificationCenter等

dealloc中不可以做什么 ❌

其实,除了上面dealloc中可以做什么部分提到的,其余的逻辑尽量都不要去尝试。下面列举几个易犯错的地方

  • 不要使用accessor操作instance variable
  • 尽量不要执行异步任务
  • 不要释放系统的、稀缺的资源,如file descriptors, network connections, and buffers or caches
  • 尽量避免执行除dealloc中可以做什么以外的方法调用

以下部分对上面几点做详细解释

不要使用accessor操作instance variable

根本原因在于,accessor只是语法糖,其背后会触发方法调用(消息发送),消息发送在dealloc中存在各种不确定性

经典案例如下:

objectivec 复制代码
@interface HWObject : NSObject
@property(nonatomic) NSString* info;
@end
    
@implementation HWObject
- (void)dealloc {
    self.info = nil;
}
- (void)setInfo:(NSString *)info {
    if (info)
    {
        _info = info;
        NSLog(@"%@",[NSString stringWithString:info]);
    }
}
@end

@interface HWSubObject : HWObject
@property (nonatomic) NSString* debugInfo;
@end

@implementation HWSubObject
- (void)setInfo:(NSString *)info {
    NSLog(@"%@",[NSString stringWithString:self.debugInfo]);
}
- (void)dealloc {
    _debugInfo = nil;
}
- (instancetype)init {
    if (self = [super init]) {
        _debugInfo = @"This is SubClass";
    }
    return self;
}
@end
  • HWSubObject实例释放时执行其dealloc方法,_debuginfo = nil之后_debugiinfo就被释放掉了
  • HWSubObject的dealloc方法执行到最后,会执行父类HWObject的dealloc,self.info会走到子类的setInfo:,进而导致访问了已经释放掉的_debuginfo而产生crash

尽量不要执行异步任务

dealloc方法结束后当前对象就释放掉了,此时如果异步任务还未结束,异步任务中但凡尝试访问释放掉的对象就crash

  • 即使通过类似block等技术(如GCD中async系列方法)尝试捕获当前对象,也无法阻止对象被释放

Note: 有的资料给出建议,可以使用一些同步方法(如performSelector)来完成异步的任务,这样就能避免当前对象被释放了。后面GPUImage的源码中有类似使用

不要释放系统的、稀缺的资源

  • 因为dealloc的执行时机、执行线程都是由系统控制,而并不是我们能够控制和清晰了解的,所以对于稀缺资源,我们不能依赖于对象的生命周期来控制
  • 如仍坚持这样做,那可能会导致系统资源延迟释放甚至一直无法释放,进而对整个应用产生影响

此时合理的做法是:提供主动释放稀缺的方法,外部使用者在资源使用结束时主动调用

尽量避免执行除dealloc中可以做什么以外的方法调用

查阅各种资料时,其实可以看出,dealloc执行过程中,其实系统已经进入对当前对象数据结构的清理过程了,此时的方法调用需要格外慎重,因为任何方法调用背后可能隐藏着各种业务逻辑,我们很难保证,这些逻辑都仅做了dealloc中可以做什么的事情

dealloc中能否直接访问instance variable

先说个人观点:可以,但要注意间接影响

理由如下:

LLVM-Clang10官方ARC文档明确表示:

The instance variables for an ARC-compiled class will be destroyed at some point after control enters the dealloc method for the root class of the class. The ordering of the destruction of instance variables is unspecified, both within a single class and between subclasses and superclasses.

instance variable的释放被延迟到了根类-NSObject的dealloc中

但,还是可能带来的间接影响,比如当对instance variable发送消息[_xx someMethod]

  • 如果someMethod做了一些不该在当前时机做的事情,那也会增加出问题风险

开源项目中dealloc是怎么写的

看一下几个优秀开源项目中,dealloc是如何写的,作为参考

AFNetworking

ini 复制代码
// AFHTTPBodyPart
- (void)dealloc {
    // close方法为NSInputStream系统类所提供
    if (_inputStream) {
        [_inputStream close];
        _inputStream = nil;
    }
}

// AFNetworkActivityIndicatorManager
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
    [_activationDelayTimer invalidate];
    [_completionDelayTimer invalidate];
}

GPUImage

scss 复制代码
// GLProgram
// 几个shader都是instance variable
- (void)dealloc
{
    if (vertShader)
        glDeleteShader(vertShader);
        
    if (fragShader)
        glDeleteShader(fragShader);
    
    if (program)
        glDeleteProgram(program);
       
}

// GPUImageToneCurveFilter
- (void)dealloc
{
    runSynchronouslyOnVideoProcessingQueue(^{
        [GPUImageContext useImageProcessingContext];

        if (toneCurveTexture)
        {
            glDeleteTextures(1, &toneCurveTexture);
            toneCurveTexture = 0;
            free(toneCurveByteArray);
        }
    });
}

SDWebImage

ini 复制代码
// SDWebImageDownloader
- (void)dealloc {
    [self.session invalidateAndCancel];
    self.session = nil;

    [self.downloadQueue cancelAllOperations];
}

// SDWebImageImageIOCoder
- (void)dealloc {
    if (_imageSource) {
        CFRelease(_imageSource);
        _imageSource = NULL;
    }
}

Swift类的deinit是否也需要注意这些问题

Swift中在重写deinit时,要比OC简单一些

  • 首先,Swift中没有accessor语法糖,所以就不存在直接、间接访问instance variable的问题。而且官方明确提到,deinit中property都是可以直接访问的
  • Swift也是应用ARC,所以无需主动释放支持ARC对象类型;同样需要释放不支持ARC类型的对象
  • 同样不建议执行异步任务,closure不会捕获当前对象
  • 不要释放系统的、稀缺的资源

参考

相关推荐
crasowas8 小时前
iOS - 超好用的隐私清单修复脚本(持续更新)
ios·app store
ii_best10 小时前
ios按键精灵脚本开发:ios悬浮窗命令
ios
Code&Ocean15 小时前
iOS从Matter的设备认证证书中获取VID和PID
ios·matter·chip
/**书香门第*/15 小时前
Laya ios接入goole广告,开始接入 2
ios
恋猫de小郭1 天前
什么?Flutter 可能会被 SwiftUI/ArkUI 化?全新的 Flutter Roadmap
flutter·ios·swiftui
网安墨雨1 天前
iOS应用网络安全之HTTPS
web安全·ios·https
福大大架构师每日一题1 天前
37.1 prometheus管理接口源码讲解
ios·iphone·prometheus
BangRaJun2 天前
LNCollectionView-替换幂率流体
算法·ios·设计
刘小哈哈哈2 天前
iOS 多个输入框弹出键盘处理
macos·ios·cocoa
靴子学长2 天前
iOS + watchOS Tourism App(含源码可简单复现)
mysql·ios·swiftui