Objc Tips
总结记录 Objective-C 使用过程中一些 Tips。
NS_ENUM 和 NS_OPTIONS
1 | typedef NS_ENUM(NSInteger, UIViewAnimationCurve) { |
1 | typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) { |
NS_OPTIONS
可以同时选择多个枚举值,使用了移位运算来保证相加结果的唯一性。简而言之,NS_ENUM
在互斥环境下使用;NS_OPTIONS
在多选情况下使用。
简介接口设计模板
1 | typedef NS_ENUM(NSInteger, UserSex) { |
weak 和 assign 对比
什么情况下使用 weak?
- 用来避免循环引用
- OBOutlet 默认为 weak
与 assign 的区别
- weak 为该属性定义了一种非拥有关系,不影响引用计数,在属性所指对象销毁时,属性值也会自动置为 nil
- assign 用来修饰基础类型变量(如 CGFloat、NSInteger)
- assign 可用于非 OC 对象,weak 必须用于 OC 对象
weak 的实现原理
weak 有两种作用:
- 被 weak 修饰符修饰的弱引用除了不会增加对象的引用计数外
- 在引用对象被释放后,这个弱引用会自动失效并置为 nil
实现原理
- 源码入口 objc_initWeak()
- 查看对象是否有效,无效置空
- 调用 storeWeak
- storeWeak
- 核心方法
weak_unregister_no_lock
和weak_register_no_lock
- 都是对 SideTable 的实例进行操作
- 核心方法
- SideTable
- 内含有
weak_table_t
,是 oc 中 weak 的核心数据结构 - weak 表通过哈希表实现
- 通过目标对象的地址作为 key 检索得到对应弱引用变量地址
- 需要注意:一个 key 可能对应多个弱引用变量地址,放存放在
weak_entry
- 内含有
- weak_register_no_lock
- 先校验是否满足校验条件(计数方式、是否在析构,是否能弱引用)
- 通过弱引用对象的地址,在
weak_table
查找weak_entry
- 找到,在
weak_entry
中的referrers
添加新的弱引用地址 - 若未找到,新建
weak_entry
并添加 - 数组中
referrers
起始为静态数组,如果在操作过程中发现静态数组空间不够用切换为动态数组,如果动态数组超过总空间 3/4,扩容一倍
- 找到,在
- weak_unregister_no_lock
- 根据对象地址,找出
weak_entry
- 删除
weak_entry
中的弱引用地址 - 如果最后发现
weak_entry
空了,从 weak_table 移除
- 根据对象地址,找出
- 自动置为 nil
- 底层触发
clearDeallocating
方法 - 先校验对象是否满足弱引用 dealloc
- 对象 dealloc 时,通过 this 指针(对象指针),找到 SideTable。再通过对象地址在 SideTable 上找到所有的弱引用指针,逐个置 nil
- 底层触发
atomic 和 nonatomic
atomic 属性使用了互斥锁。一般情况下不适用 atomic,因为并不能保证线程安全,若要实现线程安全。
如,一个线程在连续多次读取某属性值的过程中有别的线程在同时改写该值,则即使声明为 atomic,也还是会读到不同的属性值。
copy 和 mutableCopy
NSString、NSArray、NSDictionary 常用 copy。因为各自对应了可变类型 NSMutableString、NSMutableArray、NSMutableDictionary,防止内容在不知情的情况下被更改。所以使用 copy 来复制一份不可变的。
对 NSMutableArray 使用 copy
添加、删除、修改组内的元素时,程序会因为找不到对应的方法而崩溃,原因是 copy 复制了一份不可变的 NSArray 对象。错误代码如下:
1 | @property (nonatomic, copy) NSMutableArray *mutableArray; |
strong 修饰 NSArray 时
1 | @property (nonatomic, strong) NSArray *array; |
打印结果:
1 | 2016-04-03 00:10:10.765 DIYArrayCopyDemo[10681:713670] ( |
所以 strong 的问题很明显,数据存在被篡改风险。
对象拷贝
- 声明该类遵循 NSCopying 协议
- 实现 NSCopying 协议方法
-copyWithZone:
1 | - (id)copyWithZone:(NSZone *)zone { |
集合/非集合类对象 copy
非集合类对象:
- 不可变对象:
- copy:指针拷贝
- mutableCopy:内容拷贝
- 可变对象:
- copy:内容拷贝
- mutableCopy:内容拷贝
集合类对象:NSArray、NSDictionary、NSSet 等
- 不可变对象:
- copy:指针拷贝
- mutableCopy:单层深拷贝。(如对 NSArray,是拷贝 array 这个对象,但 Array 内部元素任是指针拷贝)
- 可变对象:
- copy:单层深拷贝
- mutableCopy:单层深拷贝
1 | [array copy] //浅拷贝 |
@property
@property = ivar + getter + setter,即属性 = 实例变量 + 存取方法。
完成属性定义后,编译器在编译期会自动编写访问这些属性所需要的方法,即“自动合成”。除了生成 getter、setter 外,编译器还会向类中添加适当类型的实例变量,名称为 _propertyName
。我们也可以通过 @synthesize
来指定实例变量的名称。
@property 大致的实现过程
属性的源码实现结构:
- 偏移量
- setter、getter 实现函数
- ivar_list:成员变量列表
- method_list:方法列表
- prop_list:属性列表
实现过程:
- 每增加一个属性,系统就会在 ivar_list 中添加一个成员变量;在 method_list 中添加对应的 setter 和 getter 方法;在 prop_list 中添加一个属性。
- 之后计算该属性在对象中的偏移量,给出 setter、getter 方法对应的实现。setter 方法中从偏移量位置开始赋值;getter 方法中从偏移量开始取值。
@protocol 和 category 如何使用 @property
protocol 中使用 property 只会生成 setter 和 getter 方法声明。我们使用属性的目的是,希望遵循协议的对象能实现该属性。
category 使用 @property 只会生成 setter 和 getter 方法的声明。如果我们需要给 category 增加属性的实现,需要借助 runtime 函数:
- objc_setAssociatedObject
- objc_getAssociatedObject
@property 的修饰符归类
- 原子性
- atomic
- nonatomic
- 读写权限
- readwrite
- readonly
- 内存管理语义
- assign
- strong
- weak
- unsafe_unretained
- copy
- 方法名
- getter=
,如 getter=isOn - setter=
- getter=
- nullable
- nonnull
- null_resettable
- nullable
- ARC 下不显示关键字是,默认为:
- 基本类型
- atomic
- readwrite
- assign
- 普通 OC 对象
- atomic
- readwrite
- strong
- 基本类型
@synthesize 和 @dynamic
@property
如果都没写,默认为:@synthesize = _var;
@systhesize
语义是,默认让编译器加上 setter 和 getter 方法@dynamic
(动态绑定)作用:告诉编译器,属性的 setter、getter 方法不自动生成
什么时候不会 autosynthesis?
- 同时重写了 setter 和 getter
- 重写了只读属性的 getter
- 使用了 @dynamic
- @protocol 中定义的所有属性
- category 中定义的所有属性
- 重写的属性
注意点:
- 若子类重写了父类属性,必须用 @synthesize 来手动合成 ivar