iOS之内存管理

Tagged Pointer

  • bit64之后引入,针对小对象(NSString、NSNumber、NSDate等)的存储
  • 使用前:
    • NSNumber等对象需要动态分配内存,维护引用计数,NSNumber指针存储的是堆中的NSNumber对象的地址值
  • 使用后:
    • NSNumber指针里面存储的数据变成了Tag + Data,将数据直接存储在了指针中
    • 当指针不够存储数据时,才会使用动态分配内存的方式来存储数据(堆空间)
  • 判断是否是Tagged Pointer:
    • 对于Mac平台,指针的二进制最低有效位是1时,该指针为Tagged Pointer
    • 对于iOS平台,指针的二进制最高有效位是1时,该指针为Tagged Pointer
  • objc_msgSend能识别Tagged Pointer,直接从指针中提取数据,节省了调用开销(不必在类对象的方法列表中查找方法)
  • 优点:存储数据节省了内存开销,提取数据节省了调用开销

内存管理策略

MRC:assign、copy、retain

ARC:assign、copy、strong、weak

  • assign

    • 适用于基本数据类型
    • 实现方式是直接赋值
    1
    2
    3
    4
    5
    @property(nonatomic, assgin) int age;
    // MRC下或ARC编译后的实现
    -(void)setAge:(int)newAge {
    _age = newAge;
    }
  • copy

    • 适用于blockNSStringNSArrayNSDictionaryNSSet等Foundation框架的不可变类型。
    • 实现方式是对源对象进行一份浅拷贝(指针拷贝),指向源对象,所以源对象的引用计数会+1
    1
    2
    3
    4
    5
    6
    7
    8
    @property(nonatomic, copy) NSArray *array;
    // MRC下或ARC编译后的实现
    -(void)setArray:(NSArray *)newArray {
    if (_array != newArray) {
    [_array release];
    _array = [newArray copy];
    }
    }
  • retain/strong

    • 适用于对象类型和NSMutableStringNSMutableArrayNSMutableDictionaryNSMutableSet等Foundation框架的可变类型。
    • 实现方式是对源对象进行retain,源对象的引用计数+1
    1
    2
    3
    4
    5
    6
    7
    @property(nonatomic, retain) NSMutableArray *array;
    // MRC下或ARC编译后的实现
    -(void)setArray:(NSMutableArray *)newArray {
    if (_array != newArray) {
    [_array release];
    _array = [newArray retain];
    }

    可以看到,copyretain的实现上就是对源对象进行copy还是retain操作的差别。

    上述如果没有新旧对象是否相同的判断,多次调用setter传入同一个源对象时,可能会造成源对象retainCount变成0,导致对源对象retain报错。比如:

    MRC下,ZZPerson类中有个_dog成员和setDog方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // ZZPerson拥有dog属性
    @interface ZZPerson: NSObject
    {
    ZZDog *_dog;
    }
    -(void)setDog(ZZDog *)dog;
    @end

    @implementation ZZPerson()
    -(void)setDog:(ZZDog *)dog {
    [_dog release];
    _dog = [dog retain];
    }
    -(void)dealloc {
    [_dog release];
    [super dealloc];
    }
    @end

    外部调用时:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    {
    ZZDog *dog = [[ZZDog alloc] init];
    ZZPerson *person = [[ZZPerson alloc] init];
    [person setDog: dog]; // dog引用计数为2
    [dog release]; // dog引用计数为1(person引用)

    [person setDog: dog]; // 这里set时先release再retain,会出错
    [person setDog: dog];

    [person release];
    }

    上面第二次setDog方法调用拆解: [_dog release]: _dog指向dog,dog被release,引用计数变成0

    _dog = [dog retain]: dog已被释放,retain则会报错

  • weak:

    • 适用于delegate,以及一些需要弱引用的特殊场景,比如通过代理对象弱持有target来解决Timer循环引用、IBOutlet控件自身已经对该对象强引用而没必要再使用strong

    • 底层实现:

      runtime在类对象中维护了一个weak表,用于存储指向某个对象的所有weak指针。这个表是一个由单个自旋锁管理的哈希表;

      对象准备释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

  • __unsafe_unretained

    • 不安全
    • 当指向的对象被销毁后,指针不会自动置为nil,仍然指向原来那块内存,形成悬垂指针。
  • atomic

    • 可以保证属性的settergetter都是原子性操作,相当于settergetter内部加了线程同步的锁
    • 不能保证使用属性的过程是线程安全的

    比如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @property(atomic, strong) NSMuableArray *array;

    {
    self.array = [[NSMutableArray alloc] init];
    // 只能保证[self array]是线程安全的,但不能保证addObject是线程安全的
    [self.array addObject: @1];
    [self.array addObject: @2];
    [self.array addObject: @3];
    }

copy和mutableCopy

copy的目的:得到一份源对象的副本,源对象和副本互不影响;

深拷贝和浅拷贝:

  • 对不可变的源对象:copy时是浅拷贝(指针拷贝),mutableCopy是深拷贝;
  • 对可变对象:copy和mutableCopy都是深拷贝;

拷贝结果:

  • copy的结果都是不可变副本对象;
  • mutableCopy的结果都是可变副本对象。