2.1 内存消耗
- 2.1.1 栈大小
1.每个线程都有专用的栈空间
2.每个方法都有自己的栈帧,并消耗整体的栈空间,栈帧会消耗一定字节的内存
3.复杂页面会递归调用layoutSubViews和drawRect方法,如果层级过深,可能会导致栈溢出
- 2.1.2 堆大小
1.每个进程的所有线程共享一个堆
2.一个应用可以使用的堆大小远远小于设备的RAM值
2.2 内存管理模型
- 当一个对象创建于某个方法的内部时,那该方法就持有了这个对象
- 如果这个对象从方法返回,则调用者持有该对象
2.3 自动释放对象
- 自动释放对象让你能放弃对一个对象的持有关系,但延后对它的销毁
- 使用autorelease的引用计数有如下规则:
1.持有的对象是alloc方法返回的
2.不需要再使用release
3.使用autorelease表明你想要放弃持有关系,同时允许方法的调用者在对象呗释放之前使用对象
2.4 autoreleasepool
- autoreleasepool是允许你放弃对一个对象的持有关系,但可避免它立即被回收的一个工具
- 块中收到过aurolease消息的所有对象都会在autoreleasepool块结束时收到release消息。
2.5 自动引用计数
- ARC规则如下:
1.不能用retain,release,autorelease,retainCount方法
2.可以实现dealloc,但是不能调用
3.不能在C语言的结构体内使用对象指针
4.id和void *不能自动转换,需要显示转换
5.属性不能以new开头
2.6 引用类型
- 默认的引用类型是强引用
- 被强引用指向的内存不会被释放
- 强引用会对引用计数加1,从而拓展对象的生命周期
- 弱引用是一种特殊的引用类型。它不会增加引用计数,因而不会拓展对象的生命周期
- __weak具有安全性,对象被回收时,指针将自动被设置为nil
- __unsafe_unretained和__weak相似,但不能在对象被回收时,自动设置为nil
- assign,unsafe_unretained只应该用于值类型
2.8 僵尸对象
- 僵尸对象用于捕捉内存错误的调试功能
- 通常情况下,当引用计数降为0时,对象会被立即被释放,使得调试困难。如果开启了僵尸对象,那么对象不会被立即释放,而是标记为僵尸。
- 任何访问僵尸对象的行为都会被记录,因此可以排查问题
- NSZombieEnabled是一个环境变量,可以控制Core Foundation的运行时是否将使用僵尸对象
- 不应该长期保留NSZombieEnabled,因为牧人情况下不会有对象呗真正析构,这会导致应用使用大量内存。
- 发布前要禁用NSZombieEnabled
2.10 循环引用
2.11 弱类型:id
- 由于可以把任何消息都发送给id类型的对象,会出现编译器不报错的问题,代码如下:
@interface Test:NSObject
- (void)test1;
@end
@implementation Test
- (void)test1 {
NSLog(@"xxxx");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSArray *ary = @[@"a",@"a",@"a"];
[ary[1] test1];
}
return 0;
}
- 上述ary[1]得到的为id类型的实例,且没有提示没定义test1函数等错误信息,是因为编译器会根据所调用的方法名称遍历查找导入的头文件以匹配的方法名,然后通过找到第一个匹配的方法获取参数长度
- 解决方案:
1.[(NSString*)ary[1] test1]; 显示调用,编译器会直接在对应类型中查找
2.开启编译器警告"Strict Selector Matching",开启后在你id类型实例发送方法时,若多个类有完全相同的方法签名(名称和参数)时会有警告,但开启后会导致内置的框架产生许多警告,出现一堆不必要警告
- 在常规命名中尽量避免使用id类型
2.13 单例
- 依赖注入:需要时传递依赖,将依赖以外界传入,而不是说在本类进行实例化,将实例化的过程外放
2.15 最佳实践
- 常用最佳实践:
1.避免大量使用单例
2.不要设计出万能类,(尽量职责单一)
3.对数值属性(SEL,CGFloat等)用assign
4.对block属性,使用copy
5.避免在block中直接使用外部的变量,@weakify,@strongify
6.注意销毁定时器、移除通知、解除回调
网友评论