@property
的本质是什么?@property
和运行时有什么关系?@property
一个 demo
# property
的本质是什么
@property
的本质就是成员变量加 getter
和 setter
方法。getter
和 setter
方法是编译器自动为我们生成的。我们也可以尝试去复写 getter
和 setter
方法,在设置成员变量前后增加一些我们想要的业务逻辑。
@property
有不同的 attribute
: readonly
,atomic
,nonatomic
等等。编译器会根据不同的 attribute
生成不同的 getter
和 setter
方法
# 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)∝
}
}
}
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
我们之前猜测不同 property
的 attribute
会对编译器造成影响,即生成出来的 setter
和 getter
方法可能不同,比如 atomic
的 setter
方法可能会有锁的实现,于是我补充了上面源码的 getter
和 setter
实现:
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
出来的结果应该是有问题的。怎么生成更准确的编译结果我也不清楚。。
你也许会问 objc_setProperty_atomic
这个是干什么用的,翻源码到 objc-accessors.mm
这个文件看,可以看到不同的修饰符走了不同的设置属性的方法比如 objc_setProperty_nonatomic
,objc_setProperty_atomic
,objc_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);
}
可以看到,属性机制的实现不仅靠编译时,运行时也起了很大的作用。
一个思考,能否在运行时候动态添加属性呢?