iOS与OS X多线程和内存管理(一)内存管理

前言

读完《iOS与OS X多线程和内存管理》有一段时间了,当时是和《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法 》一起学习的,个人认为这两本书中有很多相辅相成的东西 ,很适合一起学习,现在有空闲时间,就想把之前粗略的笔记进行一下简单的整理,主要是书中的内容整理和总结,如有理解错误的地方,希望大家指出,我们一起学习和探讨~

心得

内存管理的内容建议先阅读《iOS与OS X多线程和内存管理》中的内容,个人认为讲解的很详细和深入,阅读完以后建议配合《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法 》中的内存管理再次深入,这只是个人学习上的一点建议,希望对大家有帮助

内容

  • 自己生成的对象,自己所持有
  • 非自己生成的对象,自己也能持有
  • 不再需要自己持有的对象时释放
  • 非自己持有的对象无法释放
D343490EFA176479A39517E2F1F74686.jpg

cocoa框架中Foundation框架类库中NSObject类来负责内存管理

一、自己生成的对象,自己所持有

使用以下名称开头的方法(并且方法名遵守驼峰式拼写法),‘自己生成的对象只有自己持有’;调用该方法的调用者也意味着‘自己生成并持有对象’

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

new 
id obj = [NSObject new];

copy

mutableCopy

  • 方法名命名实例:
//下列名称也意味着自己生成并持有对象
allocMyObject
newThatObject
copyThis
mutablCopyYourObject
//不属于同一类别的方法
allocated
newer
copying
mutableCopyed

二、非自己生成的对象,自己也能持有

使用除alloc、new、copy、mutableCopy以外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者,但是可以通过retain方法取得对象的持有权

id obj = [NSMutableArray array];//取得的对象存在,但自己不持有对象
[obj retain];//取得对象的持有权

三、不再需要自己持有的对象时释放

注:自己持有的对象,一旦不再需要,持有者有义务释放该对象,释放使用release方法

取得对象的持有权方式(包含释放):

/*
 * 通过alloc/new/copy/mutableCopy等方法‘自己生成并持有对象’
 */

 id obj = [[NSObject alloc] init];//自己持有对象
[obj release];  //释放对象 

/*
 * 通过alloc/new/copy/mutableCopy等开头的自定义方法‘自己生成并持有对象’
 */

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

//取得非自己生成并持有对象
 id obj1 = [obj0 allocObject];//因为allocObject符合命名规则,所以意味着‘自己生成并持有对象’
[obj release]; //释放对象 
/*
 * 通过alloc/new/copy/mutableCopy等方法以外的方法‘取得非自己生成并持有对象’
 */
 id obj = [NSMutableArray array];//取得的对象存在,但自己不持有对象
[obj retain];//通过retain方法,自己持有对象  
[obj release]; //释放对象 
/*
 * 通过alloc/new/copy/mutableCopy等开头以外的自定义方法‘取得非自己生成并持有对象’
 */
-(id)object{
     id obj = [[NSObject alloc] init];//自己持有对象
    [obj autorelease];//取得的对象存在,但自己不持有对象,使用NSMutableArray类的array类方法等可以取得谁都不持有的对象,同时通过‘autorelease实现的’
    return obj;
}

 id obj1 = [obj0 object];//因为object不符合命名规则,所以意味着‘取得的对象存在,但自己不持有对象’
[obj1 retain];//也能通过retain方法将调用autorelease方法取得的对象变为自己持有;  
[obj release];  //释放对象 

四、非自己持有的对象无法释放

可以并需要释放对象的情况

  • 用alloc/new/copy/mutableCopy方法生成并持有的对象
  • 用retain方法持有的对象

不可以释放对象的情况

  • 自己生成并持有对象后,在释放完不再需要的对象后再次释放
   id obj = [[NSObject alloc] init];
  [obj release];
  [obj release];
  • 取得的对象存在,但自己不持有的情况下释放对象
  id obj1 = [obj0 object]
  [obj1 release];

五、allco/new/copy/mutableCopy实现

alloc

id obj =[NSObject alloc]
/*
* alloc方法实现
* 通过allocWithZone:类方法调用NSAllocateObject函数分配了对象
* NSAllocateObject函数通过调用NSZoneMalloc函数来分配存放对象所需的内存空间,之后将内存空间置0,最后返回作为对象而使用的指针
* 执行alloc后对象的retainCount=1(不考虑其他因素,只执行alloc后通过[obj retainCount]方法获取的值)
*/
+(id)alloc{
   return [self allocWithZone : NSDfaultMallocZone()];
}
+(id) allocWithZone:(NSZone *)z{
 return NSAllocateObject(self , 0 ,z);
}

疑惑点
1、 NSAllocateObject函数通过调用NSZoneMalloc函数来分配存放对象所需的内存空间,之后将内存空间置0,最后返回作为对象而使用的指针

  • 为什么要内存空间置0 ?
    • 答:分配存放对象A所需的内存空间,可能之前属于对象B并且存放很多B的东西,现在分配给A后,要将内存空间置0,防止野指针产生。
  • 知识点扩充:
    • 产生野指针的成因:
      • 1、指针变量未初始化
      • 2、指针释放后之后未置空
      • 3、指针操作超越变量作用域

总结

  • a、 在objective-C的对象中存有引用计数这一整数值
  • b、调用alloc或是retain方法后,引用计数值加1
  • c、调用release后,引用计数值减1
  • d、引用计数值为0时,调用dealloc方法废弃对象
  • e、苹果采用散列表(引用计数表)来管理引用计数


    D253DA4A-D7C8-4589-8323-3AD3AEDAC06C.png
  • f、引用计数表各记录中存有内存块地址,可以从各个记录追溯到各对象的内存块
    • f-a、有助于调试,即使出现故障导致对象占有的内存块损坏,只要引用计数表没有被破坏,就可以确认各内存块的位置,另外在利用工具检测内存泄露时,引用计数表的各记录有助于检测各对象的持有者是否存在


      D09BA31A-4A1D-4E3F-896B-3A71A964D9E2.png

六、autorelease

  • autorelease会像C语言的自动变量那样对待对象实例,当超出其作用域(NSAutoreleasePool对象的生存周期)时,对象实例的release实例方法会被调用,另外编程人员可设定变量的作用域

  • autorelease的具体使用方法:

    • (1)生成并持有NSAutoreleasePool对象
    • (2)调用已有分配对象的autorelesae实例方法
    • (3)废弃NSAutoreleasePool对象
5C0271E3-A893-4691-8D09-7AF019CF912B.png
  • 所有调用autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法(只要不废弃NSAutoreleasePool对象,那么生成的对象就不能被释放)
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
  id obj = [[NSObject alloc]init];
  [obj release];
  [pool drain];//废弃NSAutoreleasePool对象,相当于 [obj release];
  • 程序主循环的NSRunLoop或者在其他程序可运行的地方,对NSAutoreleasePool对象进行生成、持有、废弃处理


    27529D24-9612-4FB8-8D6C-7C94D84CADCB.png
  • autorelease如何实现

/*
* autorelease实例方法的本质就是调用NSAutoreleasePool对象的addObject类方法
* addObject类方法调用正在使用的NSAutoreleasePool对象的addObject实例方法(如果嵌套生成或持有NSAutoreleasePool对象,使用最内侧的对象)
* 调用NSObject类的autorelease实例方法,该对象将被追加到正在使用的NSAutoreleasePool对象中的数组中
*/
  [obj autorelease];
  
-(id)autorelease{
  [NSAutorelesaePool addObject:self];
}

// addObject类方法:
+(id)addObject:(id) anObj{
  NSAutorelesaePool *pool = 取得正在使用的NSAutorelesaePool对象;
  if(pool != nil){
      [pool addObject:anObj];
  }else{
     //NSAutorelesaePool对象非存在状态下调用autorelease;
  }
}

 //NSAutoreleasePool对象的addObject实例方法
-(id)addObject:(id)anObj{
   [array addObject: anObj]  ;
 }

//通过drain实例方法废弃正在使用的NSAutoreleasePool对象过程(数组中所有的对象都调用了release实例方法)
-(void)drain{
    [self dealloc];
}
-(void)dealloc{
    [self emptyPool];
    [array release];
}
-(void) emptyPool{
    for(id obj in array){
        [obj release];
  }
}

总结

  • 在Cocoa框架中有很多类方法用于返回autorelease的对象(例如:NSMutableArray类中的arrayWithCapacity类方法)
  id array = [NAMutableArray arrayWithCapacity:1];
  //等同与以下代码
  id array =[[[NAMutableArray alloc] initWithCapacity:1] autorelease] ;
  • 可通过NSAutoreleasePool类中的非公开类方法showPools来确认已被autorelease的对象的状况(该函数在检查某对象是否被自动release时非常有用)
  [NSAutorelease showPools];
  • 对象的autorelease实例方法,实现上是调用的都是NSObject类的autorelease实例方法

七、ARC规则

  • 设置ARC是否有效
    • -fno-objc-arc 无效
    • -fobjc-arc 有效(默认)

所有权修饰符

  • OC编程中为了处理对象,可将变量类型定义为id类型或各种对象类型

    • 对象类型:就是指向NSObject这样的OC类的指针,例如NSObject *
    • id类型:用于隐藏对象类型的类名部分
    • ARC有效时,id类型和对象类型上必须附加所有权修饰符
  • 所有权修饰符

    • _strong修饰符
    • _weak修饰符
    • _unsafe_unretained修饰符
    • _autoreleaseing修饰符

_strong修饰符

  • _strong修饰符是id类型和对象类型默认的所有权修饰符
id obj = [[NSObject alloc] init];
//等价于
id _strong obj = [[NSObject alloc] init];
  • 源代码ARC无效时情况
/*
* 情况一
*/
id obj = [[NSObject alloc] init];
//等价于
id obj = [[NSObject alloc] init];//ARC无效时

/*
* 情况二
*/
id _strong obj = [[NSObject alloc] init];
//等价于
id obj = [[NSObject alloc] init];
[obj release];//为了释放生成并持有的对象,增加调用了release方法
  • _strong修饰符表示对对象的‘强引用’,附有_strong修饰符的变量obj在超出其变量的作用域时,即在该被变量被废弃时,随着强引用的失效,引用的对象会随之释放
  • _strong修饰符的变量,不仅只在变量的作用域中,在赋值上也可以管理其对象的所有者
//obj0持有对象A的强引用
id obj0 = [[NSObject alloc] init];/*对象A*/
//obj1持有对象B的强引用
id obj1 = [[NSObject alloc] init];/*对象B*/
/*
* obj0持有由obj1赋值的对象B的强引用
* 因为obj0被赋值,所以原先持有的对对象A的强引用失效
* 对象A的持有者不存在,因此废弃对象A
* 此时持有对象B的强引用的变量为obj0 和obj1
*/
obj0 = obj1;

  • _strong _weak _autorelease修饰符,可以保证将附有这些修饰符的自动变量初始化为nil
  • 通过_strong修饰符,不必再次键入retain和release
  • 只要通过对带有_strong修饰符的变量赋值就可达成‘自己生成的对象,自己持有’和‘非自己生成的对象,自己也能持有’
  • 通过废弃带_strong修饰符的变量(变量作用域结束、成员变量所属对象废弃)或者对变量赋值,都可达到‘不在需要自己持有的对象时释放’

_weak修饰符

  • 循环引用:就是应当废弃的对象在超出其生命周期后继续存在,循环引用容易发生内存泄露
{
//test0持有Test对象A的强引用
id test0 =[[Test alloc] init];/*对象A*/
//test1持有Test对象B的强引用
id test1 =[[Test alloc] init];/*对象A*/
/*
* Test对象A的obj_成员变量持有Test对象B的强引用
* 此时持有Test对象B的强引用变量为test1、Test对象A的obj_
*/
[test0 setObject: test1];

/*
* Test对象B的obj_成员变量持有Test对象A的强引用
* 此时持有Test对象A的强引用变量为test0、Test对象B的obj_
*/
[test1 setObject: test0];
}
/*
* 因为test0变量超出其作用域,强引用失效,自动释放Test对象A
* 因为test1变量超出其作用域,强引用失效,自动释放Test对象B
* 此时持有Test对象B的强引用变量为Test对象A的obj_
* 此时持有Test对象A的强引用变量为Test对象B的obj_
* 内存泄露
*/

  • _weak修饰符于_strong修饰符相反,提供弱引用,弱引用不能持有对象实例
  • 将自己生成并持有的对象赋值为附有_weak修饰符的变量,因为弱引用不能持有对象,在超出其变量的作用域时,对象就没有了持有者,所以废弃该对象
  • _weak修饰符,在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态
  • _weak修饰符可避免循环引用,通过检查 _weak修饰符的变量是否为nil,可以判断被赋值的对象是否已废弃

_unsafe_unretained修饰符

  • 尽管ARC式的内存管理时编译器的工作,但附有_unsafe_unretained修饰符的变量不属于编译器内存管理对象

_autorelease修饰符

  • ARC有效时不能使用autorelease方法,也不能使用NSAutoreleasePool类
  • ARC有效时,通过将对象赋值给附有_autorelease修饰符的变量来代替调用autorelease方法,将对象注册到autoreleasepool
  • 非显示使用_autorelease修饰符
    • 编译器检查方法名是否已alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool(init方法返回值的对象不注册到autoreleasepool)
    • 在使用_weak修饰符的变量时就必然要使用注册到autoreleasepool中的对象
    • id的指针或对象的指针在没有显示指定时会被附加_autorelease修饰符(id * 类型 默认为‘id _autorelease ’* 类型)
  • 赋值给对象指针时,所有权修饰符必须一致
  • NSRunLOop等实现无论ARC有效还是无效,均能随时释放注册到main函数中autoreleasepool中的对象
ARC规则
  • 不能使用retain/release/retainCount/autorelease
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 必须遵守内存管理的命名规则
  • 不能显示调用dealloc
  • 使用@autorelease块代替NSAutoreleasePool
  • 不能使用区域(NSZone)
  • 对象型变量不能作为C语言结构体(struct/union)的成员
  • 显示转化‘id’和‘void’

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

  • 设置ARC有效时,禁止再次键入retain/release代码
  • retainCount获取的值不准确 ,不建议使用

2、不能使用NSAllocateObject/NSDeallocateObject

  • 会引起编译错误(同retain/release)

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

  • ARC有效时追加一天命名规则init(驼峰式)
    • 已init开头的方法,该方法必须时‘实例方法’
    • 并且必须要返回对象
    • 返回的对象应为id类型或该方法声明的对象类型,抑或是该类的超类型或子类型
    • 返回对象并不注册到autoreleasepool上
    • 基本上只是对alloc方法返回的对象进行初始化处理并返回该对象

4、不能显示调用dealloc

  • 无论ARC是否有效,只要对象的所有者都不持有该歇对象,该对象就被废弃,对象废弃,不管ARC是否有效,都会调用dealloc方法
  • ARC有效不能显示调用dealloc
//ARC无效时
-(void)dealloc{
    [super dealloc];
}
//ARC有效时(ARC会自动处理,ARC有效不能显示调用dealloc所以不必书写 [super dealloc])
-(void)dealloc{
 
}
  • dealloc方法在大多数情况下还适用于删除已注册的代理或者观察者对象
-(void)dealloc{
   [ [NSNotificationCenter defaultCenter] removeObserver:self];
}

八、属性

  • 当ARC有效时,OC类的属性也发生变化
@property (nonatomic , strong) NSString * name;
F7DC6FC7-543A-4E23-9673-9294EA918EF5.png
  • copy属性不是简单的赋值,它赋值的是通过NSCopying接口的copyWithZone:方法复制的复制源所生成的对象
  • 在声明类成员变量时,如果同属性声明中的属性不一致则会引起编译错误
/*
*错误举例
*/
/声明id型obj成员变量
id obj;

//定义其属性声明为weak
@property (nonatomic , weak) id obj;

/*
*正确举例1
*/
/声明id型obj成员变量
id   _weak obj;

//定义其属性声明为weak
@property (nonatomic , weak) id obj;


/*
*正确举例2
*/

//声明id型obj成员变量
id  obj;

//定义其属性声明为strong
@property (nonatomic , strong) id obj;

九、数组

  • 除_unsafe_unretained修饰符以外, _strong修饰符、 _weak修饰符、 _autorelease修饰符保证其指定的变量,数组初始化为nil

十、ARC实现

_strong修饰符

  • 自己生成并持有对象
    • 通过两次调用objc_msgSend方法(alloc方法、init方法),变量作用域结束时通过调用objc_release释放对象
  • 非自己生成,自己持有对象
    • objc_retainAutoreleasedReturnValue函数:自己持有对象函数(持有的对象应为返回注册在autoreleasepool中的对象方法或函数返回值)


      CFA44A73-0A3C-4428-A455-2E0419676574.png

_weak修饰符

  • 附有_weak修饰符的变量,通过objc_initWeak函数初始化,将对象赋值给该变量,会将对象的作为参数调用objc_storeWeak函数,objc_storeWeak函数将对象的地址作为键值,对应变量的地址注册到weak表中,并且一个对象可同时赋值给多个_weak修饰的变量,所以一个键值,可以注册多个变量地址,对象释放时时最后调用的objc_clear_deallocting函数会从weak表中获取废弃对象的地址为键值的记录,将包含在记录中的所有附有_weak修饰符变量的地址,赋值为nil,从weak表中删除该记录,从引用计数表中删除废弃对象的地址为键值的记录 。

  • 若附有_weakk修饰符的变量所引用的对象被废弃,则将nil赋值给该变量

    • 释放对象时,废弃谁都不持有的对象的同时,有如下操作(详情见《iOS与os x多线程和内存管理》1.4.2 _weak修饰符 p68):
      • 1、objc_release
      • 2、因为引用计数为0,所以执行dealloc
      • 3、_objc_rootDealloc(不知道)
      • 4、objc_dispose(不知道)
      • 5、objc_destructInstance(不知道)
      • 6、objc_clear_deallocting(了解)
    • 对象被废弃时最后调用的objc_clear_deallocting函数操作如下:
      • 1、从weak表中获取废弃对象的地址为键值的记录
      • 2、将包含在记录中的所有附有_weak修饰符变量的地址,赋值为nil
      • 3、从weak表中删除该记录
      • 4、从引用计数表中删除废弃对象的地址为键值的记录
  • 使用附有_weak修饰符的变量,即是使用注册到autoreleasepool中的对象

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容