范围:
- 任何继承了NSObject的对象,对基本数据类型无效。OC对象是放在堆内存里,非OC对象是放在栈内存里,栈内存里的东西系统会自动管理
内存区域
- 内存分为5个区域,分别指的是----->栈区/堆区/BSS段/数据段/代码段
- 栈:存储局部变量,当其作用域执行完毕之后,就会被系统立即收回,函数调用开销,比如局部变量,分配的内存空间地址越来越小
- 堆:存储OC对象,手动申请的字节空间,需要调用free来释放
- BSS段:未初始化的全局变量和静态变量,一旦初始化就会从BSS段中回收掉,转存到数据段中
- 数据段:存储已经初始化的全局变量和静态变量,以及常量数据,直到结束程序时才会被立即收回
- 代码段:代码,直到结束程序时才会被立即收回
原理
- 每个对象内部都保存了一个与之相关联的整数,称之为引用计数器。
- 当使用alloc,new copy 创建一个对象时,对象的引用计数器被设置为1.
- 给对象发送一条retain消息,可以使引用计数器值+1
- 给对象发送一条release消息,可以使引用计数器值-1
- 当一个对象的引用计数器值为0时,那么它将被销毁,其占用的内存被系统回收,OC也会自动向对象发送一条dealloc消息,一般会重写dealloc方法,在这里释放一些相关资源,一定不要直接调用dealloc方法。
- 可以给对象发送retainCount消息获得当前的引用计数器值
内存管理原则
- 谁创建,谁释放。如果你通过alloc,new或(mutable)copy来创建一个对象,那么你必须调用release或autorelease。换句话来说,不是你创建的,就不用你去释放
- 一般来说,除来allocation,new或copy之外的方法创建的对象都被声明了autorelease。
- 谁retain,谁release,只要你调用了retain,无论这个对象是如何生成的,你都要release。
//计数器为1 NSObject *obj = [[NSObject alloc] init]; //0,被释放了。 [obj release]; //查看引用计数 [obj retainCount];
- 在MRC在用retain修饰属性,先release原来的值在retain新的值。
自动释放(autorelease)
- OC对象只需要发送一条autorelease消息,就会把这个对象添加到最近的自动释放池(栈顶的释放池),不会改变引用计数。
- autorelease实际上只是把对release的调用延迟了,对于每一次autorelease,系统只是把该对象放入了当前的autoreleasepool中,当pool被释放时,该pool中所有的对象会被调用release。
- 静态方法返回的对象不需要自己管理内存,会自动释放。
自动释放池
- 自动释放池是oc里面的一种内存自动回收机制,一般可以将一些临时变量添加到自动释放池中,统一回收释放,当自动释放池销毁时,池里面的所有对象都会调用一次release方法
- 自动释放池中的对象会集中同一时间释放,如果操作需要生成的对象较多占用内存空间大,可以使用多个释放池来进行优化。比如在一个循环中需要创建大量的临时变量,可以创建内部的池子来降低内存占用峰值
- 使用注意
- 再ARC下,不能使用[[autoreleasepool alloc]init],可以使用@autoreleasepool
- 不要把大量循环操作放在同一个autoreleasepool 中,这样会造成内存峰值的上升。
- 尽量避免对大内存使用该方法,对于这种延迟释放机制还是少用。
- SDK中一般利用静态方法创建并返回的对象已经是autorelease,不需要release操作了。
- 通过[NSmunber numberWithInt:10];返回的对象不再需要release的,但是通过[[NSnumber alloc]initWithInt:10]创建的对象需要release
野指针和空指针
-
野指针
- 在C中,声明一个指针变量,没有为这个指针变量初始化,那么这个指针变量的值也就是一个垃圾值,指针指向随机的一块空间,那么我们叫做野指针
- 在OC中,一个指针指向的对象被释放了,那么这个指针叫野指针
- 给野指针发消息会报EXC_BAD_ACCESS错误
-
空指针
- 没有指向存储空间的指针(里面存的是nil, 也就是0)
- 给空指针发消息是没有任何反应的
-
僵尸对象
已经被收回但是这个对象的数据仍然处在内存中,像这样的对象叫做僵尸对象
僵尸对象有可能可以访问也有可能不可以访问,当僵尸对象所占的内存空间还没有分配给别人使用的时候,这个数据的对象其实仍然存在,通过指针仍然可以找到这个对象,所以说这个时候僵尸对象还可以被访问,当这个僵尸对象已经分配给别人使用的时候,这个对象就不存在了,这个时候不可以被访问
注意:一旦一个对象成为僵尸对象之后,这个对象无论如何都不应该被使用,无论有没有分配给别人使用,都不能用!且不可以复活
属性修饰
- 读写属性
- readwrite
- readonly
- set处理
- retain:自动把set方法中的成员变量,release原来的值,然后再retain新的值。
- assign:基本数据类型,set方法直接赋值,而不进行retain操作。
- copy:set方法release原来的值,在copy新的值。
- getter:指定get方法的方法名。
- 原子性
定时器的循环引用问题
- CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用
- 解决方案
- 使用block
- 使用代理对象(NSProxy)
- NSProxy介绍
- 不继承NSObject,和NSObject是同一个级别,是一个特殊的基类
- 作用:经常用作,做消息转发。
- 示例
//MJProxy.h @interface MJProxy : NSProxy + (instancetype)proxyWithTarget:(id)target; @property (weak, nonatomic) id target; @end //MJProxy.m #import "MJProxy.h" @implementation MJProxy + (instancetype)proxyWithTarget:(id)target { // NSProxy对象不需要调用init,因为它本来就没有init方法 MJProxy *proxy = [MJProxy alloc]; proxy.target = target; return proxy; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [self.target methodSignatureForSelector:sel]; } - (void)forwardInvocation:(NSInvocation *)invocation { [invocation invokeWithTarget:self.target]; } @end //调用 #import "ViewController.h" #import "MJProxy.h" #import "MJProxy1.h" @interface ViewController () @property (strong, nonatomic) NSTimer *timer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES]; } - (void)timerTest { NSLog(@"%s", __func__); } - (void)dealloc { NSLog(@"%s", __func__); [self.timer invalidate]; }
Tagged Pointer
从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
在没有使用Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中
当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
objc_msgSend能识别Tagged Pointer,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销
如何判断一个指针是否为Tagged Pointer?
- iOS平台,最高有效位是1(第64bit)非Tagged Pointer最后一位是0。
- Mac平台,最低有效位是1
copy详解
自定义对象添加copy属性
- 实现NSCopying协议
- 重写- (id)copyWithZone:(NSZone *)zone方法
- 示例
MJPerson.h @interface MJPerson : NSObject <NSCopying> @property (assign, nonatomic) int age; @property (assign, nonatomic) double weight; @end MJPerson.m @implementation MJPerson - (id)copyWithZone:(NSZone *)zone { MJPerson *person = [[MJPerson allocWithZone:zone] init]; person.age = self.age; person.weight = self.weight; return person; } - (NSString *)description { return [NSString stringWithFormat:@"age = %d, weight = %f", self.age, self.weight]; } @end //调用 int main(int argc, const char * argv[]) { @autoreleasepool { MJPerson *p1 = [[MJPerson alloc] init]; p1.age = 20; p1.weight = 50; MJPerson *p2 = [p1 copy]; p2.age = 30; NSLog(@"%@", p1); NSLog(@"%@", p2); //MRC下要release [p2 release]; [p1 release]; } return 0; }
引用计算的存储
- 在64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable类中
- refcnts是一个存放着对象引用计数的散列表