属性和运行时

  1. @property 的本质是什么?
  2. @property 和运行时有什么关系?
  3. @property 一个 demo

# property 的本质是什么

@property 的本质就是成员变量加 gettersetter 方法。gettersetter 方法是编译器自动为我们生成的。我们也可以尝试去复写 gettersetter 方法,在设置成员变量前后增加一些我们想要的业务逻辑。

@property 有不同的 attribute : readonly,atomic,nonatomic 等等。编译器会根据不同的 attribute 生成不同的 gettersetter 方法

# property 和运行时有什么关系?

来看下运行时系统对 property 相关类型的定义

template <typename Element, typename List, uint32_t FlagMask>
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};

typedef struct property_t *objc_property_t;
struct property_t {
    const char *name;
    const char *attributes;
};

/// Defines a property attribute
typedef struct {
    const char *name;           /**< The name of the attribute */
    const char *value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

entsize_list_tt 是通过 C++ 的容器类,提供了容器类的基本方法,通过这些基本方法可以管理数据并以遍历的方式获取容器内的数据。property_list_t 是管理 property_t 的一个属性类型。

了解了基本数据类型之后,我们来讨论几个问题

# 类是怎么获取到 @property 的?

看源码及注释

objc_property_t class_getProperty(Class cls, const char *name)
{
    .......
    //依次遍历父类
    for ( ; cls; cls = cls->superclass) {
        //遍历当前类的属性列表
        for (auto& prop : cls->data()->properties) {
            //比较字符串如果相等就返回当前的 property
            if (0 == strcmp(name, prop.name)) {
                return (objc_property_t)&prop;
            }
        }
    }

    return nil;
}

# 类是怎么增加 property 的?

看源码及注释

static bool _class_addProperty(Class cls, const char *name,
                   const objc_property_attribute_t *attrs, unsigned int count,
                   bool replace)
{
    if (!cls) return NO;
    if (!name) return NO;
    //先从当前类获取指定名称的 property,如果有而且 caller 不想替换则直接返回
    property_t *prop = class_getProperty(cls, name);
    if (prop  &&  !replace) {
        // already exists, refuse to replace
        return NO;
    }
    //替换当前 property 流程,其实就是把 property 相关的 attributes 进行替换。
    else if (prop) {
        // replace existing
        rwlock_writer_t lock(runtimeLock);
        try_free(prop->attributes);
        //参数 count 是 attrs 的数量。
        prop->attributes = copyPropertyAttributeString(attrs, count);
        return YES;
    }
    else {
        //为新的 property 分配空空间。值得注意的是这里并不是直接分配的 property 而是通过 property_list 进行操作的。
        property_list_t *proplist = (property_list_t *)malloc(sizeof(*proplist));
        proplist->count = 1;
        proplist->entsizeAndFlags = sizeof(proplist->first);
        proplist->first.name = strdupIfMutable(name);
        proplist->first.attributes = copyPropertyAttributeString(attrs, count);
        //cls->data() 拿到 class_rw_t 结构体,结构体里的 properties 是 property_array_t 类型,将新的 property 加入到 property 数组中。
        cls->data()->properties.attachLists(&proplist, 1);

        return YES;
    }
}

# property 和成员变量是怎么对应起来的?

其实是在编译这一步就做好了。

@interface Fan : NSObject
@property (nonatomic,strong) NSString *name;
@end
.....

编译 clang -rewrite-objc main.m 之后结果

typedef struct objc_object Fan;

extern "C" unsigned long OBJC_IVAR_$_Fan$_name;
struct Fan_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_name;
};
// @property (atomic,strong) NSString *name;
/* @end */
...

可以看到 name 属性变成了成员变量 _name

# property 一个 demo

我们之前猜测不同 propertyattribute 会对编译器造成影响,即生成出来的 settergetter 方法可能不同,比如 atomicsetter 方法可能会有锁的实现,于是我补充了上面源码的 gettersetter 实现:

static NSString * _I_Fan_name(Fan * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Fan$_name)); }
static void _I_Fan_setName_(Fan * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_Fan$_name)) = name; }

发现 setter 并没有什么特别的地方...

难道我的验证有问题吗?感觉不应该。我尝试在 demo 中给 name 属性赋值,并打了个符号断点 objc_setProperty_atomic 发现这个方法确实走了,所以 clang 出来的结果应该是有问题的。怎么生成更准确的编译结果我也不清楚。。

Jietu20180106-185957

你也许会问 objc_setProperty_atomic 这个是干什么用的,翻源码到 objc-accessors.mm 这个文件看,可以看到不同的修饰符走了不同的设置属性的方法比如 objc_setProperty_nonatomicobjc_setProperty_atomicobjc_setProperty_atomic_copy 最终他们都调用了 reallySetProperty 这个方法接收了众多参数,根据不同的参数,走了不同的执行流。

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }
    id oldValue;
    //这里的 offset 其实就是上一步编译时候确定的。
    id *slot = (id*) ((char*)self + offset);
    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }
    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        //在这里看到 atomic 确实是使用自旋锁来对赋值进行了保护。
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;
        slotlock.unlock();
    }
    objc_release(oldValue);
}

可以看到,属性机制的实现不仅靠编译时,运行时也起了很大的作用。

一个思考,能否在运行时候动态添加属性呢?