当前位置: 首页 > news >正文

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

文章目录

    • 前言
    • strong和copy的区别
      • 为什么要用copy?
      • 什么时候用什么修饰?
    • strong(ARC自动管理)
      • strong修饰变量的底层流程图
      • 底层代码核心实现
      • 小结
    • copy
      • 底层流程图
      • 对比与strong的关键不同之处
      • 内部调用关系(伪代码)
      • 小结
    • OC中的属性关键字
      • 原子性(Atomicity):控制操作的原子性
        • atomic(原子性,默认值)
        • nonatomic(非原子性)
        • 小结
      • 读写权限:控制属性的访问方式
        • readwrite(可读可写,默认值)
        • readonly(只读)
        • 小结
      • 内存管理:控制对象的内存生命周期
        • strong(强引用,默认值)
        • weak(弱引用)
        • copy(拷贝)
        • assign(直接赋值)
        • 小结
      • 空值约束(Nullability):标记属性是否允许为 nil
        • nonnull(非空)
        • nullable(可空)
        • null_unspecified(未指定)
        • 集合宏(简化声明)
        • 小结
    • 总结

前言

  我们编写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(防御性编程)

什么时候用什么修饰?

类型建议修饰符
NSStringcopy
NSMutableStringcopy
NSArray/NSDictionarycopy
自定义对象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的关键不同之处

行为strongcopy
是否复制对象❌ 不复制,直接引用原对象✅ 调用 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
避免循环引用(如 delegatenonatomic, weak
基本数据类型/结构体nonatomic, assign
接口只读,内部可写.hreadonly.mreadwrite
强制非空nonatomic, strong, nonnull
http://www.lryc.cn/news/608121.html

相关文章:

  • 电脑手机热点方式通信(下)
  • 「iOS」————weak底层原理
  • 「iOS」————SideTable
  • JAVA国际版同城服务同城信息同城任务发布平台APP源码Android + IOS
  • Ajax——异步前后端交互提升OA系统性能体验
  • Dice Combinations(Dynamic Programming)
  • 8.2 状态机|贪心|dfs_dp
  • Linux初步认识与指令与权限
  • 机器学习——K 折交叉验证(K-Fold Cross Validation),实战案例:寻找逻辑回归最佳惩罚因子C
  • Jotai:React轻量级原子化状态管理,告别重渲染困扰
  • React ahooks——副作用类hooks之useThrottleFn
  • react 和 react native 的开发过程区别
  • Javascript面试题及详细答案150道之(016-030)
  • 【REACT18.x】使用vite创建的项目无法启动,报错TypeError: crypto.hash is not a function解决方法
  • NEXT.js 打包部署到服务器
  • OLTP,OLAP,HTAP是什么,数据库该怎么选
  • React ahooks——副作用类hooks之useThrottleEffect
  • 超平面(Hyperplane)是什么?
  • 深入 Go 底层原理(十四):timer 的实现与高性能定时器
  • 卡尔曼滤波轨迹跟踪算法与MATLAB实现
  • 关于Web前端安全防御XSS攻防的几点考虑
  • 【软考中级网络工程师】知识点之 VRRP
  • 智能学号抽取系统V5.6.4重磅发布
  • 【Docker】RK3576-Debian上使用Docker安装Ubuntu22.04+ROS2
  • 28Rsync免密传输与定时备份
  • 【学习笔记】MySQL技术内幕InnoDB存储引擎——第9章 性能调优
  • leetcode热题——组合
  • Android性能优化--16K对齐深入解析及适配指南
  • 【数据结构初阶】--排序(二)--直接选择排序,堆排序
  • AI Agent开发学习系列 - LangGraph(10): 带有循环的Looping Graph(练习解答)