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 流程图

四、Type Encodings 类型编码

我们会看到这样一些 objc_selector,其中有 v24@0:8@16 类似这样的一些看不懂的字符串。他们叫类型编码

具体可以对照官方文档这里就不展开聊了

setName 为例分析一次:

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

Last updated