大家好,欢迎来到IT知识分享网。
Dealloc怎么写(dealloc的方法实现建议放的位置)
推荐的代码组织方式是将dealloc方法放在实现文件的最前面(直接在@synthesize以及@dynamic之后)
顺便提一下,@synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法,当然定义一个属性的时候,系统会默认编写了,而 @dynamic 的用法则相反,属性的 setter 与 getter 方法由用户自己实现,不自动生成。
在dealloc方法中通常需要做的有移除通知或监听操作,或对于一些非Objective-C对象也需要手动清空,比如CoreFoundation中的对象。
MRC下就要手动释放、置空变量等操作后还需要调用父类的dealloc,而ARC下除了CoreFoundation的对象需要手动释放以及KVO监听移除外(NSNotification 在iOS9之后也不需要手动移除了),基本就没了,当然ARC的内存销毁具有一定的滞后性,也可将一些变量手动置空,也就是告诉系统这些变量已经使用完毕可以释放了,当然也可以不做任何操作,系统会自动释放这些成员变量或者属性。
MRC下dealloc 方法
ARC下系统会帮助我们释放该对象所包含的实例变量,但是有些对象还是需要们自己去释放的(比如Core Foundation框架下的一些对象),另外通知中观察者的移除,代理置空,停止timer等;
示例如下所示: 一定不能有 [super dealloc];
- (void)dealloc{
[[NSNotificationCenterdefaultCenter] removeObserver:self];//移除通知观察者 [[XMPPManager sharedManager] removeFromDelegateQueue:self];//移除委托引用 [[MyClass shareInstance] doSomething ]//其他操作 scrollView.delegate=nil; [timer invalidate]; }
ARC 对于Core Foundation对象的内存管理是无效,需要手动添加CFRelease、CFRetain消息
对于CFRelease、CFRetain可以直观认为与Object-C中的retain、release等价。因此对于底层Core Foundation对象,依然需要手动引用计数来管理内存。
// 创建 CFStringRef 对象 CFStringRef strRef = CFStringCreateWithCString(kCFAllocatorDefault, "hello world", kCFStringEncodingUTF8); // 创建 CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", 12, NULL); // 引用计数加一 CFRetain(strRef); CFRetain(fontRef); // 引用计数减一 CFRelease(strRef); CFRelease(fontRef);
Dealloc常见错误
dealloc中置空操作
- (void)dealloc {
NSLog(@"BaseModel dealloc"); self.baseName = nil; }
这个表面看上去没什么问题,在《Effective Objective-C 2.0》一书中第7条提到:
]在对象内部尽量直接访问实例变量,而在初始化方法和dealloc方法中,总是应该直接通过实例变量来读写数据。
首先self.name意为调用属性name的setter方法进行赋值
-(void)setName:(NSString*)name {
_name = name; }
即将nil作为参数传递到setName:方法中,其方法内部本身执行的仍为_name = nil,乍看来和手动在dealloc中书写_name = nil没有什么区别,但是setter方法并没有看起那样简单,若在MRC中通常setter方法如下所示:
- (void)setName:(NSString*)name {
if (_name != name) {
[_name release]; _name = [name retain]; } }
对于setter赋值方法采用的为释放旧值保留新值的方式。直接调用_name=nil避免了指针转移问题且避免了Objcetive-C的“方法派发”操作,因此直接调用_name=nil相比self.name=nil执行效率会高一些。
除了文中所说的加快访问速度之外,但是如果用法不巧当的话,会出现不必要的崩溃问题。下面举个简单的例子分析一下:
定义了一个BaseModel 基类,基类中演示了使用self.baseName = nil:
// BaseModel.h
@interface BaseModel : NSObject @property (nonatomic, copy) NSString * _Nullable baseName; @end // BaseModel.m #import "BaseModel.h" @implementation BaseModel - (void)dealloc {
NSLog(@"BaseModel dealloc"); self.baseName = nil; } - (void)setBaseName:(NSString *)baseName {
_baseName = baseName; NSLog(@"BaseModel setBaseName:%@", baseName); } @end
同时定义了一个子类SubModel继承自BaseModel,子类中重写了baseName 的setter方法,并获取baseName进行其他操作
#import "SubModel.h" @implementation SubModel - (void)dealloc {
NSLog(@"SubModel dealloc"); } - (void)setBaseName:(NSString *)baseName {
[super setBaseName:baseName]; NSLog(@"SubModel setBaseName:%@", [NSString stringWithString:baseName]); / [NSString stringWithString:baseName] 原因是[NSString stringWithString:baseName] 这里,baseName是nil,而这个方法是不允许传nil参数的, Summary Returns a string created by copying the characters from another given string. Declaration + (instancetype)stringWithString:(NSString *)string; Parameters aString The string from which to copy characters. This value must not be nil. Important Raises an NSInvalidArgumentException if aString is nil. Returns A string created by copying the characters from aString. */ } @end
//VC.m - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
SubModel * m = [[SubModel alloc]init]; [m setBaseName:@"mhf"]; / 打印结果 2020-05-16 11:14:04.+0800 OCEssence[4731:] BaseModel setBaseName:mhf 2020-05-16 11:14:52.+0800 OCEssence[4731:] SubModel setBaseName:mhf 2020-05-16 11:15:04.+0800 OCEssence[4731:] SubModel dealloc 2020-05-16 11:15:07.099555+0800 OCEssence[4731:] BaseModel dealloc 2020-05-16 11:15:33.+0800 OCEssence[4731:] BaseModel setBaseName:(null) 2020-05-16 11:15:58.+0800 OCEssence[4731:] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -[NSPlaceholderString initWithString:]: nil argument' 2 Foundation 0x00007fff2577d523 -[NSPlaceholderString initWithValidatedFormat:validFormatSpecifiers:locale:arguments:error:] + 0 3 Foundation 0x00007fffb +[NSString stringWithString:] + 45 4 OCEssence 0x00000001096c4267 -[SubModel setBaseName:] + 135 5 OCEssence 0x00000001096cdf01 -[BaseModel dealloc] + 65 6 OCEssence 0x00000001096c41d4 -[SubModel dealloc] + 68 7 libobjc.A.dylib 0x00007fffd6 _ZN11objc_object17sidetable_releaseEb + 174 8 OCEssence 0x00000001096c4589 -[ViewController touchesBegan:withEvent:] */ }
子类SubModel被释放会调用子类的dealloc方法,然后会调用父类BaseModel的dealloc方法,此时父类中通过setter方法来赋值nil,而子类SubModel重写了,子类拿到nil来处理导致崩溃问题
究竟属性是否需要手动置空释放?实际上来说,是不需要手动释放的,因为dealloc中.cxx_destruct会处理。当然因为执行是有一定延迟性,为了节省资源,在确保属性没利用价值的时候可以手动清空。
dealloc中使用__weak
举个简单例子来模拟实际复杂业务场景:
// SubModel.m #import "SubModel.h" @implementation SubModel - (void)dealloc {
NSLog(@"SubModel dealloc"); [self performSelectorWhenDealloc]; } -(void)performSelectorWhenDealloc {
__weak typeof(self) weakSelf = self; // 模拟复杂的block结构,需要弱引用解除循环引用 void(^block)(void) = ^{
[weakSelf test]; }; block(); } - (void)test {
NSLog(@"test"); } @end //VC.m - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
SubModel * m = [[SubModel alloc]init]; [m setBaseName:@"mhf"]; } / 打印结果 2020-05-16 12:09:04.+0800 OCEssence[5430:] SubModel dealloc objc[5430]: Cannot form weak reference to instance (0xab60b0) of class SubModel. It is possible that this object was over-released, or is in the process of deallocation.
先了解一下__weak 到底做了什么操作,通过clang 转换的代码是这样的 attribute((objc_ownership(weak))) typeof(self) weakSelf = self; 这样还是看不出问题,我们看回堆栈,堆栈崩在objc_initWeak 函数中,我们可以看看Runtime 源码 objc_initWeak 函数的定义是怎么样的:
id objc_initWeak(id *location, id newObj) {
if (!newObj) {
*location = nil; return nil; } return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); }
可以留意到,内部调用了 storeWeak 函数,其中有个模板名称是DoCrashIfDeallocating 不难猜到,当调用到了 storeWeak 函数的时候,如果释放过程中存储,那就会crash,函数最终会调用register函数 id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, bool crashIfDeallocating)
id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, bool crashIfDeallocating) {
... if (deallocating) {
if (crashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of " "class %s. It is possible that this object was " "over-released, or is in the process of deallocation.", (void*)referent, object_getClassName((id)referent)); } else {
return nil; } } ... }
在这就找到了崩溃时打印出信息了。通过上面的分析,我们也知道,__weak 其实就是会最终调用objc_initWeak 函数进行注册。抱着求学的态度,可以在clang 8.7 对objc_initWeak函数描述中找到答案:
dealloc中使用GCD
GCD相信大家平时用得不少,但在Dealloc方法里面使用GCD大家有没有注意呢,先来举个简单例子,我们在主线程中创建一个定时器,然后类被释放的时候销毁定时器,相关代码如下
- (void)dealloc {
[self invalidateTimer]; } - (void)fireTimer {
__weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{
if (!weakSelf.timer) {
weakSelf.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"TestDeallocModel timer:%p", timer); }]; [[NSRunLoop currentRunLoop] addTimer:weakSelf.timer forMode:NSRunLoopCommonModes]; } }); } - (void)invalidateTimer {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.timer) {
NSLog(@"TestDeallocModel invalidateTimer:%p model:%p", self->_timer, self); [self.timer invalidate]; self.timer = nil; } }); }
补充说明一下,定时器的释放和创建必须在同一个线程,这个也是比较容易犯的错误点,官方描述如下:
说回正题,当定时器所在类被释放后,此时调用invalidateTimer 方法去销毁定时器的时候就会出现崩溃情况。
崩溃报错:Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
出现访问野指针问题了,原因其实不难想到程序代码默认是在主线程主队列中执行,而dealloc中异步执行主队列中释放定时器释放,GCD会强引用self,此时dealloc已经执行完成了,那么self 其实已经被free释放掉了,此时销毁内部再调用self就会访问野指针。
我们来继续分析一下,GCD为啥会强引用self,以及简单分析一下GCD的调用时机问题。
强引用问题,简单转换如下代码
- (void)dealloc {
dispatch_async(dispatch_queue_create("Kong", 0), ^{
[self test]; }); }
struct __TestModel__dealloc_block_impl_0 {
struct __block_impl impl; struct __TestModel__dealloc_block_desc_0* Desc; TestModel *const __strong self; __TestModel__dealloc_block_impl_0(void *fp, struct __TestModel__dealloc_block_desc_0 *desc, TestModel *const __strong _self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __TestModel__dealloc_block_func_0(struct __TestModel__dealloc_block_impl_0 *__cself) {
TestModel *const __strong self = __cself->self; // bound by copy ((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("test")); } static void __TestModel__dealloc_block_copy_0(struct __TestModel__dealloc_block_impl_0*dst, struct __TestModel__dealloc_block_impl_0*src) {
_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);} static void __TestModel__dealloc_block_dispose_0(struct __TestModel__dealloc_block_impl_0*src) {
_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);} static struct __TestModel__dealloc_block_desc_0 {
size_t reserved; size_t Block_size; void (*copy)(struct __TestModel__dealloc_block_impl_0*, struct __TestModel__dealloc_block_impl_0*); void (*dispose)(struct __TestModel__dealloc_block_impl_0*); } __TestModel__dealloc_block_desc_0_DATA = {
0, sizeof(struct __TestModel__dealloc_block_impl_0), __TestModel__dealloc_block_copy_0, __TestModel__dealloc_block_dispose_0}; static void _I_TestModel_dealloc(TestModel * self, SEL _cmd) {
dispatch_async(dispatch_queue_create("Kong", 0), ((void (*)())&__TestModel__dealloc_block_impl_0((void *)__TestModel__dealloc_block_func_0, &__TestModel__dealloc_block_desc_0_DATA, self, ))); }
原理是一样的,简单总结一下:GCD任务底层通过链表管理,队列任务遵循FIFO模式,那么任务执行肯定就会有延迟性,同一时刻只能执行一个任务,只要dealloc任务执行先,那么此时block使用self就会访问野指针,因为dealloc内会有free操作。
Dealloc 源码分析
1、为什么dealloc 中使用GCD 会容易访问野指针?
出现野指针访问,那肯定就有free操作,我们查一下这个free是在哪一步执行:
//NSObject.mm // Replaced by NSZombies - (void)dealloc {
_objc_rootDealloc(self); }
最终调用在 objc-object.h 的 inline void objc_object::rootDealloc() 函数中
inline void objc_object::rootDealloc() {
if (isTaggedPointer()) return; // fixme necessary? if (fastpath(isa.nonpointer && // 是否是优化过的isa !isa.weakly_referenced && // 不包含或者不曾经包含weak指针 !isa.has_assoc && // 没有关联对象 !isa.has_cxx_dtor && // 没有c++析构方法 !isa.has_sidetable_rc))// 引用计数没有超出上限的时候可以快速释放,rootRetain(bool tryRetain, bool handleOverflow) 中设置为true {
assert(!sidetable_present()); free(this); } else {
object_dispose((id)this); }
可以看到满足一定条件下,对象指针会直接free释放掉,实际很多情况下都会走object_dispose((id)this) 函数,这个函数是在objc-runtime-new.mm文件,下面继续分析这个函数实现
id object_dispose(id obj) {
if (!obj) return nil; objc_destructInstance(obj); /// 释放内存 free(obj); return nil; }
终于看到了大概调用结构了,objc_destructInstance 函数后面分析,可以发现dealloc内部最终都会走free 操作,而这个操作就会导致野指针访问问题
1、为什么属性或者说成员变量会自动释放
开篇说了系统会自动释放属性或者成员变量,其实就是objc_destructInstance 函数的处理,其定义如下:
void *objc_destructInstance(id obj) {
if (obj) {
// Read all of the flags at once for performance. bool cxx = obj->hasCxxDtor(); bool assoc = obj->hasAssociatedObjects(); // This order is important. // 对象拥有成员变量时编译器会自动插入.cxx_desctruct方法用于自动释放,可打印方法名证明; if (cxx) object_cxxDestruct(obj); // 移除关联对象 if (assoc) _object_remove_assocations(obj); // weak->nil obj->clearDeallocating(); } return obj; }
代码上我已经部分注释了,我们直接看object_cxxDestruct 函数实现,后面_object_remove_assocations 是移除关联对象,就是我们分类中通过objc_setAssociatedObject 函数新增的就是关联对象,而clearDeallocating 则是把对应weak哈希表的置空
void object_cxxDestruct(id obj) {
if (!obj) return; // 如果是isTaggedPointer,不处理 ///为了节省内存和提高执行效率,苹果提出了Tagged Pointer的概念,避免32位机器迁移到64位机器内存翻倍 https://blog.devtang.com/2014/05/30/understand-tagged-pointer if (obj->isTaggedPointer()) return; object_cxxDestructFromClass(obj, obj->ISA()); }
static void object_cxxDestructFromClass(id obj, Class cls) {
void (*dtor)(id); // Call cls's dtor first, then superclasses's dtors. // 按继承链释放 for ( ; cls; cls = cls->superclass) {
if (!cls->hasCxxDtor()) return; /// .cxx_destruct是编译器生成的代码,在.cxx_destruct进行形如objc_storeStrong(&ivar, null)的调用后,对应的实例变量就被release和设置成nil了 dtor = (void(*)(id)) lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct); // 进行过动态方法解析后会标记IMP为_objc_msgForward_impcache,进行缓存后会进行消息分发 if (dtor != (void(*)(id))_objc_msgForward_impcache) {
if (PrintCxxCtors) {
_objc_inform("CXX: calling C++ destructors for class %s", cls->nameForLogging()); } (*dtor)(obj); } } }
看最终实现可以发现,内部就是通过继承链遍历调用lookupMethodInClassAndLoadCache 函数来进行后续释放,实际上是通过SEL_cxx_destruct 来执行C++的析构方法
简单总结一下dealloc的处理逻辑
object_cxxDestruct(obj)(释放成员变量) -> _object_remove_associations(obj) (移除关联对象) -> obj->clearDeallocating()(weak对象置为nil) -> free(obj)(free内存)
Dealloc 用法总结
1、dealloc中尽量直接访问实例变量来置空。
2、dealloc中切记不能使用__weak self。
3、dealloc中切线程操作尽量避免使用GCD,可利用performSelector,确保线程操作先于dealloc完成。
Dealloc 机制应用
从源码中可以知道,dealloc在对象置nil以及free之前,会进行关联对象释放,那么可以利用关联对象销毁监听dealloc完成,做一些自动释放操作,例如通知监听释放等,实际上网上也是有一些例子了。
@interface TestDeallocAssociatedObject : NSObject - (instancetype)initWithDeallocBlock:(void (^)(void))block; @end @implementation TestDeallocAssociatedObject {
void(^_block)(void); } - (instancetype)initWithDeallocBlock:(void (^)(void))block {
if (self = [super init]) {
self->_block = [block copy]; } return self; } - (void)dealloc {
if (self->_block) {
self->_block(); } } @end
然后在需要监听的地方创建关联对象,Block内处理即可,此时要注意Block引用的问题。
// 添加关联对象 TestDeallocAssociatedObject *object = [[TestDeallocAssociatedObject alloc] initWithDeallocBlock:^{
NSLog(@"TestDeallocAssociatedObject dealloc"); }]; objc_setAssociatedObject(self, &KTestDeallocAssociatedObjectKey, object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
造成ViewController不释放的原因可能有很多。遇到dealloc不调用的时候只需要检查您的ViewController中是否存在以下几个问题:
常用第三方中dealloc方法
//AFNetworkActivityIndicatorManager.m - (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self]; [_activityIndicatorVisibilityTimer invalidate]; }
// SDWebImageDownloader.m - (void)dealloc {
[self.session invalidateAndCancel]; self.session = nil; [self.downloadQueue cancelAllOperations]; SDDispatchQueueRelease(_barrierQueue); }
//FMDataBase.m - (void)dealloc {
[self close]; FMDBRelease(_openResultSets); FMDBRelease(_cachedStatements); FMDBRelease(_dateFormat); FMDBRelease(_databasePath); FMDBRelease(_openFunctions); #if ! __has_feature(objc_arc) [super dealloc]; #endif }
//SocketRocket/SRProxyConnect.m - (void)dealloc {
// If we get deallocated before the socket open finishes - we need to cleanup everything. [self.inputStream removeFromRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode]; self.inputStream.delegate = nil; [self.inputStream close]; self.inputStream = nil; self.outputStream.delegate = nil; [self.outputStream close]; self.outputStream = nil; }
其他
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self]; dispatch_sync(self.taskQueue, ^{
if (self.componetInstance) {
self.isRunning = NO; AudioOutputUnitStop(self.componetInstance); AudioComponentInstanceDispose(self.componetInstance); self.componetInstance = nil; self.component = nil; } }); } - (void)dealloc {
[UIApplication sharedApplication].idleTimerDisabled = NO; [[NSNotificationCenter defaultCenter] removeObserver:self]; [_videoCamera stopCameraCapture]; if(_gpuImageView){
[_gpuImageView removeFromSuperview]; _gpuImageView = nil; } } - (void)dealloc{
[self removeObserver:self forKeyPath:@"isSending"]; } - (void)dealloc; {
free(rawImagePixels); free(cornersArray); }
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/129461.html