深入理解 Objective-C 属性内存管理语义(assign、weak、copy、strong)

1,284 阅读4分钟

深入理解 Objective-C 属性内存管理语义

一文彻底搞懂 assign、weak、copy、strong 这些内存管理语义的用法与原理

举个例子:

//
// Dog.h
// oc-demo
//
// Created by 姚明振 on 2022/8/17.
//#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface Dog : NSObject@property(nonatomicassignnullableNSObject *assignProperty;
@property(nonatomicweaknullableNSObject *weakProperty;
@property(nonatomiccopynullableNSString *propertyCopy;
@property(nonatomicstrongnullableNSObject *strongProperty;
@endNS_ASSUME_NONNULL_END

assign: image.png 想要它不 crash 其实是有办法的,我们在它所指向对象被销毁时手动帮它为属性赋 nil:image.png当然这没有什么实际意义,不能实现我们预期的目的。

原因是 alloc、init 方法返回的是一个 autorelease 的 NSObject 实例,当走完 init 后如果没有人对他进行 retain 操作,则立马会被销毁。

image.png让临时变量持有该 NSObject 实例,即在作用域内被强引用着,所以 assign 修饰的属性在这种情况下也能正常访问指向的对象。例子只为让大家易于理解,千万不要在项目里这么用。

总结:

  • 只进行简单的赋值操作(把一个指针赋值给该属性,如果是对象则是对象地址,如果是基本数据类型则是该值的地址)
  • 只适用于基本数据类型(非 Objective-C 对象「语法上是支持修饰 Objective-C 对象的」,如:int,float,NSInterge,CGFloat,及结构体类型)
  • 不影响该属性所指向对象的生命周期(不改变retaincount)
  • 在所指向对象销毁时不会自动为该属性赋 nil 值

weak:

参考 assign,做个对比

image.png 我们发现跟 assign 不同的是,这里没有发生 crash,原因是 weak 修饰的属性在所指向对象销毁时自动为该属性赋 nil 处理。image.png使用临时变量强引用 NSObject,此时能正常访问该属性指向的对象。

总结:

  • 只适用于 Objective-C 对象(语法上就不支持修饰基本数据类型)
  • 不影响该属性所指向对象的生命周期(不改变retaincount)
  • 在所指向对象销毁时会自动为该属性赋 nil 值(防止野指针访问导致 crash)

strong:

image.png可以看到属性值正常输出字符串 "123",说明对字符串进行了强引用。image.png不过当我们赋值 NSMutableString 实例时,发现这违背了该属性的设计意图。明明是不可变的字符串,刚赋值为123,讲道理在不走 setter 方法时它的值是不允许改变的,可这种事真的发生了,它被修改为 123456。于是引入了 copy 来解决这个问题(下面介绍)。

总结:

  • 只适用于 Objective-C 对象(语法上就不支持修饰基本数据类型)

  • 会强引用指向的对象(retaincount + 1)

  • 会释放之前指向的对象(retaincount - 1)

  • 当修饰 block 类型属性时(ARC 下表现与 copy 一致)

    • 当指向的对象为 StackBlock 时会把 block 对象复制到堆区成为 MallocBlock
    • 当指向的对象为 MallocBlock 时会增加引用计数(retaincount + 1)
    • 当指向的对象为 GlobalBlock 时什么都不会发生

copy:

当用 copy 修饰一个 NSObject 类型的属性,运行时发生了 crash,并抛出 NSObject 未实现 copyWithZone: 方法的异常。这是因为 copy 修饰的属性会在 setter 方法内对老值进行 release,对新值进行 copy 操作,也就是它真正指向的是传入对象 copy 后的返回值(可能是深 copy 也可能是浅 copy )。这一操作避免了给不可变类型(如:NSString)属性赋值对应可变子类(如:NSMutableString)导致的逻辑异常。image.png把属性类型改为 NSString(它实现了copyWithZone: 方法)。

image.png这里打印了字符串 “123”,说明了 copy 修饰的属性会对它指向的值进行强引用。

另一方面当为该属性赋值后,继续修改原值时该属性的值不受影响,因为此时已经把 NSMutableString copy 为了 NSString。

总结:

  • 只适用于 Objective-C 对象(语法上就不支持修饰基本数据类型)

  • 会强引用指向的对象(retaincount + 1)

  • 会释放之前指向的对象(retaincount - 1)

  • 当修饰 NSString,NSArray 等存在可变类型的子类时(这是 copy 与 strong 的最大区别)

    • 使用 copy 修饰,把可变类型深 copy 为不可变类型,避免不安全修改
    • 不安全的原因是:可变类型的对象可以在不改变对象地址的情况下修改值,因为你的属性是 NSString 类型,此时值被意外修改是不符合预期的。
  • 当修饰 block 类型属性时

    • 当指向的对象为 StackBlock 时会把block对象复制到堆区成为 MallocBlock
    • 当指向的对象为 MallocBlock 时会增加引用计数(retaincount + 1)
    • 当指向的对象为 GlobalBlock 时什么都不会发生

总结

今天开始复习,决定把自己的理解以文章的形式输出,一方面能加深理解做个笔记,另一方面也希望能给大家带来点帮助。笔者水平有限,难免有理解偏差与遗漏,希望大家批评指正。