2019-10-15数组
属性(property)是为类的成员变量提供公开的访问器。属性与方法有很是紧密的联系,可读写的属性有 getter 和 setter 两个方法与之对应。bash
属性(property)大多数状况是做为成员变量的访问器(accessor)使用,为外部访问成员变量提供接口。使用@property
声明属性时须要指定属性的特性(attribute),包括:数据结构
readwrite
/readonly
);atomic
/nonatomic
);assign
/strong
/weak
/copy
);nullable
/nonnull
);注意:上面括号中的第一个值是属性的默认特性,不过是否可空有其特殊性,能够经过
NS_ASSUME_NONNULL_BEGIN
/NS_ASSUME_NONNULL_END
宏包围属性的声明语句,将属性的默承认空特性置为nonnull
。app
除了上述特性还能够显式指定getter、setter。属性的特性指定了属性做为访问器的行为特征。声明了属性只是意味着声明了访问器,此时的访问器是没有getter
和setter
的实现的,想要访问器关联特定的成员变量在代码上有两种方式:一、使用@synthesize
修饰符合成属性;二、实现属性的 getter 和 setter。可是二者的本质是同样的,就是按属性的特性实现属性的 getter 和 setter。函数
注意:
@dynamic
修饰属性,表示不合成属性的 getter 和 setter。此时要么在当前类或子类的实现中实现getter/setter、要么在子类实现中用@synthesize
合成属性。性能
类的class_rw_t
中包含属性列表property_array_t
类的properties
成员,property_array_t
为list_array_tt<property_t, property_list_t>
,所以类中的属性列表保存property_list_t
的数组,一样是个二维数组,property_list_t
继承自entsize_list_tt
顺序表容器,元素类型是property_t
。相关数据结构以下,大部分在介绍成员变量和方法列表时有说起,所以再也不赘述。从属性相关的数据结构可知,类中保存的属性信息只有属性名、特性信息。ui
属性在类中的保存与方法列表的保存也很是类似。class_ro_t
中的property_list_t
类型的basePropertyList
仅保存类定义时定义的基本属性,这些属性是编译时决议的;class_rw_t
中的property_array_t
类型的properties
保存类的完整属性列表,包括类的基本属性,以及运行时决议的 类的分类中定义的属性以及运行时动态添加的属性。atom
class property_array_t :
public list_array_tt<property_t, property_list_t>
{
typedef list_array_tt<property_t, property_list_t> Super;
public:
property_array_t duplicate() {
return Super::duplicate<property_array_t>();
}
};
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
struct property_t {
const char *name;
const char *attributes;
};
复制代码
添加属性调用class_addProperty(...)
函数,注意到在源代码中并无与方法列表相关的操做,推测属性关联方法列表的操做隐藏在@synthesize
、@dynamic
以及属性的attributes
解析的实现中没有公开。属性添加与方法添加也基本同样,是添加到class_rw_t
的完整属性列表properties
的外层一位数组容器的开头,所以也知足优先级关系:运行时动态添加的属性 > 类的分类定义的属性 > 类定义时定义的基本属性
。spa
// 添加属性
BOOL
class_addProperty(Class cls, const char *name,
const objc_property_attribute_t *attrs, unsigned int n)
{
return _class_addProperty(cls, name, attrs, n, NO);
}
// 添加属性、替换属性的实现逻辑
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_t *prop = class_getProperty(cls, name);
if (prop && !replace) {
// 已存在且不是指定替换属性
return NO;
}
else if (prop) {
// 替换属性
rwlock_writer_t lock(runtimeLock);
try_free(prop->attributes);
prop->attributes = copyPropertyAttributeString(attrs, count);
return YES;
}
else {
rwlock_writer_t lock(runtimeLock);
assert(cls->isRealized());
property_list_t *proplist = (property_list_t *)
malloc(sizeof(*proplist));
proplist->count = 1;
proplist->entsizeAndFlags = sizeof(proplist->first);
proplist->first.name = strdup(name);
proplist->first.attributes = copyPropertyAttributeString(attrs, count);
cls->data()->properties.attachLists(&proplist, 1);
return YES;
}
}
objc_property_t class_getProperty(Class cls, const char *name)
{
if (!cls || !name) return nil;
mutex_locker_t lock(runtimeLock);
checkIsKnownClass(cls);
assert(cls->isRealized());
for ( ; cls; cls = cls->superclass) {
for (auto& prop : cls->data()->properties) {
if (0 == strcmp(name, prop.name)) {
return (objc_property_t)∝
}
}
}
return nil;
}
复制代码
存取属性值的代码集中在objc-accessors.mm
源文件中。调试
调用objc_getProperty_gc(...)
获取对象的属性值,实际上只是按必定的方式访问了属性对应的成员变量空间。若属性为atomic
则会在获取属性值的代码两头添加spinlock_t
的加锁解锁代码,这也是atomic
和nonatomic
的区别所在。
注意:实际上,第三节 介绍的动态添加属性对应用开发者并无什么用处(对 runtime 自己固然是有用的),缘由是:一、没有指定属性关联成员变量的 runtime API;二、能够经过定义函数关联对象模拟属性,此时动态添加的属性就成了鸡肋,无关紧要。
#if SUPPORT_GC
id objc_getProperty_gc(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
return *(id*) ((char*)self + offset);
}
#else
id
objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic)
{
return objc_getProperty_non_gc(self, _cmd, offset, atomic);
}
#endif
id objc_getProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
复制代码
获取属性值对copy
类型没有作相关处理,也就是说**copy
属性的getter返回的也是属性指向对象自己,copy属性的getter并不包含拷贝操做**。用如下代码能够验证,在标记处打断点运行。查看testObj
的内存,第8-16个字节保存的就是testObj
的arr
属性所指向的实际的NSArray
地址。会发现打印出来的testObj.arr
地址和testObj
的内存的第8-16个字节内容是一致的。
@interface TestPropCopy: NSObject
@property(copy, nonatomic) NSArray* arr; // 不要用NSString类型,调试时很差看地址
@end
@implementation TestPropCopy
+(void)testPropCopy{
NSArray* arr = [NSArray arrayWithObject:@"app"];
TestPropCopy* testObj = [[self alloc] init];
testObj.arr = arr;
NSLog(@"testObj:%@", testObj);
NSLog(@"arr:%@", arr);
NSLog(@"testObj.arr:%@", testObj.arr);
// 此处打断点
}
@end
复制代码
注意:
spinlock_t
的本质是os_lock_handoff_s
锁,关于这个锁网上找不到什么资料,推测是个互斥锁。注意spinlock_t
并非OSSpinLock
。OSSpinLock
已知存在性能问题,已经被弃用。
调用objc_setProperty(...)
设置对象的属性值,一样是是按必定的方式访问属性对应的成员变量空间。一样,若属性为atomic
则会在设置属性值的代码两头添加spinlock_t
的加锁解锁代码。若属性为copy
时,则将传入 setter 的参数指向的对象 拷贝到对应的成员变量空间。
#if SUPPORT_GC
void objc_setProperty_gc(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) {
if (shouldCopy) {
newValue = (shouldCopy == MUTABLE_COPY ? [newValue mutableCopyWithZone:nil] : [newValue copyWithZone:nil]);
}
objc_assign_ivar(newValue, self, offset);
}
#else
void
objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue,
BOOL atomic, signed char shouldCopy)
{
objc_setProperty_non_gc(self, _cmd, offset, newValue, atomic, shouldCopy);
}
#endif
void objc_setProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
// 设置属性值的总入口
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;
id *slot = (id*) ((char*)self + offset);
// 若属性为copy,则将newVal参数指向的对象拷贝到对应成员变量空间
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 {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
复制代码
注意:存取属性值的实现是直接调用属性的getter、setter的对应的方法的
SEL
触发的,属性与方法的关联细节则没有公布源代码。
Objective-C 代码中,属性关联成员变量是经过@synthesize
实现的,runtime 并无公开这块代码。本节探讨@synthesize
的实现原理。
类定义属性且不手动定义属性的 getter 和 setter 方法时,类的方法列表会添加对应的prop
方法及setProp
方法。且object_getProperty(...)
的参数列表包含self
和_cmd
,和消息的格式很相似。所以 推测 声明属性@synthesize
时,runtime 会根据属性的attributes
生成属性的 getter 方法SEL
、IMP
和 setter 方法SEL
、IMP
,并将其添加到类的方法列表中,getter 和 setter 的IMP
伪代码以下,其中#
号包围的时编译时可肯定的参数;
id propGetter(id self, SEL _cmd) {
char* ivarName = synthizeName ? : ( '_' + #propertyName#)
Class selfClass = object_getClass(self)
Ivar ivar = getIvar(Class cls, ivarName)
uint_32 ivarOffset = ivar_getOffset(ivar)
objc_getProperty(self, _cmd,
#propertyNameAttr#,
ivar,
#propertyAtomicAttr#)
}
void propSetter(id self, SEL _cmd, id newVal) {
char* ivarName = #synthesizeName# ? : ( '_' + #propertyName#)
Class selfClass = object_getClass(self)
Ivar ivar = getIvar(Class cls, ivarName)
uint_32 ivarOffset = ivar_getOffset(ivar)
objc_setProperty(self, _cmd,
#propertyNameAttr#,
newVal
ivar,
#propertyAtomicAttr#,
#propertyShouldCopyAttr#)
}
复制代码
可是上面的处理是有明显的性能缺陷的,每次访问成员变量是都要调用getIvar(...)
,而getIvar(...)
是遍历类的整个成员变量列表,根据成员变量名查找成员变量,实际实现显然不该如此。所以上述代码只是模拟了属性的实现流程,而具体实现细节将在后续将在独立文章中介绍。
Runtime 提供的关于属性的动态特性对应用开发的意义不大,runtime 属性关联成员变量是隐藏在@synthesize
的实现代码中,并且成员变量不能动态添加,所以即便提供也是意义不大;
动态添加属性时,是不包含添加属性 getter 和 setter 方法的操做的,所以必须手动实现其getter 和 setter 方法,为分类定义属性也是不包含 getter 和 setter 方法实现,开发者能够;
下一篇文章介绍分类。