iOS的内存管理和引用计数规则、Block的用法以及三种形式(stack、malloc、global)
学习内容
-
iOS的内存管理和引用计数规则
-
内存管理的思考方式
- 自己生成的对象自己持有
- 非自己生成的对象自己也能持有
- 自己持有的对象不需要时释放
- 非自己持有的对象不能释放
-
ARC有效时,id类型和对象类型必须加上所有权修饰符,一共有四种
-
__strong
-
id和对象类型如果不加所有权修饰符那么默认为__strong类型
-
id obj = [[NSObject alloc]init] id __strong obj = [[NSObject alloc]init] //以上两种在ARC有效情况下是相同的
-
//ARARC { id __strong obj = [[NSObject alloc]init] } //ARC无效时 { id obj = [[NSObject alloc]init] [obj release] } //ARC无效时执行release操作
-
__strong修饰符表示对对象的强引用,持有强引用的变量在超出其作用域时被废弃,它强引用的对象也会被释放
-
含有__strong修饰的变量,不仅仅在作用域上,在赋值上也能正确管理对象的生命周期
-
-
__weak
- __strong所有权修饰符大部分情况下可以完美的进行内润管理,但是引用计数式内存管理方法必然会遇到循环引用,这时候就需要使用__weak来解决
- 循环引用会造成内存泄漏,所谓内存泄漏就是应当被废弃的对象在超出其生存周期(作用域)后继续存在
- __weak修饰的变量不持有对象,原对象在超出其作用域时会被立即释放
- 通过检查__weak修饰符的变量是否为nil,可以判断被赋值的对象是否已经被释放
-
__unsafe_unretained
- 这是不安全的所有权修饰符
- 附有__unsafe_unretained的变量不属于编译器的内存管理对象
- 如果对象已经被释放,但是__unsafe_unretained修饰的变量仍然访问了原对象的内存,那么这个变量就是个悬垂指针,错误访问,大概率会引起程序的崩溃
-
__autoreleasing
- 在ARC有效时,用@autoreleasepool块代替NSAutoreleasePool类,用附有__autoreleasing修饰符的变量来代替autorelease方法
- 编译器会自动检查方法名是否为alloc/new/copy/mutablecopy开头,如果不是的话则自动将返回值的对象注册到autoreleasepool中(init方法返回值的对象也不注册到autoreleasepool中)
-
-
属性声明的属性修饰符与所有权修饰符之间的关系
-
属性修饰符 _unsafe_unretained assign _unsafe_unretained copy _strong(指针变量指向的是新的被复制的对象) retain _strong strong _strong unsafe_unretained _unsafe_unretained weak _weak -
为属性添加各种修饰符就相当于给变量添加各种对应的所有权修饰符
-
@property (strong) id obj; -------------------------- id __strong obj = [[NSObject alloc]init]
-
-
ARC的规则
- 不能使用retain/retainCount/release/autoRelease
- 不能使用NSAllocateObject和NSDeallocateObject
- 遵守内存管理的方法命名规则
- 不要显式的调用dealloc
- 使用@autoreleasepool代替NSAutoreleasePool
- 不能使用NSZone
- 对象型变量不能作为c语言结构体的成员
- 显式转换"id"和"void *"
-
-
Block
-
block是什么
-
//一句话概括,block是带有自动变量的匿名函数 ^(int param){ NSLog(@"%d",param); } ------------------------------------- //上面的为简写的,完整的block形式为 //^返回值类型 (参数列表){表达式} ^void (int param){ NSLog(@"%d",param); } //完整形式的block语法与C语言函数定义相比,仅有两点不同 //1.没有函数名---没有函数名因为它是匿名函数 //2.带有"^"符号---^是插入记号,方便查找
-
block的返回值类型可以省略,省略返回值类型时
- 如果表达式中有return语句,那么返回值类型就和return的相同
- 没有return,返回值类型就是void
- 有多个return语句时,所有return的类型必须相同
-
block的参数类型也可以省略
-
^(void)(void){expression} ------------------------- ^{expression}
-
-
//C语言的函数指针的写法 int func(int a){ printf("%d",a); } int (*funcptr)(int) = &func //将func函数的地址赋值给函数指针funcptr //block的写法 int (^blk)(int a); //仅仅是将c语言函数指针的"*"更换成了"^"
-
block类型变量与c语言变量的用法完全相同,可以作为以下用途使用
- 自动变量(局部变量)
- 函数参数
- 静态变量
- 静态全局变量
- 全局变量
-
block变量的使用
-
//将block赋值给block变量 int (^blk)(int) = ^(int){}; //将block变量赋值给block变量 int (^blk1)(int) = blk; //两个block变量互相赋值 int (^blk2)(int); blk2 = blk1;
-
//在函数参数中使用block类型变量可以向函数传递block void func((int)(^blk)(int)); //在函数返回值中使用block可以将返回值指定为block void func(){ return ^{return;}; }
-
//使用typedef定义block typedef (int)(^blk)(int); //定义一个block,后面该block的类型就为blk //通过block类型变量调用block与在c语言中通常的函数执行没什么区别 blk func(blk block,int rate){ return blk(rate); }
-
//这里blk截获的是Array对象的实例指针,通过这个实例指针调用该对象的方法是完全没问题的,但是如果向Array指针赋值的话就会编译错误(可以用__block解决) id Array = [NSMutableArray new]; void (^blk)(void) = ^{ id obj = [NSObject new]; [Array addObject:obj]; };
-
在block中如果需要改变被截获的外部变量的值,可以使用__block说明符(__block存储域类说明符)来解决
-
block代码转换为cpp代码分析(待完善)
//block对外部变量捕获的原理,使用cpp代码查看 //这里写一个block捕获外部变量val的值 int val = 10; void (^blk)(void) = ^{ printf("%d",val); }; //block本质也是一个OC的对象,oc对象都是结构体,其中含有指向父类的isa指针 struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int val; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; //这里使用clang -rewrite-objc将.m代码转换成.cpp代码,这里的_cself是block的自身的结构体指针,可以看到在block的函数中是将val重新创建了一个变量进行输出 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int val = __cself->val; // bound by copy printf("%d",val); }
-
三种block的形式
-
NSConcreteGlobalBlock(全局Block,存放在全局区[数据区])
- 记录全局变量的地方有Block语法时
- Block语法的表达式中不使用应截获的自动变量时
-
NSConcreteStackBlock(栈Block)
- 除了1.中的两种情况生成的Block,其他使用Block语法产生的Block都是栈Block
-
NSConcreteMallocBlock(堆Block)
-
配置在全局区的block在变量作用域外也可以通过指针安全的访问,但是配置在栈上的block一旦其作用域结束就会被系统回收
-
Block提供了将Block和__Block变量复制到堆上的方法来解决这个问题
-
复制到堆上的block将类对象NSMallocBlock赋值给isa指针
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) { //注意这里就是讲isa指针指向堆Block impl.isa = &_NSConcreteStackBlock; } };
-
-
-
-
-