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 方法實現,開發者能夠;
下一篇文章介紹分類。