1看来大家在学习iOS的过程中都考虑过这个问题allocretainreleasedello是做什么的?autoreleasepool是如何实现的?什么是__unsafe_unretained?Block什么时候实现的?会不会造成循环引用,什么时候不会造成循环引用?所以在这篇博文中,我将从ARC到iOS的内存管理,以及Block相关的原理和源码进行详细讲解。2从ARC说起iOS内存管理,就不得不从ARC(AutomaticReferenceCounting/自动引用计数)说起,ARC是WWDC2011和iOS5引入的一个变化。ARC是LLVM3.0编译器的一项功能,可以自动管理内存。与Java中的GC不同,ARC是一种编译器特性而非基于运行时,因此ARC实际上是帮助开发者在编译阶段自动插入内存管理代码,而不是实时监控和回收内存。ARC的内存管理规则可以简单描述为:每个对象都有一个“引用计数”对象被持有,“引用计数”+1个对象被放弃,“引用计数”-1“引用计数”=0,释放对象3个你需要知道的是,包含NSObject类的Foundation框架并没有公开CoreFoundation框架的源码,通过NSObject进行内存管理的部分源码是公开的。GNUstep是Foundation框架的交换框架。GNUstep也是GNU项目之一。将CocoaObjective-C软件库重新实现为免费软件。从某种意义上说,GNUstep和Foundation框架的实现是相似的。通过GNUstep的源码分析Foundation的内存管理。4Allocretainreleasedealloc实现4.1GNU-alloc查看GNUStep中的alloc函数。GNUstep/modules/core/base/Source/NSObject.malloc:+(id)alloc{return[selfallocWithZone:NSDefaultMallocZone()];}+(id)allocWithZone:(NSZone*)z{returnNSAllocateObject(self,0,z);}GNUstep/modules/core/base/Source/NSObject.mNSAllocateObject:structobj_layout{NSUIntegerretained;};NSAllocateObject(ClassaClass,NSUIntegerextraBytes,NSZone*zone){intsize=计算保存对象所需的内存大小;idnew=NSZoneCalloc(zone,1,size);memset(new,0,size);new=(id)&((obj)new)[1];}NSAllocateObject函数调用NSZoneCalloc函数分配存储对象所需的空间,以及然后将内存空间设置为nil,并***返回用作对象的指针。我们简化上面的代码:GNUstep/modules/core/base/Source/NSObject.malloc简化版:structobj_layout{NSUIntegerretained;};+(id)alloc{intsize=sizeof(structobj_layout)+objectsize;structobj_layout*p=(structobj_layout*)calloc(1,size);return(id)(p+1)return[selfallocWithZone:NSDefaultMallocZone()];}alloc类方法使用structobj_layout中保留的整数来保存引用计数并存储写入到对象的内存头,将对象的所有内存块置0后返回。一个对象的表示如下:4.2GNU–retainGNUstep/modules/core/base/Source/NSObject.mretainCount:-(NSUInteger)retainCount{returnNSExtraRefCount(self)+1;}inlineNSUIntegerNSExtraRefCount(idanObject){return((obj_layout))anObject)[-1].retained;}GNUstep/modules/core/base/Source/NSObject.mretain:-(id)retain{NSIncrementExtraRefCount(self);returnself;}inlinevoidNSIncrementExtraRefCount(idanObject){if(((obj)anObject)[-1].retained==UINT_MAX-1)[NSExceptionraise:NSInternalInconsistencyExceptionformat:@"NSIncrementExtraRefCount()askedtoincrementtoofar”];((obj_layout)anObject)[-1].retained++;}在上面的代码中,NSIncrementExtraRefCount方法是先输入当retained变量超过最大值时发生异常的代码(因为retained是一个NSUInteger变量),然后执行retain++代码4.3GNU-release和retain对应的,release是什么方法是保留--。GNUstep/modules/core/base/Source/NSObject.m发布-(onewayvoid)release{if(NSDecrementExtraRefCountWasZero(self)){[selfdealloc];}}BOOLNSDecrementExtraRefCountWasZero(idanObject){if(((obj)anObject)[-1].retained==0){returnYES;}((obj)anObject)[-1].retained--;returnNO;}4.4GNU–deallocdealloc将释放对象。GNUstep/modules/core/base/Source/NSObject.mdealloc:-(void)dealloc{NSDeallocateObject(self);}inlinevoidNSDeallocateObject(idanObject){obj_layouto=&((obj_layout)anObject)[-1];free(o);}4.***pple实现设置Debug->DebugWorkflow->AlwaysShowDisassenbly以在Xcode中打开。这样断点之后,可以看到更详细的方法调用。通过在NSObject类的alloc和其他方法上设置断点跟踪,可以看到内部调用了几个方法:从前缀可以看出该方法包含在CoreFoundation中,可以在CFRuntime.c中找到,简化后列出源码:CFRuntime.c__CFDoExternRefOperation:int__CFDoExternRefOperation(uintptr_top,idobj){CFBasicHashReftable=Getobjecthash表(obj);intcount;switch(op){caseOPERATION_retainCount:count=CFBasicHashGetCountOfKey(table,obj);returncount;break;caseOPERATION_retain:count=CFBasicHashAddValue(table,obj);returnobj;caseOPERATION_release:count=CFBasicHashRemoveValue(table,obj==;returncount;}}所以__CFDoExternRefOperation是针对不同的操作调用具体的方法,如果op是OPERATION_retain,去掉使用retain具体实现的方法,从方法名如BasicHash可以看出,引用计数表其实就是一个哈希表,key是hash(对象的地址),value是引用计数,下图是Apple和GNU的对比:5autorelease和autoreleasepool在Apple的NSAutoreleasePool文档中,声明每个线程(包括主线程)维护一个管理NSAutoreleasePool的栈。创建新池时,它们将添加到堆栈的顶部。当Pool被销毁时,它们将从堆栈中移除。autorelease的对象会被添加到当前线程栈顶的Pool中。当Pool被销毁时,其中的对象也被释放。当线程结束时,所有池都被销毁并释放。破解NSAutoreleasePool类方法和autorelease方法,查看其运行过程,可以看到调用了如下函数:];//相当于objc_autorelease(obj)[NSAutoreleasePoolshowPools];//查看NSAutoreleasePool状态[pooldrain];//相当于objc_autoreleasePoolPop(pool)[NSAutoreleasePoolshowPools]可以看到当前线程中所有pool的状态:objc[21536]:##############objc[21536]:AUTORELEASEPOOLSforthread0x10011e3c0objc[21536]:2releasespending.objc[21536]:[0x101802000]...PAGE(热)(冷)objc[21536]:[0x101802038]################POOL0x101802038objc[21536]:[0x101802040]0x1003062e0NSObjectobjc[21536]:##############Programendedwitheexitcode:0AutoreleasePoolPage可以在objc4中查看:objc4/NSObject.mmAutoreleasePoolPageclassAutoreleasePoolPage{staticinlinevoid*push(){generateorholdNSAutoreleasePoolclassobject}staticinlinevoidpop(void*token){丢弃NSAutoreleasePool类对象releaseAll();}staticinlineidautorelease(idobj){相当于NSAutoreleasePool类的addObject类方法autoreleasePoolPage*page=获取正在使用的AutoreleasePoolPage实例;}id*add(idobj){将对象添加到内部数组}voidreleaseAll(){调用内部数组中对象的release方法}};void*objc_autoreleasePoolPush(void){if(UseGC)returnil;returnAutoreleasePoolPage::push();}voidobjc_autoreleasePoolPop(void*ctxt){if(UseGC)return;AutoreleasePoolPage::pop(ctxt);}AutoreleasePoolPage以双向链表的形式组合(分别对应结构体中的父指针和子指针)线程指针指向当前线程。每个AutoreleasePoolPage对象都会开辟4096字节的内存(也就是一页虚拟内存的大小)。除了上述实例变量占用的空间外,其余空间用于存放autorelease对象的地址。next指针指向将存储下一个添加的自动释放对象的位置。当一个Page的空间满了,会创建一个新的AutoreleasePoolPage对象来连接链表。6__unsafe_unretained除了__weak和__strong,我们有时还会使用__unsafe_unretained修饰符,那么我们对__unsafe_unretained了解多少呢?__unsafe_unretained是一个不安全的所有权修饰符,虽然ARC的内存管理是编译器的工作,但是带有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。赋值时既不会得到强引用也不会得到弱引用。运行一段代码:id__unsafe_unretainedobj1=nil;{id__strongobj0=[[NSObjectalloc]init];obj1=obj0;NSLog(@"A:%@",obj1);}NSLog(@"B:%@",obj1);运行结果:2017-01-1219:24:47.245220__unsafe_unretained[55726:4408416]A:2017-01-1219:24:47.246670__unsafe_unretained[55726:4408416]B:Programendedwithunthexitcode:0代码详细分析;{//生成并持有对象id__strongobj0=[[NSObjectalloc]init];//因为obj0变量是强引用,//所以我持有对象obj1=obj0;//虽然obj0变量赋值给了obj1//Butobj1变量既不持有对象的强引用也不持有对象的弱引用NSLog(@"A:%@",obj1);//输出obj1变量表示的对象}NSLog(@"B:%@",obj1);//输出obj1变量代表的对象//obj1变量代表的对象已经被丢弃//所以此时得到的是一个悬空指针//错误访问所以,***NSLog刚好能正常工作,如果错误访问会导致crash使用__unsafe_unretained修饰符时,用__strong修饰符赋值给变量时,确保对象确实存在
