深化了解替代单纯回忆

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

最近发现出现了由于重写dealloc书写不标准导致的线上问题,所以想扫除一下该知识盲点。

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

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

  • dealloc是当目标从内存中开释时履行的办法,由体系调用,开发者制止主动履行
    • 目标和instance variable开释后,则不能再企图拜访,否则会由于拜访野指针而发生Crash等严重问题
  • 官方要求重写dealloc时中需求对目标类型的instance variable进行开释(如履行release办法),且必须在办法最终履行父类的dealloc办法(ARC中编译器已自动完成这些工作)
    • 能够看出目标的开释进程是从子类到父类,最终到达NSObject
  • dealloc或许履行在任何线程
    • 源码层面的剖析能够参阅iOS摸鱼周报 第三十八期
  • 不能确保,在程序运转期间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中存在各种不确定性

  • 比方很或许有的类重写了set或get办法,里面的逻辑或许做了其他不允许在dealloc中做的工作

经典案例如下:

@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:
  • NSString stringWithString:self.debuginfo]办法要求,string参数必须不为nil,但此刻_debugInfo现已是nil,所以溃散

尽量不要履行异步使命

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

// AFHTTPBodyPart
- (void)dealloc {
    // close办法为NSInputStream体系类所供给
    if (_inputStream) {
        [_inputStream close];
        _inputStream = nil;
    }
}
// AFNetworkActivityIndicatorManager
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [_activationDelayTimer invalidate];
    [_completionDelayTimer invalidate];
}

GPUImage

// 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

// 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不会捕获当时目标
  • 不要开释体系的、稀缺的资源

参阅

  • Advanced Memory Management Programming Guide
  • Effective Objective-C 2.0: 52 Specific Ways to Improve Your IOS and OS X Programs-Item 31: Release References and Clean Up Observation State Only in dealloc
  • Effective Objective-C 2.0-Item 31中文翻译
  • LLVM-AutomaticReferenceCounting
  • iOS摸鱼周报 第四十四期-Dealloc 运用留意事项及解析
  • ARC下,Dealloc还需求留意什么?
  • ARC中dealloc进程以及.cxx_destruct的探究