774 字
4 分钟
深入探究属性
@interface RYModel : NSObject
@property (nonatomic, copy) NSString *name;@property (nonatomic, assign) NSInteger age;@property (nonatomic, assign) float height;@property (nonatomic, strong) RYModel *father;@property (nonatomic, assign) BOOL isBoy;
- (void)dosomething;
- (void)dosomethingWith:(NSString *)title;
+ (void)classDoSomething;
- (NSString *)sayMyName;
@end一、成员变量和属性
我们知道,属性会生成带下划线的成员变量和对应的set、get方法。我们将上面用到的RYModel,编译成C++看下。
static NSString * _I_RYModel_name(RYModel * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_RYModel$_name)); }extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_RYModel_setName_(RYModel * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct RYModel, _name), (id)name, 0, 1); }
static NSInteger _I_RYModel_age(RYModel * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_RYModel$_age)); }static void _I_RYModel_setAge_(RYModel * self, SEL _cmd, NSInteger age) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_RYModel$_age)) = age; }
static float _I_RYModel_height(RYModel * self, SEL _cmd) { return (*(float *)((char *)self + OBJC_IVAR_$_RYModel$_height)); }static void _I_RYModel_setHeight_(RYModel * self, SEL _cmd, float height) { (*(float *)((char *)self + OBJC_IVAR_$_RYModel$_height)) = height; }
static RYModel * _I_RYModel_father(RYModel * self, SEL _cmd) { return (*(RYModel **)((char *)self + OBJC_IVAR_$_RYModel$_father)); }static void _I_RYModel_setFather_(RYModel * self, SEL _cmd, RYModel *father) { (*(RYModel **)((char *)self + OBJC_IVAR_$_RYModel$_father)) = father; }
static BOOL _I_RYModel_isBoy(RYModel * self, SEL _cmd) { return (*(BOOL *)((char *)self + OBJC_IVAR_$_RYModel$_isBoy)); }static void _I_RYModel_setIsBoy_(RYModel * self, SEL _cmd, BOOL isBoy) { (*(BOOL *)((char *)self + OBJC_IVAR_$_RYModel$_isBoy)) = isBoy; }8.1 copy 修饰符
name 属性用的是 copy 来修饰的。很容易发现它的set方法明显有不同。
他用的是 objc_setProperty ,而其他的都是直接通过内存平移对成员变量进行赋值操作。
二、objc_setProperty
我们看下 objc_setProperty 的源码:
/// 对象 sel 偏移量 值 原子性 是否copyvoid objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy){ // 是否是 Copy bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY); // 是否是 可变的Copy bool mutableCopy = (shouldCopy == MUTABLE_COPY); reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);}发现他实际调用了 reallySetProperty
2.1 reallySetProperty
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy){ if (offset == 0) { // 修改 ISA object_setClass(self, newValue); return; }
id oldValue; // 定位(首地址+偏移量) id *slot = (id*) ((char*)self + offset);
// Copy相关操作 if (copy) { newValue = [newValue copyWithZone:nil]; } else if (mutableCopy) { newValue = [newValue mutableCopyWithZone:nil]; } else { if (*slot == newValue) return;// 相同的话不做操作 // 引用计数+1 newValue = objc_retain(newValue); }
// 原子性相关 if (!atomic) { oldValue = *slot; *slot = newValue; } else { // 加锁 spinlock_t& slotlock = PropertyLocks[slot]; slotlock.lock(); oldValue = *slot; *slot = newValue; slotlock.unlock(); }
// release 旧值 引用计数 -1 objc_release(oldValue);}三、 Set 流程图

四、Type Encodings 类型编码
我们会看到这样一些 objc_selector,其中有 v24@0:8@16 类似这样的一些看不懂的字符串。他们叫类型编码
具体可以对照官方文档这里就不展开聊了
以 setName 为例分析一次:
| v | 24 | @ | 0 | : | 8 | @ | 16 |
|---|---|---|---|---|---|---|---|
| 返回值类型-void | 占用大小-24 | 参数-id | 从偏移量0开始 | SEL | 从偏移量8开始 | 参数-id | 从偏移量16开始 |
static struct /*_method_list_t*/ { unsigned int entsize; // sizeof(struct _objc_method) unsigned int method_count; struct _objc_method method_list[21];} _OBJC_$_INSTANCE_METHODS_RYModel __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 21, {{(struct objc_selector *)"ry_protocolDoSomething", "v16@0:8", (void *)_I_RYModel_ry_protocolDoSomething}, {(struct objc_selector *)"name", "@16@0:8", (void *)_I_RYModel_name}, {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_RYModel_setName_}, {(struct objc_selector *)"age", "q16@0:8", (void *)_I_RYModel_age}, {(struct objc_selector *)"setAge:", "v24@0:8q16", (void *)_I_RYModel_setAge_}, {(struct objc_selector *)"height", "f16@0:8", (void *)_I_RYModel_height}, {(struct objc_selector *)"setHeight:", "v20@0:8f16", (void *)_I_RYModel_setHeight_}, {(struct objc_selector *)"father", "@16@0:8", (void *)_I_RYModel_father}, {(struct objc_selector *)"setFather:", "v24@0:8@16", (void *)_I_RYModel_setFather_}, {(struct objc_selector *)"isBoy", "c16@0:8", (void *)_I_RYModel_isBoy}, {(struct objc_selector *)"setIsBoy:", "v20@0:8c16", (void *)_I_RYModel_setIsBoy_}, ... {(struct objc_selector *)"isBoy", "c16@0:8", (void *)_I_RYModel_isBoy}, {(struct objc_selector *)"setIsBoy:", "v20@0:8c16", (void *)_I_RYModel_setIsBoy_}}};