《Objective-C高级编程》 学习笔记 属性关键字

自动引用计数
1.首先是生成对象,持有对象,释放对象,摧毁对象的概念:

  1. 生成并持有对象: alloc/copy/new/mutableCopy等方法
  2. 持有对象: retain方法
  3. 释放对象: release方法
  4. 废弃对象: dealloc方法

2.内存管理的四个思考方式:
持有的含义:持有已经是很表意的说法,
A持有B的意思就是A引用着B,
在A中含有记录B对象的地址指针的变量
(1).自己生成的对象,自己持有
使用以下名称开头的方法名意味着自己生成的对象只能自己持有:
allloc
new
copy
mutableCopy

(2).非自己生成的对象,自己也能持有
用alloc/copy/new/mutableCopy以外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者。

//取得非自己生成并持有的对象
id obj = [NSMutableArray array];
//取得的对象存在,但自己不持有对象

NSMuatableArray类对象被赋值给变量obj,但obj并不持有该对象,使用retain方法可以持有对象

[obj retain];
//自己持有对象

(3).不再需要自己持有的对象时释放
release方法
使用后,指向对象的指针仍然被保留在变量obj中,貌似能够访问
但对象一经释放绝对不可访问
注意如果自己生成非自己所持有的对象,如果用retain方法变为自己持有,也同样可以用release方法释放

用某个方法生成对象,并将其返还给该方法的调用方,那么它的源代码是怎样的呢?

//方法
- (id)allocObject {
//自己生成并持有对象
id obj = [[NSObject alloc] init];
//自己持有对象
return obj;
}

如上,原封不动的返回用alloc方法生成并持有的对象obj给allocObject方法,就能让调用发allocObject也持有该对象

然后就可以这样调用该方法啦

//执行这句代码时 obj1取得非自己生成并持有的对象 因为这是调用obj0执行方法
id obj1 = [obj0 allocObject];
//执行这段代码后 obj1自己持有对象

调用[NSMuatableArray array]方法使取得的对象存在,但自己不持有对象,是如何实现的?
先说自己持有对象的情况下,使取得的对象存在,但自己不持有对象:

- (id)object {
id obj = [[NSObject alloc] init]
//自己持有对象

[obj autorelease];
//取得的对象存在,但自己不持有对象

return obj;

上面用了autorelease方法使取得的对象存在,但自己不持有对象。autorelease是可以提供这样的功能的,使对象在超出指定的生存范围后能够自动并正确的释放(调用release方法)

(4).无法释放非自己持有的对象
对于用alloc/copy/new/mutableCopy方法生成并持有的对象,或者是用retain方法持有的对象,由于持有者是自己,所以在不需要该对象时需要将其释放。
但在自己生成并持有对象后,在释放完不再需要的对象后再次释放就会造成崩溃。

还有一种造成崩溃的可能:取得的对象存在,但自己不持有对象时释放

id obj1 = [obj0 object];
[obj1 release];

3.使用多重区域放置内存碎片化 P14

4.在进行C/C++编程的时候,需要程序员对内存的了解比较好清楚,经常需要操作的内存可分为下面几个类别:

堆栈区(stack):由编译器自动分配与释放,存放函数的参数值,局部变量,临时变量等等,它们获取的方式都是由编译器自动执行的
堆区(heap):一般由程序员分配与释放,基程序员不释放,程序结束时可能由操作系统回收(C/C++没有此等回收机制,Java/C#有),注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
全局区(静态区)(static):全局变量和静态变量的存储是放在一块儿的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。
文字常量区:常量字符串是放在这里的,程序结束后由系统释放。
程序代码区:存放函数体的二进制代码。

5.malloc与calloc函数不同点:calloc函数与malloc函数的一个显著不同时是,calloc函数得到的内存空间是经过初始化的,其内容全为0。calloc函数适合为数组申请空间,可以将size设置为数组元素的空间长度,将n设置为数组的容量。

6.引用计数用散列表来管理,就是表键值为内存块地址的散列值,也就是内存块地址是随机分配给表键值key的,不是按固定的顺序来分配。

7.autorelease会像C语言的自动变量那样来对待实例。当超出其作用域,对象实例的release方法被调用,编程人员同时可以设定变量的作用域
autorelease的具体使用方法:
1.生成并持有NSAutorelease对象 也就是说用alloc init 方法来生成对象
2.调用已分配对象的autorelease实例方法
3.废弃NSAutorelease对象

8.在大量产生autorelease的对象时,只要不废弃NSAutoreleasePool对象,那么生成的对象就不能被释放,因此有时会产生内存不足的现象
Cocoa框架中也有很多类方法用于返回autorelease的对象。比如NSMutableArray类的arrayWithCapacity类方法

id array = [NSMutableArray arrayWithCapacity:1];

此源代码等同于以下源码。

id array = [[[NSMutableArray  alloc]initWithCapacity:1]autorelease];

9.类方法与实例方法的区别:
1,类方法可以调用类方法。
2,类方法不可以调用实例方法,但是类方法可以通过创建对象来访问实例方法。
3,类方法不可以使用实例变量。类方法可以使用self,因为self不是实例变量。
4,类方法作为消息,可以被发送到类或者对象里面去(实际上,就是可以通过类或者对象调用类方法的意思

注意点二:类方法和实例方法
1:实例方法是— 类开头是+ 实例方法是用实例对象访问,类方法的对象是类而不是实例,通常创建对象或者工具类。
在实例方法里,根据继承原理发送消息给self和super其实都是发送给self
在类方法里面self是其他的类的类方法,在类方法中给self发送消息只能发类方法self是类super也是
什么时候用类方法,要创建一个实例时候获取一个共享实例,或者获取关于类的一些共有信息

2:类方法(class method)和实例方法(instance method)。类方法被限定在类范围内,不能被类的实例调用(即脱离实例运行)。alloc就是一种类方法。实例方法 限定在对象实例的范围内(即实例化之前不能运行)。init就是一种实例方法,被alloc方法返回的对象实例调用。

instance method 以减号 "-" 开头

class method 以加号 “+” 开头,相当于static方法

10.如果调用NSObject类的autorelease实例方法,该对象将被追加到正在使用的NSAutoreleasePool对象中的数组里

11.为什么autorelease NSAutoreleasePool对象会发生异常?
在OC中,无论调用哪一个对象的autorelease实例方法,实现上调用的都是NSObject类的autorelease实例方法。但是对NSAutoreleasePool类,autorelease实例方法已经被该类重载,因此运行时会出错

ARC规则

12.对象的所有权/所有者含义:


《Objective-C高级编程》 学习笔记 属性关键字_第1张图片
图片.png

13.生成并持有——对象的强引用 比较有特点: P33

//obj0持有对象A的强引用
id obj0 = [[NSObject alloc] init];

//obj1持有对象B的强引用
id obj1 = [[NSObject alloc] init];

id obj2 = nil;

obj0 = obj1;
//obj0持有由obj1赋值的 对象B 的强引用
//因为obj0被赋值,所以原先持有的对对象A的强引用失效
//对象A的所有者不存在,因此废弃对象A
//此时 持有(拿着)对象B的强引用 的  变量 为 obj0 和 obj1

obj2 = obj0;
//obj2持有由obj0赋值的对象B的强引用
//此时 持有对象B的强引用的 变量 为 obj0 obj1 obj2

总结就是 storng修饰符(默认什么都没有为strong)修饰的变量,不仅只在变量作用域中,在赋值上也能够正确地管理其对象的所有者

14.strong weak autoreleasing修饰符可以保证的是:
将附有这些修饰符的自动变量初始化为nil

15.下面这段代码怎么解释?

//自己生成并持有对象
id __strong obj0 = [[NSObject alloc] init];

//obj1变量持有生成对象的弱引用
id __weak obj1 = obj0;

16.weak修饰符另一个优点:在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值状态(空弱引用)。
所以这个功能的作用就是:通过检查附有__weak修饰符的变量是否为nil可以判断被赋值的对象是否已废弃。

__autorelease修饰符:

17.ARC有效时,要通过将对象赋值给附加了_autoreleasing修饰符的变量等价于在ARC无效时,对象被注册到autorelease
也可以理解为ARC有效时,用__autorelease修饰符的变量替代autorelease方法

18.又差点忘的一个知识点:
strong修饰符会自动进行release操作!所以alloc/new/copy/mutableCopy方法来取得对象 这些方法都是强引用,相当于strong,不会自动将返回值的对象注册到autoreleasepool.
所以这里就引申出编译器的另一个功能:编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool 是的话就是上面说的

19.下面有一个很有意思的调用:

@autoreleasepool {
//取得非自己生成并持有的对象
id __strong obj = [NSMutableArray array];
//因为变量obj为强引用,所以自己持有对象
//由编译器判断其方法名后,自动注册到autoreleasepool中
}
离开大括号后:
obj超出其作用域,强引用失效 所以自动释放自己持有的对象

同时,随着@autoreleasepool块的结束 注册到@autoreleasepool块中的所有对象都被自动释放
但是!!! 因为对象的所有者不存在 所以废弃对象 

20.一个疑问:

+ (id)array
{
    id obj = [[NSMutableArray alloc] init];
    return obj;
}

因为该对象作为函数的返回值,编译器会自动将其注册到autoreleasepool。
那么注册进去之后呢?是用的时候取出来还是?
是会一直在pool中吗? 什么时候被销毁啊
emmm答案就是 假如在@autoreleasepool 里面这样使用该方法:
id __strong obj = [NSMutableArray array];

那么在执行完array方法后 现在obj被注入自动释放池了,然后程序返回
id __strong obj = [NSMutableArray array];

这一行 现在现在obj已被注入自动释放池 那么什么时候释放呢? 那当然是随着autoreleasepool被销毁后一起释放啊!

21.在访问附有__weak修饰符的变量时必须访问注册到autoreleasepool中的对象,比如:

id __weak obj1 = obj0;
id __autoreleaseing tmp = obj1;
//就是想要访问 obj1 就得用有__autoreleaseing属性的对象来访问

原因是:因为__weak修饰符只持有对象的弱引用,而在访问引用对象的过程中,该对象有可能被废弃。如果把要访问的对象注册到autoreleasepool中(id __autoreleaseing tmp = obj1;)

那么在@autoreleasepool块结束之前都能确保该对象存在

22.所以对__autoreleaseing属性总结下来就是:
使用附有__autoreleaseing修饰符的变量作为对象取得参数,与除alloc/new/copy/mutableCopy之外其他方法的返回值取得对象完全一样,都会注册到autoreleasepool,并取得非自己生成并持有的对象

23.对象指针必须附加__strong修饰符,保证其所有权修饰符必须一致:

NSError * error = nil;
NSError * __strong * pError = &error;
//这里用strong修饰是因为 什么修饰符都不添加 默认error也用strong来修饰

24.调用非公开函数_objc_autoreleasePoolPrint()函数可以打印出自动释放池中的内容,有效地帮我们调试 注册到autoreleasepool上的对象

25.在编写程序的过程中,在ARC有效的情况下,它会遵守一定的规则

1、不能使用retain/release/retainCount/autorelease。

2、不能使用NSAllocateObject和NSDeallocateObject
的。

3、须遵守内存管理的方法命名规则

4、不要显式调用dealloc

5、使用@autoreleasepool块替代NSAutoreleasePool

6、不能使用区域NSZone

7、对象型变量不能作为C语言结构体(struct/union)的成员

8、显式转换“id”和“void”

第一项:"不能使用retain/release/retainCount/autorelease。" 很明显,当程序使用了ARC进行编程的话,是禁止键入retain/release代码的,因为ARC有效时,调用了retain/release会造成编译器错误。
使用retain/release/retainCount/autorelease方法只有在ARC无效且手动进行内存管理的情况下使用

第二项:"不能使用NSAllocateObject和NSDeallocateObject的" 其实

id objc = [[NSObject alloc]init];

苹果内部是直接调用NSAllocateObject来生成并持有对象的,他同retain一样,只有在没有使用ARC的情况下才可以使用。如果在ARC有效时使用了的话,会造成编译器错误,提示:not available in automatic reference mode.

第三项:"须遵守内存管理的方法命名规则" 在ARC无效的时候,用于对象生成/持有的方法必须遵守以下的命名规则
alloc
new
copy
mutableCopy

以上述名称开始的方法,应当返回调用方所应当持有的对象,这在ARC有效时也同样有效,只不过在ARC有效时,会更加的严格。并且,在ARC有效时,还需要再添加一条命名规则:

init

以init开始的方法的规则,要比alloc\new/copy/mutableCopy更加的严格,该方法必须是实例方法,而且必须是返回对象。返回的类型必须是id类型或者该方法声明类的对象类型,亦或者是该类的超类型或者子类型。该返回对象并不注册到autoreleasepool上,基本上只是对alloc方法返回值的对象进行初始化处理并返回对象。

例如:

id objc = [[NSObject alloc]init];
方法的命名规则可以是:

-(id)initWithObject:(NSObject *)obj;

像下面这种命名规则是错误的,因为他没有返回对象:

-(void)initWithObject:(NSObject *)obj

第四项:"不要显式调用dealloc" 无论ARC是否有效,只要是对象的所有者不在持有该对象,该对象就会被废弃,对象被废弃时,无论ARC是否有效,都会调用dealloc方法

-(void)dealloc

{

/*

 *此处是对象被废弃的时候,要实现的代码

 *

 */

}
就如使用C语言库的时候,需要使用如下方法释放内存

-(void)dealloc

{

free(buffer_);

}
其实dealloc方法,在大部分情况下还适用于删除已注册的代理或者观察者

-(void)dealloc

{

[[NSNotificationCenter defaultCenter]removeObserver:self];

}

在ARC无效的时候,还应该注意下面一点,

/*

*在ARC无效的时候,必须调用[super dealloc]

*/

-(void)dealloc

{

[super dealloc];  

}
需要注意的是:ARC有效时 无法显式调用dealloc这一规则,如果使用就会同release方法一样 引起编译错误

第五项:"使用@autoreleasepool块替代NSAutoreleasePool" 在ARC有效的时候,调用NSAutoreleasePool会造成编译器出错

第六项:"不能使用区域NSZone" "NSZone"无论ARC是否有效,区域在现在的运行时系统(编译器宏OBJC2)中已经单纯的被忽略;

第七项:"对象型变量不能作为C语言结构体(struct/union)的成员" 这句话其实就是,ARC有效时,在结构体struct内部不能出现对象,如下编译器就会报错

struct data{

    NSArray * arr;

};

报错如下:ARC forbids Objective-C objects in struct

26._unsafe_unretained修饰符以外的 __strong/ __weak/ __autoreleasing修饰符保证其指定的变量初始化为nil.

同样的,附有__strong/ __weak/ __autoreleasing修饰变量 的数组也能保证其初始化为nil

27.数组:
像下面这样将nil代入到malloc函数所分配的数组各元素中来初始化是非常危险的:

array = (id __strong *)malloc(sizeof(id) * entries);
for (NSUInteger i = 0; i < entries; ++i) 
array[i] = nil;

原因是:比较难懂的一句话:

这是因为由malloc函数分配的内存区域没有被初始化为0,因此nil会被赋值给附有__strong修饰符的并被赋值了随机地址的变量中,从而释放一个不存在的对象。

28.数组:
在静态数组中,编译器能够根据变量的作用域自动插入释放赋值对象的代码,而在动态数组中,编译器不能确定数组的生存周期,所以无从处理。
如下所示: 一定要将nil赋值给所有元素中,使得元素所赋值对象的强引用失效,从而释放那些对象。在此之后,使用free函数废弃内存块

for (NSUInteger i = 0; i < entries; ++i) 
array[i] = nil;

free(array);

ARC的实现:
29.__strong修饰符

赋值给附有__strong修饰符的变量在实际的程序中到底如何运行?

{
id __strong obj = [[NSObject alloc] init];
}

该源代码实际上可转换为调用以下函数:

//编译器的模拟代码
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);

注意两次调用objc_msgSend方法(alloc 和 init)

30.weak修饰符:

  • 若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量
  • 使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象(我们通过下面这段代码来确定这个功能)
id __weak obj1 = obj; 
NSLog(@"%@"m obj1);

将被分解成以下代码 (模拟代码)

/* 编译器模拟代码 */
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&obj1);

__weak同引用计数一样通过散列表(哈希表)实现,大致流程如下:

1.objc_initWeak(&obj1, obj)函数初始化__weak修饰的变量,通过执行objc_storeWeak(&obj1, obj)函数,以第一个参数(变量的地址)作为key,把第二个参数(赋值对象)作为value存入哈希表。
2.由于弱引用不能持有对象,函数objc_loadWeakRetained(&obj1)取出所引用的对象并retain。
3.objc_autorelease(tmp)函数将对象注册到autoreleasepool中。
4.objc_destroyWeak(&obj1)函数释放__weak修饰的变量,通过过程执行objc_store(&obj1, 0)函数,在weak表中查到变量地址并删除。废弃对象调用objc_clear_deallocating函数,这个过程会将weak表记录中__weak修饰的变量地址赋值为nil。

同时,前面的源代码与下列源代码相同

id obj1 = 0; 
objc_storeWeak(&obj1, obj); 
objc_storeWeak(&obj1, 0);

objc_storeWeak函数把第二个参数赋值对象的地址作为键值,将第一参数的附有__weak修饰符的变量的地址注册到weak表中(哈希表),如果第二参数为, 则把变量的地址从weak表中删除。由于一个对象可同时赋值给多个附有__weak修饰符的变量中,所以对于一个键,可注册多个变量的地址。

对象的释放过程:

objc_release
引用计数为0, 执行dealloc
_objc_rootDealloc
object_dispose
objc_destructInstance
objc_clear_deallocating

objc_clear_deallocating执行动作:

  1. 从weak表中获取废弃对象的地直为键的记录
    2.将包含在记录中的所有附有__weak修饰符变是的地址,赋为nil
  2. 从weak表中删除该记录
  3. 从引用计数表中删除废弃对象的地址为键的记录

如果有大量使用附有__weak修饰符的变量,则会消耗相应的CPU资源,良策是只在需要避免循环引用的时候使用__weak,可先将__weak修饰的变量赋值给__strong修饰的变量

独自实现引用计数的类无法使用__weak,如NSMachPort,另外当allowsWeakReference/retainWeakReference方法返回No的时候也无法使用

31.很有意思的实现:

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

该源代码将自己生成并持有的对象赋值给附有__weak修饰符的变量中,所以自己不能持有该对象,这时会被释放并被废弃,引起编译警告。
那么编译器是怎么处理这行代码的呢?

//编译器的模拟代码
id obj;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp,@selector(init));
objc_initWeak(&obj, tmp);
objc_release(tmp);//tmp自己不能持有对象 需要被释放并废弃
objc_destoryWeak(&object);

虽然自己生成并持有的对象通过objc_initWeak函数被赋值给附有__weak修饰符的变量中,但weak不能持有,编译器判断其没有持有者,故该对象立即通过objc_release函数被释放和废弃
这样一来,nil就会被赋值给引用废弃对象的附有__weak修饰符的变量中

32.一个总结:
因为附有 __weak修饰符变量所引用的对象被注册到autoreleasepool中,所以在@autoreleasepool块结束前都可以放心使用。但是,如果大量的使用附有__weak修饰符的变量,注册到autoreleasepool的对象数量也会大大的增加,因此在使用附有__weak修饰符的变量时,最好先暂时赋值给附有__strong修饰符的变量后再使用。

如下所示:

{
id __weak o = obj;
id tmp = 0;
NSLog(@"1 %@", tmp);
NSLog(@"2 %@", tmp);
NSLog(@"3 %@", tmp);
NSLog(@"4 %@", tmp);
NSLog(@"5 %@", tmp);
}

在 tmp = 0 时 对象仅登录到autoreleasepool一次
但如果不加 tmp这个变量而是直接打印 o 那么o所赋值的对象就会被注册到autoreleasepool五次

33.__autoreleasing的实现
_autoreleasing修饰变量,等同于ARC无效时对象调用autorelease方法。

@autoreleasepool {
    id __autoreleasing obj = [[NSObject alloc] init];
}

/* 编译器的模拟代码 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelese(obj);
objc_autoreleasePoolPop();

34.不使用_autoreleasing修饰符,仅使用附有__weak声明的变量也能将引用对象注册到autoreleasepool中

你可能感兴趣的:(《Objective-C高级编程》 学习笔记 属性关键字)