阅读 336

小码哥iOS学习笔记第二十五天: OC对象的内存管理

一、引用计数

  • 在iOS中,使用引用计数来管理OC对象的内存
    • 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
    • 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
  • 内存管理的经验总结
    • 当调用allocnewcopymutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
    • 想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1

MRC下的setter方法

  • MRC下, 对象需要持有成员变量, 当销毁时释放成员变量
@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;

@end

@implementation Person

- (void)setName:(NSString *)name {
    if (_name != name) {
        [_name release];
        _name = [name retain];
    }
}

- (void)setAge:(int)age {
    _age = age;
}

- (void)dealloc {
    self.name = nil;
    [super dealloc];
}

@end
复制代码

二、copy和mutableCopy

  • 通过copymutableCopy, 可以生成一个副本, 与源代码分隔开, 两者之间互不干扰
  • mutableCopy为例, 有如下代码
  • 深拷贝: 产生一个新的副本, 与源对象相互独立
  • 浅拷贝: 指针拷贝, 指向源对象

自定义对象的拷贝

  • 自定义对象的拷贝需要实现NSCopying协议
#import <Foundation/Foundation.h>

@interface Person : NSObject <NSCopying>

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;

@end

@implementation Person

- (void)setName:(NSString *)name
{
    if (_name != name) {
        [_name release];
        _name = [name retain];
    }
}

- (void)setAge:(int)age
{
    _age = age;
}

- (id)copyWithZone:(nullable NSZone *)zone
{
    Person *person = [[Person allocWithZone:zone] init];
    person.age = self.age;
    person.name = self.name;
    return person;
}
@end
复制代码

三、引用计数的存储

  • 可以通过retainCount方法, 查看引用计数的存储
  • 通过runtime源码, 查看- (NSUInteger)retainCount方法, 如下图

  • 进入rootRetainCount函数, 可以看到引用计数的查找过程
    • 如果指针是Tagged Pointer, 那么直接返回, 否则进入下一步
    • 判断isa是否优化过, 如果优化过, 那么最后isa的最后19位存储的是引用计数
    • 如果最后19位不足以存储, 那么多余的引用计数会存储到sidetable中, 同时将倒数第20位的值置为1, 就是has_sidetable_rc的值为1
    • 如果has_sidetable_rc的值为1, 就会从sidetable_getExtraRC_nolock函数中取出sidetable中存储的引用计数

  • sidetable_getExtraRC_nolock中的代码如下图

  • 如果isa没有优化过, 那么就会进入sidetable_retainCount函数, 获取sidetable中的引用计数

  • Sidetable结构如下

  • 我们也可以从sidetable_retainsidetable_release函数中, 看到对引用技术的操作

四、weak的原理是什么?

  • 在程序中写入下面一段代码
__weak NSObject *obj;
NSLog(@"1");
{
    obj = [[NSObject alloc] init];
}
NSLog(@"2 - %@", obj);
复制代码
  • 可以看到, 当对象释放时, 被__weak修饰的指针会执行nil

  • 我们可以通过-dealloc的源码, 查看weak的实现过程
  • 下图是-dealloc方法的底层实现

  • 进入_objc_rootDealloc函数

  • 进入rootDealloc函数, 在这里可以看到两种情况
    • isa是优化过的指针, 对象没有被弱引用, 没有关联对象, 没有c++析构函数, 没有将引用计数存到Sidetable中, 就会立即释放
    • 否则调用object_dispose函数

  • 进入object_dispose函数, 可以看到调用了objc_destructInstance函数

  • 进入objc_destructInstance函数, 可以看到对objc的处理, 是在clearDeallocating函数中将弱指针置为nil的

  • 进入clearDeallocating函数, 又可以看到两种情况
    • 对象的isa没有优化过
    • 和优化过, 并且被弱指针引用 或者 将引用计数存放到了Sidetable

  • 当isa没有被优化过, 进入sidetable_clearDeallocating函数, 可以看到weak引用是存放到SideTable中的

  • 存放在了SideTableweak_table_t

  • 查看weak_table_t, 如下图, 即weak会被存放到一个全局的散列表中

  • 会通过weak_clear_no_lock函数, 对弱指针置为nil, 同时移除删列表中的weak记录

  • 如果isa被优化过, 并且对象被弱引用或者将引用计数存到Sidetable中, 就会调用clearDeallocating_slow函数

  • 进入clearDeallocating_slow函数, 可以看到在函数中, 调用了weak_clear_no_lock函数, 并清空了引用计数