13.内存管理
每个OC对象里都有一个引用计数器,用来统计正在被引用多少次,每个引用计数器占4个字节,对象刚刚创建时引用计数器默认为1。如果OC对象引用计数器为0时,系统就可以回收这个对象了。
引用计数器的操作:
(1).给对象发送一条retain消息,可以使引用计数器值+1(retain方法返回对象本身)
(2).给对象发送一条release消息,可以使引用计数器-1
(3).给对象发送一条retainCount消息,可以获得当前的引用计数器值
对象的销毁:
(1).当一个对象的引用计数器的值为0时,那么它将被销毁,其占用的内存会被系统回收
(2).当一个对象被销毁时,系统会自动向对象发送一条dealloc消息
(3).一般会重写dealloc方法,在这里释放相关资源,dealloc就像是对象的遗言
(4).一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用(ARC中不需要调用[super dealloc],因为系统会帮我们做)
(5).不要直接调用dealloc方法,它只能重写
(6).一旦对象被回收了,它占用的内存不再可用,坚持使用就会导致程序崩溃(因为已经是僵尸对象)
概念:
僵尸对象:所占用的内存已经被回收的对象,僵尸对象不能再使用
野指针:指向僵尸对象的指针,给野指针发送消息会报错
空指针:没有指向任何东西的指针(存储的东西是nil,null,0),给空指针发送消息不会报错(所以如果一个对象内存已经被释放的话,一定要将它的指针置为空指针,以免出现野指针)
备注:nil和null不同,null是一个宏定义,值为0,nil表示无值,任何指针变量在没有赋值之前都是nil,对于真假判断,只有nil和false表示假,其他皆为真
内存管理原则:
(1).谁创建,谁release:如果你通过alloc、new或者[mutable]copy来创建一个对象,那么必须调用release或autorelease
(2).谁retain,谁release: 只要你调用了retain,无论这个对象如何生成的,你都要调用release
14.setter方法内存管理
Setter方法中,会对一个retain修饰的属性进行retain操作,那么,我们必须保证在对象销毁前,对这个属性进行release操作.
比如一个人person有一个属性book:
- (void)dealloc{
[_book release];//b引用计数器减1
[super dealloc];//这个方法写在最后
}
- (void)test{
Person *p = [Person new];//alloc,p引用计数器为1
Book *b = [Book new];//alloc,b引用计数器为1
p.book = b;//调用setter方法,b引用计数器为2
[p release];//p应用计数器0,将被系统回收,系统会给p发送dealloc消息
p = nil;//置为空指针,防止野指针出现
/**
由于调用setter方法时,p对b进行了引用计数器加1的操作,那么我们得在dealloc中将b的引用计数器减1
*/
}
- (void)setBook:(Book *)book{
//判断是否传进来的是旧对象
if (_book != book) {
[_book release];//旧对象引用计数器减1
_book = book.retain;//新对象引用计数器+1
}
}
在工程中同时支持ARC与MRC,方法:
在Build Phase里面的Compile Source中找到需要特殊处理的文件,双击加上编译选项(Complier Flags):
(1)项目是ARC,文件需要支持MRC,那么加上”-fno-objc-arc”
(2)项目是MRC,文件需要支持ARC,那么加上”-fobjc-arc”
如果对于代码中要分别处理ARC和MRC,可以加上条件编译这样写:
- (void)dealloc{
#if !__has_feature(objc_arc)//下方写MRC的处理
[_book release];//b引用计数器减1
[super dealloc];//这个方法写在最后
#else
/**这里写ARC时候的处理 */
#endif
}
15.@class
@class是对一个类的声明,它仅仅是告诉编译器有这么一个类。它主要用于在一个类的.h文件中声明另一个类,防止两个类同时在.h文件中#import对方造成的循环拷贝.
在实际开发中,使用如下:
(1).在.h文件中用@class来声明类
(2).在.m文件中#import类
16.两个类的循环引用
在实际开发中,两个类中会出现都要声明对方为自己的属性的情况,如果都用strong/retain,那么会出现”你包含我,我包含你”问题,在对对象发送release消息时,会出现占用内存永远回收不了的问题。解决方法:
-个修饰符用strong/retain,一个修饰符用weak
17.autorelease(自动释放池)
自动释放池是通过以AutoreleasePoolPage为节点的双向链表来实现的。它的行为和栈类似(先进后出)
autorelease的基本用法:
(1).会将对象放到一个自动释放池中
(2).当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
(3).会返回对象本身
(4).调用完autorelease方法后,对象的引用计数器值不变
在ios5.0之前,autorelease写法为
NSAutoreleasePool *autoreleasePool = [[NSAutoreleasePool alloc]init];
//这里会将该对象加入最新创建的自动释放池里面
Person *p = [[Person new]autorelease];
[autoreleasePool release];
在ios5.0之后
@autoreleasepool
{//此处创建自动释放池
Person *p = [[Person new]autorelease];
}//此处销毁自动释放池
autorelease好处:
(1).不用再关心对象释放的时间
(2).不用再关心什么时候调用release
autorelease使用注意:
(1).占用内存交大的对象不要随便使用autorelease
(2).占用内存较小的对象使用autorelease,没有太大影响
autorelease错误用法
(1).alloc之后调用了autorelease,又调用release
@autoreleasepool
{
Person *p = [[Person new]autorelease];
[p release];//错误,它会自动释放
}
(2).连续调用多次autorelease
@autoreleasepool
{
Person *p = [[[Person new]autorelease]autorelease];//错误,只能调用一次autorelease
}
自动释放池
(1).在ios程序运行过程中,会创建无数个池子。这些池子都是以栈结构存在(先进后出)
(2).当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池
备注:系统自带的方法中,如果不包含alloc、new、copy,那么这些方法返回的对象都是已经autorelease过的,比如:
[NSString stringWithFormat:@""];
[NSDate date];
开发中经常写一些类方法快速创建一个autorelease对象(创建对象的时候不要直接使用类名,使用self),例如:
//以后可以只用使用这个类方法类创建对象
+ (instancetype)person{
return [[[self alloc]init]autorelease];
}
18.ARC(内存回收)
ARC是编译器特性,可以理解为XCode的功能,它主要用来帮助用户做内存管理工作。
ARC判断准则:
只有没有强指针指向对象,就会释放对象。同时系统还会根据弱指针的情况及时释放弱指针对象,避免野指针的产生
指针分两种:
(1).强指针:默认情况下,所有指针都是强指针(__strong,属性修饰符为strong)
(2).弱指针:定义一个指针是弱指针,只需要在定义钱加上__weak声明即可(属性修饰符为weak)
ARC特点:
(1).不允许调用release、retain、retainCount
(2).允许重写dealloc,但是不允许调用[super dealloc]
(3).@property的参数
①strong:成员变量是强指针(适用于OC对象类型)
②weak:成员变量是弱指针(适用于OC对象类型)
③assign:适用于非OC对象类型
(4).以前的retain改为用strong
19.Block
定义:
返回值类型 (^block名称)(参数表) =
^(参数表){
//代码块部分
};
int (^sumBlock)(int,int) = ^(int a,int b){
return a+b;
};
int sum = sumBlock(10,2);
Block和函数很像,Block特点:
(1).可以保存代码
(2).可以有返回值
(3).可以有形式参数
(4).调用方式和调用函数一样
(5).可以像方法一样调用外面的变量
(6).默认条件下,Block不能修改外面的局部变量,但是在变量名签名加上__block修饰就可以使Block代码块中修改这个变量。(Block默认不能修改局部变量,但是Block可以修改全局变量)
(7).实际开发中,常用typedef定义Block以减少代码量
typedef int (^MyBlock) (int,int);
@property (nonatomic,copy) MyBlock block;
//调用
self.block = ^int(int a, int b) {
return a % b;
};
备注:当block要用到外卖对象的指针的时候,要把这个指针使用__weak或者__unsafe_unretained声明为弱指针,否则会出现循环引用的问题,声明方法:
//使用__weak声明,不能用在MRC中,否则会报错
__weak typeof(self) weakSelf = self;
//使用__unsafe_unretained声明
__unsafe_unretained typeof(self) unRetainedSelf = self;
后面在Block代码块中调用就使用弱指针,就不会出现循环引用了
20.block底层原理
关于Block底层实现原理可以看看王威大神的这篇文章:Block技巧与底层解析
大致的信息如下:
Block的底层结构为:
/* Revised new layout. */
struct Block_descriptor{
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa; int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
(1).block底层实现为一个一个结构体,其中有isa指针,所以block也是一个对象(runtime里面,对象和类都是用结构体表示,并且都有isa指针)
(2).invoke为block执行时调用的函数指针,记录函数地址,block定义时内部的执行代码都在它指向的函数中
(3).flags为标志变量,在实现block内部操作时会用到;reserved为保留变量
(4).Block_descriptor是对block的详细描述
.copy/dispose,辅助拷贝/销毁函数,处理block范围外的变量时使用
总结:block就是一个里面存储了指向函数体中包含定义block时的代码块的函数指针,以及block外部上下文变量等信息的结构体
Block的类型:(类型看的是isa指针指向哪种)
(1)._NSConcreteGlobalBlock 全局,创建的位置为全局区
(2)._NSConcreteStackBlock 栈 创建的位置在函数体内
(3)._NSConcreteMallocBlock 堆 这种不能直接创建,只能由栈类的block拷贝而来,他会调用_Block_copy_internal函数类拷贝,这个函数的实现中使用memove将栈中的block的内存拷贝到堆中,并将新拷贝过来的block的isa指针指向_NSConcreteMallocBlock
捕捉变量对block结构的影响:
局部变量
int a = 5;
__block typeof(a) blockA = a;
^{blockA = 10;};
如果直接将a传入block,不能修改,原因是block的实现中,定义了一个和a同类型的成员变量来存储外部变量a的值,这次的拷贝是一个值传递,因为作用域不同,所以直接对a赋值是没有意义的,所以编译器给了错误。所以我们要使用__blcok修饰局部变量
全局变量
由于全局变量都是在静态数据存储区,在程序结束前不会被销毁,所以block直接访问到了对应的变量,没有创建中间变量,所以全局变量可以直接在block中进行赋值等修改操作
局部静态变量
静态变量和全局变量一样,都是存储在静态数据存储区,和程序拥有一样的生命周期,也就是说在程序运行时,都能够保证block访问到一个有效的变量。但是要注意的是,局部静态变量的作用域还是在定义它的函数中,所以只能在block通过静态局部变量的地址来进行访问
__block修饰的变量
使用__block修饰后,在实现中会创建一个结构体b来包装局部变量a,在block被拷贝到堆中时,拷贝辅助函数会将这个结构体拷贝到堆中,堆中结构体b的__forwarding指针指向自身,栈中的__forwarding指针指向堆中的拷贝,这样就可以保证操作的值始终是堆中的拷贝,而不是栈中的值,这样也就可以对局部变量进行修改了
网友评论