Links

07.深入探究属性

@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 偏移量 值 原子性 是否copy
void 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 流程图

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_}}
};