【iOS】strong和copy工作流程探寻、OC属性关键字复习

发布于:2025-08-03 ⋅ 阅读:(10) ⋅ 点赞:(0)

前言

  我们编写OC代码在.h文件里声明变量时会用到strong和copy这两个修饰符,那么这两者到底有什么区别,什么时候该用strong,什么时候该用copy?今天我们通过这两者的底层实现逻辑来探究一下这里面的奥秘。

strong和copy的区别

首先,先看一下这段测试代码:
请添加图片描述
请添加图片描述

请添加图片描述

代码运行结果如下:

请添加图片描述

  从上述结果我们可以看出来,copy修饰的字符串strCopy的值没有随着newStr的改变而改变,而strong修饰的字符串strStrong因为newStr的修改而随着改变了。

  在我们前面学习的过程中,我们知道copy修饰的变量,编译器会自动生成自定义的 setter方法,其核心逻辑是:==赋值时不直接保留原对象,而是先调用对象的 copy方法生成副本,再保留该副本。==也就是说,copy修饰的变量对象地址是一个新的地址,这个指针变量指向的是一个新的内存区域(相当于深拷贝),所以修改原本的newStr不会对其产生影响。

  所以,使用 copy和 strong修饰变量,在 OC中的最大区别在于 赋值时是否进行对象拷贝(复制内存)。这在处理==可变对象(如 NSMutableString)==时尤为关键。

关键字 作用
strong 引用传递:只增加引用计数,指向原对象
copy 拷贝传递:生成副本,指向新对象

为什么要用copy?

防止外部可变对象的改变影响内部状态

保证对象的不可变性,尤其是对 NSString、NSArray、NSDictionary 等常用于声明为不可变对象的属性

推荐使用 copy 修饰 NSString / NSArray / NSDictionary(防御性编程)

什么时候用什么修饰?

类型 建议修饰符
NSString copy
NSMutableString copy
NSArray/NSDictionary copy
自定义对象 strong(实现copy时才用copy)

strong(ARC自动管理)

  使用 strong修饰的变量,在 ARC(Automatic Reference Counting)下,其底层原理和逻辑可以归结为对 objc_storeStrong() 函数的封装,它等价于:

id oldValue = _ivar;
_ivar = [newValue retain];   // 引用计数 +1
[oldValue release];         // 引用计数 -1

strong修饰变量的底层流程图

因为strong在底层是对objc_storeStrong()函数的封装,所以其底层流程图其实就等效 objc_storeStrong()

                  +----------------------+
                  |   objc_storeStrong   |
                  +----------------------+
                              |
                  +----------------------+
                  | 检查目标地址是否为 nil |
                  +----------------------+
                              |
                              v
                  +----------------------+
                  | old = *object        |
                  | *object = value      |
                  +----------------------+
                              |
           +-----------------------------------+
           | 如果新旧对象不同:                 |
           | - retain(value) → 引用计数 +1     |
           | - release(old) → 引用计数 -1      |
           +-----------------------------------+
                              |
                              v
                   +--------------------+
                   | 引用更新完成返回    |
                   +--------------------+

底层代码核心实现

在 Apple Runtime 源码中,objc_storeStrong 类似于这样实现(简化版伪代码):

void objc_storeStrong(id *object, id value) {
    id old = *object;
    if (old != value) {
        objc_retain(value);   // 引用计数 +1
        *object = value;
        objc_release(old);    // 引用计数 -1
    }
}

小结

步骤 动作
新值 retain 增加新对象引用计数,确保它不会被过早释放
旧值 release 减少旧对象引用计数,若为0则销毁
设置 _ivar 将指针指向新的对象地址
安全性 ARC 生成的代码是线程安全的,并避免循环引用等问题

copy

  当我们使用 copy修饰属性时,其背后的行为与 strong非常不同,关键在于对象赋值时会生成副本(调用 copy 方法),而不是直接引用原对象

当你使用 copy 修饰属性时,其背后的行为与 strong 非常不同,关键在于 对象赋值时会生成副本(调用 copy 方法),而不是直接引用原对象

底层流程图

使用copy修饰符在底层的调用逻辑等价于 objc_storeStrong(&ivar, [value copy])):

              +----------------------+
              |   objc_storeStrong   |
              +----------------------+
                          ^
                          |
              +----------------------+
              |     调用 [value copy] |
              +----------------------+
                          |
              +-----------------------------+
              | copy 调用 -copyWithZone:     |
              |  -> 生成新的对象             |
              |  -> 返回新对象引用           |
              +-----------------------------+
                          |
              +-----------------------------+
              | retain(copyValue)           |
              | release(oldValue)           |
              +-----------------------------+
                          |
              +----------------------+
              | ivar 指向新副本对象  |
              +----------------------+

对比与strong的关键不同之处

行为 strong copy
是否复制对象 ❌ 不复制,直接引用原对象 ✅ 调用 copy 创建新对象
内部处理方式 objc_storeStrong(&ivar, value) objc_storeStrong(&ivar, [value copy])
所需协议支持 无需 对象需实现 NSCopying 协议
适用场景 一般对象引用 NSString / NSArray / 自定义不可变类等
对可变对象是否防御 ❌ 外部修改影响内部 ✅ 内部得到的是独立副本,不受外部影响

内部调用关系(伪代码)

// ARC 编译器生成代码
- (void)setName:(NSString *)name {
    id copyValue = [name copy];             // 关键步骤:生成副本
    objc_storeStrong(&_name, copyValue);    // 然后存储副本对象
}

小结

copy修饰的属性会在赋值时复制对象副本,而不是保留原始引用。

如果传入的是 NSMutableString,copy 后变成 NSString(不可变),从而防止外部修改影响内部状态

最终仍通过 objc_storeStrong 来管理引用计数,但传入的是 [x copy] 的返回值。

OC中的属性关键字

在 Objective-C 中,属性(Property)是封装对象状态的核心机制,通过 @property 声明并结合不同的属性关键字,可以精确控制属性的行为(如内存管理、访问权限、线程安全等)。

原子性(Atomicity):控制操作的原子性

原子性关键字用于修饰属性的 setter 和 getter 方法,决定其操作是否为“原子操作”(不可分割的操作)。

atomic(原子性,默认值)

保证 settergetter 操作的原子性(即操作要么完全执行,要么完全不执行),避免多线程并发访问时的数据不一致问题。

特点:

  • 线程安全,但不保证业务逻辑的绝对安全(例如,复合操作如 _count++ 仍需额外同步)。
  • 性能开销较大(因需要加锁/解锁机制)。
@property (atomic, assign) NSInteger count; // 默认 atomic,线程安全但性能一般
nonatomic(非原子性)

不保证 settergetter 的原子性,多线程并发访问时可能导致数据不一致。

特点:

  • 无锁机制,性能更高(适合高频读写的场景)。
  • 需开发者自行处理线程安全(如通过 @synchronized、GCD 队列等)。
@property (nonatomic, strong) NSString *name; // 非原子性,性能更优
小结
  • 优先选 nonatomic:大多数场景下(尤其是移动端),性能比绝对线程安全更重要。
  • 仅当选中 atomic:需要框架级线程安全(如系统底层库),或配合其他同步机制使用。

读写权限:控制属性的访问方式

读写权限关键字决定属性是否生成 setter 方法,从而控制属性的可写性。

readwrite(可读可写,默认值)

自动生成 setter(写方法)和 getter(读方法),属性可读可写。

@property (readwrite, copy) NSString *title; // 可读可写(默认)
readonly(只读)

仅生成 getter 方法,属性不可写(外部只能读取,不能直接修改)。

  • 常用于接口设计,隐藏内部实现细节(如通过类扩展在 .m 文件中重新声明为 readwrite,允许内部修改)。
// .h 文件(对外接口)
@interface User : NSObject
@property (readonly, copy) NSString *userId; // 外部只读
@end

// .m 文件(内部实现)
@interface User ()
@property (readwrite, copy) NSString *userId; // 内部可写
@end
小结
  • readwrite:常规可修改属性。
  • readonly:常用于封装(外部只读,内部可写)。

内存管理:控制对象的内存生命周期

内存管理关键字用于告诉编译器如何管理属性所引用对象的内存(仅适用于对象类型,基本数据类型如 intCGFloat 不适用)。

strong(强引用,默认值)

属性对对象持有强引用(增加对象的引用计数),确保对象在属性作用域内不会被释放。

  • 适用于大多数对象类型(如自定义对象、系统容器类)。

  • 循环引用风险:若两个对象相互 strong 引用,会导致内存泄漏(需配合 weak 打破)。

    @property (strong, nonatomic) NSArray *dataList; // 强引用数组,数组内对象也会被保留
    
weak(弱引用)

属性对对象持有弱引用(不增加引用计数),对象释放后,属性自动置为 nil(避免野指针)。

特点:

  • 解决循环引用(如 delegate 模式、视图控制器与子视图的相互引用)。
  • 无法持有对象(对象可能因无强引用被提前释放)。
// 视图控制器的 delegate 通常用 weak,避免循环引用
@property (weak, nonatomic) id<MyDelegate> delegate;
copy(拷贝)

赋值时调用对象的 copy 方法生成副本,属性持有副本的强引用(原对象不受后续修改影响)。

适用场景:

  • 不可变对象(如 NSStringNSArray):防止外部传入可变对象(如 NSMutableString)后被修改。
  • 需要“快照”的场景(如配置参数、缓存数据)。
@property (copy, nonatomic) NSString *username; // 外部传入 NSMutableString 会被拷贝为 NSString
assign(直接赋值)

直接赋值(不涉及引用计数管理),适用于基本数据类型非对象类型(如 intCGFloat、指针)。

特点:对对象类型使用 assign 会导致野指针(对象释放后属性仍指向无效内存)。

@property (assign, nonatomic) NSInteger age; // 基本数据类型用 assign
@property (assign, nonatomic) CGPoint position; // 结构体用 assign
小结
关键字 适用类型 内存行为 典型场景 注意事项
strong 对象 强引用(+1 RC) 常规对象属性 避免循环引用
weak 对象 弱引用(不+RC,释放后置nil) delegate、IBOutlets 仅适用于对象
copy 对象(需实现 NSCopying 生成副本并强引用 字符串、集合、防止外部修改 不可变对象 copy 是浅拷贝
assign 基本类型/非对象 直接赋值(无引用计数) 数值、结构体 对象类型会导致野指针

空值约束(Nullability):标记属性是否允许为 nil

空值约束关键字用于明确属性是否允许为 nil,帮助编译器进行静态检查,减少运行时崩溃(需 Xcode 7+ 支持)。

nonnull(非空)

属性必须非空(不能为 nil),编译器会对可能的 nil赋值发出警告。

@property (nonatomic, copy, nonnull) NSString *userId; // 必须赋值非空字符串
nullable(可空)

属性可以为空(允许为 nil),无编译器警告。

@property (nonatomic, strong, nullable) UIImage *avatar; // 头像可能为空
null_unspecified(未指定)

属性是否为空未明确声明(编译器不做强制检查),用于兼容旧代码或无法确定的情况。

@property (nonatomic, copy, null_unspecified) NSString *legacyData; // 未指定是否可空
集合宏(简化声明)

为避免重复写 nonnull/nullable,可使用以下宏包裹属性列表:

  • NS_ASSUME_NONNULL_BEGIN/NS_ASSUME_NONNULL_END:区间内默认 nonnull,显式 nullable除外。

  • 示例:

    NS_ASSUME_NONNULL_BEGIN
    @interface User : NSObject
    @property (nonatomic, copy) NSString *name; // 默认 nonnull
    @property (nonatomic, strong, nullable) NSNumber *age; // 显式 nullable
    @end
    NS_ASSUME_NONNULL_END
    
小结
  • nonnull:强制属性非空,提升代码健壮性。
  • nullable:明确属性可空,避免无意义检查。
  • null_unspecified:兼容过渡场景。

总结

场景 推荐关键字组合
常规对象属性(如自定义模型) nonatomic, strong
字符串/集合(防外部修改) nonatomic, copy
避免循环引用(如 delegate nonatomic, weak
基本数据类型/结构体 nonatomic, assign
接口只读,内部可写 .hreadonly.mreadwrite
强制非空 nonatomic, strong, nonnull

网站公告

今日签到

点亮在社区的每一天
去签到