Objective-C高级编程读书笔记(一)

自动引用计数

自动引用计数(ARC, Automatic Reference Counting), 是指内存管理中对引用采用自动计数的计数,让编译器来进行内存管理

内存管理/引用计数

思考方式

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

这四种方式,对应成oc的方法的话,就是:

对象操作oc方法
生成并持有对象alloc/new/copy/mutableCopy等方法
持有对象retain方法
释放对象release方法
废弃对象dealloc方法
自己生成的对象,自己持有

使用以下名称的方法意味着自己生成的对象只有自己持有:alloc, new, copy, mutableCopy. e.g.:

id obj = [[NSObject alloc] init];
复制代码

NSObject通过alloc类方法自己生成并持有对象,并将指向此对象的指针赋值给变量obj(obj实际上是一个指针)

非自己生成的对象,自己也能持有
id obj = [NSMutableArray array];
复制代码

上面这段代码中,变量obj并不持有NSMutableArray生成的类对象(可以通过retain方法进行持有)

不在需要自己持有的对象时释放

释放采用release方法

id obj = [[NSObject alloc] init];
[obj release]; // 释放obj指向的对象,obj本身仍存在,但是其指向的对象已经被释放
复制代码
非自己持有的对象不能释放

alloc/retain/release/dealloc的实现

根据苹果的文档,可以看出alloc的实际实现是:

+ alloc
+ allocWithZone:
class_createInstance
calloc
复制代码

autorelease

具体使用方法:

  1. 生成并持有NSAutoreleasePool对象
  2. 调用已分配对象的autorelease实例方法
  3. 废弃NSAutoreleasePool对象

对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法。需要注意的是,如果生成大量的autorelease对象,而NSAutoreleasePool对象又不废弃的话,大量对象得不到释放,就会出现内存不足的现象。

autorelease的实现如下:

class AutoreleasePoolPage {
  static inline void *push () {
    // 相当于生成或持有NSAutoreleasePool类对象
  }

  static inline void *pop() {
    // 相当于废弃NSAutoreleasePool类对象
    releaseAll();
  }

  static inline id autorelease(id obj) {
    // 相当于NSAutoreleasePool类的addObject类方法
    AutoreleasePoolPage *autoreleasePoolPage = 取得正在使用的AutoreleasePoolPage实例
    autoreleasePoolPage->add(obj);
  }

  id *add(id obj) {
     // 将对象添加到内部数组
  }
  
  void releaseAll() {
    调用内部数组中对象的release实例方法
  }
}
复制代码

从源代码中我们可以看到,autorelease方法的实现是通过动态数组来进行自动释放池的对象的存储,在需要释放的时候废弃数组中的对象。

ARC规则

所有权修饰符

在ARC有效时,id类型的所有权修饰符有4种:__strong, __weak, __unsafe_unretained__autoreleasing

__strong

__strong是默认的修饰符。表示对对象的强引用,在其作用域被废弃的时候,随着强引用的失效,引用的对象会随之释放

__weak

__weak防止循环引用引起的内存泄漏。比方说

id obj1 = [[NSObject alloc] init];
id obj2 = [[NSObject alloc] init];
obj1.obj = obj2;
obj2.obj = obj1;

// or
obj1.obj = obj;
复制代码

上面代码中两个写法都会造成循环引用,因为两个对象之间有互相强引用,导致作用域被废弃时,两个对象之间得不到释放。通过__weak可避免这种情况。除此之外,在持有某对象的弱引用时,如果对象被废弃的话,弱引用将自动失效并且处于nil

__unsafe_unretained

__unsafe_unretained修饰的变量跟__strong__weak不同,是不属于编译器的内存管理对象。其变量既不持有强引用也不持有弱引用。

__autoreleasing

在ARC有效时,使用@autoreleasepool来替代NSAutoreleasePool类,用__autoreleasing替代autorelease方法。一般不会显式附加。对于非自己生成当持有的对象来说,编译器会自动检查方法名是否以alloc/new/copy/mutableCopy开头,如果不是的话会将对象注册到自动释放池当中。

使用__weak变量的时候,必定会访问到注册到自动释放池中的对象。

因为__weak只持有对象的弱引用,在访问引用对象的过程中,该对象随时有可能被废弃。但是只要将对象注册到自动释放池中的话,则能保证在作用域结束之前,对象一直存在。

可以使用_objc_autoreleasePoolPrint()方法调试自动释放池上的对象

规则
  • 不能使用retain/release/retainCount/autorelease: 内存管理是编译器的工作,没必要使用内存管理的方法
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 须遵守内存管理方法命名规则:alloc/new/copy/mutableCopy开头的方法在返回对象时,必须返回给调用方所应当持有的对象。
  • 不要显式调用dealloc
  • 使用@autoreleasepool替代NSAutoreleasePool
  • 不能使用NSZone:
  • 对象型变量不能作为C语言结构体的成员:即C语言中不能使用OC的对象
  • 显式转换idvoid *: 使用__bridge_retained__bridge_transfer(多用于OC对象和Core Foundation对象的转换)
数组

id __strong *array = nil;

id *类型默认为id __autoreleasing * 类型,因此需要显式指定为__strong。但是,这仅保证了__strong修饰的id类型变量被初始化为nil,并不保证id指针型变量被初始化为nil

ARC的实现

__strong
{
  id __strong obj = [[NSObject alloc] init];
}

// 编译器的模拟代码
id objc = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);

{
  id __strong obj = [NSMutableArray array];
}

// 编译器的模拟代码
id objc = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj); // 用于最优化程序运行。用于自己持有对象的函数,但它返回的对象应为返回注册在自动释放池中的对象的方法或者是返回值
objc_release(obj);
复制代码

objc_retainAutoreleasedReturnValueobjc_autoreleaseReturnValue一般是配套使用的,通过这两个方法,可以获取到原本应注册到释放池中的对象,省略了注册和查找的步骤,实现优化处理。

__weak
  • 若使用__weak的变量所引用的对象被废弃的话,则将nil赋值给该变量
  • 使用__weak的变量,即是使用注册到autoreleasepool中的对象。
{
  id __weak obj1 = obj; // obj是__strong修饰的值
}

// 编译器模拟代码
id obj1;
objc_initWeak(&obj1, obj); // 初始化后调用objc_storeWeak函数
-> obj1 = 0; objc_storeWeak(&obj1, obj);
objc_destoryWeak(&obj1);

// 等价于
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);
复制代码

objc_storeWeak将对象的地址作为key,将__weak修饰的变量地址作为value注册到weak表(一个哈希表,跟引用计数表相同)。

释放对象的步骤:

  1. objc_release
  2. 因为引用计数为0, 所以执行dealloc
  3. _objc_rootDealloc
  4. object_dispose
  5. objc_destructInstance
  6. objc_clear_deallocating

在最后一个步骤中,会执行以下动作:

  1. 从weak表中获取废弃对象的地址为key的记录
  2. 将包含在记录中的所有__weak修饰的变量地址赋值为nil
  3. 从weak表中删除
  4. 从引用计数表中删除废弃对象地址为key的记录

由上可知,如果大量使用__weak修饰的变量的话,会消耗相应的CPU资源

下面验证下__weak修饰的变量即为自动释放池中的对象

{
  id __weak obj1 = obj;
  NSLog(@"%@", obj1);
}

// 编译器模拟代码
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1); // 取出__weak修饰符变量所引用的对象并retain
objc_autorelease(tmp); // 将对象注册到自动释放池
NSLog(@"%@", tmp);
objc_destoryWeak(&obj1);
复制代码
__autoreleasing
@autoreleasepool {
  id __autoreleasing obj = [[NSObject alloc] init];
}

// 编译器模拟代码
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
复制代码
引用计数

使用_objc_rootRetainCount()方法来获取引用计数的数值(CFGetRetainCount((__bridge CFTypeRef)(obj))也可) 使用_objc_autoreleasePoolPrint()可打印自动释放池中的引用对象的状态:

// 需要通过extern声明
extern void _objc_autoreleasePoolPrint();
extern uintptr_t _objc_rootRetainCount(id obj);

int main(int argc, char * argv[]) {
    
    id obj = [[NSObject alloc] init];
    id obj1 = obj;
    NSLog(@"%ld", _objc_rootRetainCount(obj));
    
    @autoreleasepool {
        id obj = [[NSObject alloc] init];
        _objc_autoreleasePoolPrint();
        id __weak o = obj;
        NSLog(@"before using __weak: retain count = %d", _objc_rootRetainCount(obj));
        NSLog(@"class = %@", [o class]);
        NSLog(@"after using __weak: retain count = %d", _objc_rootRetainCount(obj));
        _objc_autoreleasePoolPrint();
    }
    return 0;
}
复制代码

输出如下:

2017-05-03 00:18:20.415 ObjectiveCDemo[7740:740389] 2
objc[7740]: ##############
objc[7740]: AUTORELEASE POOLS for thread 0x1108bc3c0
objc[7740]: 0 releases pending.
objc[7740]: [0x1]  ................  PAGE (placeholder)
objc[7740]: [0x1]  ################  POOL (placeholder)
objc[7740]: ##############
2017-05-03 00:18:20.417 ObjectiveCDemo[7740:740389] before using __weak: retain count = 1
2017-05-03 00:18:20.417 ObjectiveCDemo[7740:740389] class = NSObject
2017-05-03 00:18:20.418 ObjectiveCDemo[7740:740389] after using __weak: retain count = 1
objc[7740]: ##############
objc[7740]: AUTORELEASE POOLS for thread 0x1108bc3c0
objc[7740]: 1 releases pending.
objc[7740]: [0x7f82e2003000]  ................  PAGE  (hot) (cold)
objc[7740]: [0x7f82e2003038]  ################  POOL 0x7f82e2003038
objc[7740]: ##############

复制代码

转载于:https://juejin.im/post/5a30e2846fb9a0451a765ffe

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值