Objective-C高级编程 ,一 自动引用计数

第一章:自动引用计数

1.2 内存管理/引用计数

1.2.2 内存管理思考方式:

1)自己生成的对象,自己所持有。alloc/new/copy/mutableCopy

2)非自己生成的对象,自己也能持有。retain

3)不再需要自己持有的对象时释放。release/autorelease

4)非自己持有的对象无法释放。

1.2.3 alloc/retain/release/dealloc实现

GNUstep:开源软件。和Cocoa是互换框架。GNUstep虽说不能与苹果的Cocoa实现完全相同,但是从使用者角度来讲,两者的行为和实现方式是一样的,或者说非常相似。理解了GNUstep源代码也就相当于理解了苹果的Cocoa实现。

Apple open soure网址:http://opensource.apple.com/

GNUstep网址:http://gnustep.org/

Zone区域概念:减少内存的碎片化。但是现在运行时系统简单地忽略了区域的概念。运行时系统中的内存管理本身已极具效率,使用区域来管理内存反而会引起内存使用效率低下以及源代码复杂化等问题。

alloc : 在对象头部使用一个整数来保存引用计数。

retain : 引用计数加一。

Release : 引用计数减一。

ps: void *calloc (size_t n, size_t size);在内存的动态存储区中分配n个长度为size的连续空间,返回一个只想分配其实地址的指针。

1.2.4 苹果的实现

苹果源代码并未公开。可以利用xcode的调试器lldb和iOS大概追溯其实现过程。在alloc类方法上设置断点,追踪程序的执行。

通过散列表管理引用计数。

比较两种实现方法:

通过内存块管理:1)少量代码即可完成 2)能够统一管理引用计数内存块与对象用内存块。

通过引用计数表管理:1)对象的内存块分配无需考虑内存块头部 2)引用计数表各记录中存有内存块地址,可从各个记录追溯到各对象的内存块。

1.2.5 autorelease

IMP Caching概念:能够高效的运行OS X、iOS用应用程序中频繁调用的autorelease方法。对方法函数指针进行缓存,实际调用中直接使用缓存值。

id autorelease_class = [NSAutoreleasePool class];

SEL autorelease_sel = @selector(addObject:);

IMP autorelease_imp = [autorelease_class methodForselector:autorelease_sel];

-(id)autorelease{

(*autorelease_imp)(autorelease_class,autorelease_sel,self);

}

比如下方法快一倍

-(id)autorelease{

[NSAutoreleasePool add object:self];

}

实现使用链接列表,同在array中追加对象参数是一样的。

1.2.7苹果的实现

注意在iOS中可以通过[NSAutoreleasePool showPools]方法来确认已被autorelease的对象的状况。

(也可在运行时系统中使用非公开函数_objc_autoreleasePoolPrint()。

使用方法:

//声明

extern void _objc_autoreleasePoolPrint();

//输出

_objc_autoreleasePoolPrint();//利用该函数可以有效的帮我们调试注册到autoreleasepool上的对象

)

NSAutoreleasePool是由NSAutoreleasePoolPage为节点实现的双向链表。

1.3ARC规则

设定ARC有效可指定编译器属性为:-fobjc-arc

内存管理的思考方式在ARC有效时也是可行的。只是在源代码的记述方法上稍有不同。

1.3.3:变化1:所有权修饰符

id类型和对象类型前必须加所有权修饰符。默认为__strong。

__weak,__strong,__autoreleasing可将附有该属性的自动变量初始化为nil。而__unsafe_unretained则不可。

1)__strong:默认所有权修饰符,持有强引用的变量在超出其作用域时被废弃,随着强引用失效,引用对象会随之释放。

id obj 在ARC有效时等同于 id __strong obj 在ARC无效时等同于 id obj;[obj release];

{ id __strong obj1 = [[NSObjct alloc]init];(自己生成的对象)}//因为变量obj1超出作用于,强引用失效,自动的释放自己所持有的对象,对象的所有者不存在,因此废弃对象。

{ id __strong obj2 = [NSMutableArray array];(非自己生成的对象)}//因为变量obj2超出作用于,强引用失效,所以自动的释放自己所持有的对象。

2)__weak:解决引用循环,引用失效后,赋nil

__weak修饰符只能用于iOS5及OS X lion以上版本的应用程序。

3)__unsafe_unretained修饰符 (对所修饰的对象无任何影响)

ARC的内存管理是编译器的工作,但是该修饰符修饰的变量不属于编译器的内存管理对象。

在之后使用的时候程序可能会崩溃。

4)__autoreleasing修饰符(ARC有效时不能使用autorelease方法,也不能使用NSAutoreleasePool类)

NSAutoreleasePool *pool =[[NSAutoreleasePool alloc]init];

id obj = [[NSObject alloc]init];

[obj autorelease];

[pool drain];

等同于

@autoreleasepool {

id __autoreleasing obj1 = [[NSObject alloc]init];

}

@autoreleasepool{

  id __strong obj = [NSMutableArray array];

  //此时obj为强引用,自己持有对象,且该对象根据编译器判断其方法名后,自动注册到autoreleasepool

}

//obj超出作用于,强引用失效,自动释放所持有的对象。同时随着@autoreleasepool块的结束,注册到autoreleasepool中的所有对象被自动释放。因为对象的所有者不存在,所以废弃对象。

当然不使用__autoreleasing修饰符也能使对象注册到autoreleasepool(即非显示调用)

(1)若某一对象作为函数的返回值,那么编译器会将其自动的注册到autoreleasepool

(2)在访问有__weak修饰的变量时

 id __weak obj1 = obj0; NSLog(@"class = %@",[obj1 class]);

等同于id __weak obj1 = obj0; id autoreleasing tmp = obj1;NSLog(@"class = %@",[tmp class]);

原因:__weak只持有对象的弱引用,而在访问引用对象的过程中,该对象有可能被废弃。所以会先把该对象注册到autoreleasepool当中。

(3)id *obj等同于id __autoreleasing *obj 同样的 NSObject **obj等同于NSObject* __autoreleasing *obj

这里明确一个概念:对于id obj;来说obj就是我们所说的对象。对于id *obj;obj就是对象指针。

我们常常这样使用:

NSError *error = nil;

BOOL result = [obj performOperationWithError:&error];

该方法的声明为 - (BOOL) performOperationWithError:(NSError * __autoreleasing *)error;

对这个方法声明中的参数赋值,应当用*error = [NSError alloc]...;(叫做地址的解绑定,且此时*error不能为nil)

当赋值给对象指针时,所有权修饰符必须一致。

譬如:(编译器会报错,原因是赋值给对象指针时,所有权修饰符必须一致)

NSError *error = nil;

NSError **perror = &error;

ps:在c++中,__strong类似于智能指针std::shared_ptr;__weak类似于std::weak_ptr。

1.3.4 ARC有效情况下的规则

(1)不能使用retain/retainCount/autorelease/release

(2)不能使用NSAllocateObject/NSDeallocateObject

(3)遵守内存管理方法的命名原则 譬如:init开头的方法必须是实例方法,且必须返回对象。(initialize除外)

(4)不要显示调用dealloc方法

(5)使用@autoreleasePool块代替NSAutoreleasePool

(6)不能使用区域。但是不论是否arc,区域都已被忽略

(7)对象性变量不能作为c语言结构体的成员

若要把对象型变量加入到结构题成员中时,可强制转换为void *或是附加__unsafe_unretained修饰符(有可能造成内存泄漏,因为__unsafe_unretained不属于编译器的内存管理对象)。

(8)显式转换id和void * (桥接)

__bridge,__bridge_retained,__bridge_transfer

如果在ARC生效状态下,以下写法会报错。

id p = [[NSObject alloc]init];

void *o = p; (将OC变量赋值给C语言变量,即没有附加所有权修饰符的void *等指针型变量时,伴随着一定的风险)

这时便需要用到桥接:

__bridge_retained可使要转换赋值的变量也持有所赋值的对象。与retain类似。等同于

CFTypeRef CFBridgingRetain (id X){

  return (__bridge_retained CFTypeRef) X;

}

__bridge_transfer提供与此相反的动作,被转换的变量所持有的对象在该变量被赋值给转换目标变量后随之释放。与release类似。等同于

id CFBridgingRelease (CFTypeRef X){

  return (__bridge_transfer id) X;

}

ps:oc对象痛Core Foundation对象没有区别,所以在ARC无效时,只用简单的c语言的转换也能实现互换。另外这种转换不需要使用额外的CPU资源,因此也被称为免费桥Toll-Free-Bridge。

注意:id obj = (__bridge id)cfObject;//obj对其是强引用的,若有引用计数的话,引用计数自然加一。

1.3.6数组(有些难理解。。。)

静态数组:在定义时就已经在栈上分配好了空间,在运行时这个大小不会改变。int a[10]; id objs[10];

动态数组:大小在运行时给定,即运行时在堆上分配空间,且大小可变。int *a; id __strong*array = nil;(NSObject * __strong* array = nil;这里确保了__strong修饰符的id指针型变量被初始化为nil。)

1.4ARC的实现

ARC是编译器进行内存管理的说法并不确切。实际上ARC在此基础上还需要Objective-C运行时库的协助。就是说ARC由以下工具、库来实现。

1clang(LLVM编译器)3.0以上

2objc4 Objective-C运行时库493.9以上

(1)__strong修饰符的实现

{ id __strong obj = [[NSObject alloc]init]; }的编译器模拟代码如下

  id obj = objc_msgSend(NSObject,@selector(alloc));

  objc_msgSend(obj,@selecotr(init));

  objc_release(obj);

{id __strong obj = [NSMutableArray array];}的编译器模拟代码如下

  id obj = objc_msgSend(NSMutableArray,@selector(array));

  objc_retainAutoreleasedRetainValue(obj);

  objc_release();

解释:

对于[NSMutableArray array];其中,return的是objc_autoreleaseReturnValue(obj);这个方法编译器会检视当前方法返回后即将要执行的那段代码,如果返回对象有retain操作,便不会执行autorelease,而是设置全局数据结构的一个标志位。

而objc_retainAutoreleasedRetainValue 会检测上面提到的标志位,若已置位,则不执行retain操作。

以上,通过objc_autoreleaseReturnValue和objc_retainAutoreleasedRetainValue函数的协作,可以不将对象注册到autoreleasepool中而直接传递,这一过程达到了最优化。

(2)_weak修饰符实现

特点:若有__weak修饰符的变量所引用的对象被废弃,则将 nil赋值给该变量;使用(注意这里的情况是使用)附有__weak修饰符的变量,即是使用注册到autoreleasepool中的变量???。

{id __weak obj1 = obj0;} 的编译器模拟代码如下

  id obj1;

  objc_initWeak(&obj1,obj);//等同于obj1 = 0; objc_storeWeak(&obj1,obj);

  objc_destoryWeak(&obj1);//等同于objc_storeWeak(&obj1,0);

其中objc_storeWeak将第二个参数的赋值对象的地址作为键值,将第一个参数注册到weak表中(__weak修饰的变量的地址)。

注意:weak表同引用计数相同,作为散列表被实现。(第一个参数是值,第二个参数是键)(一个键可以对应多个值)

如果大量使用附有__weak修饰符的变量,会消耗响应的CPU资源,良策是只在需要避免引用循环时引用__weak修饰符。

注意:如下两种使用是不同的

  id __weak obj = [[NSObject alloc]init];

NSLog(@"%@",obj);

编译器模拟代码如下

  id obj;

  id tmp = objc_msgSend(NSObject,@selector(alloc));

  objc_msgSend(NSObject,@selector(init));

  objc_initWeak(&obj,tmp);

  objc_release(tmp);

  objc_destoryWeak(&obj);

2017-03-19 20:49:54.853 BAFParking[47279:4030005] (null)

id obj = [[NSObject alloc]init];

id __weak obj1 = obj;

NSLog(@"%@",obj1);

编译器模拟代码如下:

  id obj1;

  objc_initWeak(&obj1,obj);

  id tmp = objc_loadWeakRetained(&obj1);//对obj1 retain

  objc_autorelease(tmp);//对obj1 autorelease

  NSLog(@"%@",tmp);

  objc_destoryWeak(&obj1);

2017-03-19 20:49:54.854 BAFParking[47279:4030005] <NSObject: 0x60800001e680>

//这里的retaincount变化很奇怪。

extern void _objc_autoreleasePoolPrint();

extern uintptr_t _objc_rootRetainCount(id obj);

@autoreleasepool {

id obj = [[NSObject alloc]init];

//_objc_autoreleasePoolPrint();

id obj1 = obj;

id __autoreleasing obj2 = obj;

id __weak o = obj;//这里显然没有把__weak的变量推入自动释放池中

NSLog(@"before using weak:retain count = %ld",_objc_rootRetainCount(obj));

NSLog(@"class = %@",[o class]);

NSLog(@"after using weak:retain count = %ld",_objc_rootRetainCount(obj));

//_objc_autoreleasePoolPrint();

}

这里打印出来的值都是3,__weak并没有将o放到autoreleasePool中,why???待解决。可以用clang来查看编译器代码时怎样的。

不支持__weak修饰符的类:

1.类声明中都会附加__attribute__ ((objc_arc_weak_reference_unavailable))

2.allowsWeakReference ,在赋值给__weak修饰符的变量时,如果赋值对象的allowsWeakReference返回NO,程序将异常终止。

retainWeakReference,当赋值对象的retainWeakReference方法返回NO时,该变量将使用nil。

(3)__autoreleasing修饰符实现(__autoreleasing修饰的变量引用计数也会加一,等到清理自动释放池时再递减保留计数)

@autoreleasepool{ id __autoreleasing obj = [[NSObject alloc]init]; }的编译器模拟代码如下

  id pool = objc_autoreleasePoolPush();

  id obj = objc_msgSend(NSObject,@selector(alloc));

  objc_msgSend(obj,@selecotr(init));

  objc_autorelease(obj);//于strong的objc_release不同

  objc_autoreleasePoolPop(pool);

//arc有效无效时,autorelease功能都完全相同

@autoreleasepool{id __autoreleasing obj = [NSMutableArray array];}的编译器模拟代码如下

  id pool = objc_autoreleasePoolPush();

  id obj = objc_msgSend(NSMutableArray,@selector(array));

  objc_retainAutoreleasedRetainValue(obj);

  objc_autorelease(obj);

  objc_autoreleasePoolPop(pool);