iOS 之 runtime 詳解 api(三)
第一篇我們講了關於 Class
和 Category
的 api
,第二篇講了關於 Method
的 api
,這一篇來講關於 Ivar
和 Property
。這一篇乾貨比較多,有部分地方是分析 原始碼 ,請大家耐心的看下去。
4.objc_ivar or Ivar
首先,我們還是先找到能打印出 Ivar
資訊的函式:
const char * _Nullable ivar_getName(Ivar _Nonnull v) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
這個是通過傳入對應的 Ivar
,獲得 Ivar
的名字。
我們寫到一個方法裡面,以便於呼叫:
-(void)logIvarName:(Ivar)ivar { if (ivar) { const char* name = ivar_getName(ivar); NSLog(@"name = %s",name); } else { NSLog(@"ivar為null"); } }
那麼知道了如何獲得名字,那麼怎麼獲得 Ivar
呢?
Ivar _Nullable class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); Ivar _Nullable class_getClassVariable(Class _Nullable cls, const char * _Nonnull name) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
class_getInstanceVariable
是在 cls
類裡,名字為 name
的例項變數。
class_getClassVariable
是在 cls
類裡,名字為 name
的類變數,由於在OC語法裡面,並不存在類變數這個概念,所以,這個方法並沒有什麼用,那我們就驗證 class_getInstanceVariable
這個方法。
我們新建一個 Cat
類,新增一個成員變數 int _age
和一個屬性 @property(nonatomic,copy)NSString* name
,眾所周知,屬性會自動生成一個前面帶_的成員變數( name
生成 _name
)。
-(void)getIvar { Ivar ivar = class_getInstanceVariable(objc_getClass("Cat"), "_name"); Ivar ivar1 = class_getInstanceVariable(objc_getClass("Cat"), "_age"); [self logIvarName:ivar]; [self logIvarName:ivar1]; }
執行結果:
2019-02-26 11:42:38.646792+0800 Runtime-Demo[59730:4976606] name = _name 2019-02-26 11:42:38.646845+0800 Runtime-Demo[59730:4976606] name = _age
打印出來了,也確實是成員變數。
那麼如何獲得一個類的所有成員變數呢?就用下面這個方法:
Ivar _Nonnull * _Nullable class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
為了增加可靠性,我們在 Cat.m
檔案裡面加一個成員變數 BOOL _sex
和 @property(nonatomic, strong)Person* master
,下面我們把 Car
類裡面所有的成員變數列印下:
-(void)copyIvarList { unsigned int count; Ivar* ivars =class_copyIvarList(objc_getClass("Cat"), &count); for (unsigned int i = 0; i < count; i++) { Ivar ivar = ivars[i]; [self logIvarName:ivar]; } free(ivars); }
執行結果:
2019-02-26 11:50:51.090761+0800 Runtime-Demo[59875:4979802] name = _age 2019-02-26 11:50:51.090799+0800 Runtime-Demo[59875:4979802] name = _sex 2019-02-26 11:50:51.090809+0800 Runtime-Demo[59875:4979802] name = _name 2019-02-26 11:50:51.090817+0800 Runtime-Demo[59875:4979802] name = _master
如果你要獲得成員變數的型別,就可以用下面這個方法:
const char * _Nullable ivar_getTypeEncoding(Ivar _Nonnull v) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
我們試著獲得下 _name
的型別:
-(void)getTypeEncoding { Ivar ivar = class_getInstanceVariable(objc_getClass("Cat"), "_name"); const char* type = ivar_getTypeEncoding(ivar); NSLog(@"type = %s",type); }
執行結果:
type = @"NSString"
name
確實是 NSString
型別的。
下面我們看的三個方法是給 ivar
賦值或者取值。
id _Nullable object_getIvar(id _Nullable obj, Ivar _Nonnull ivar) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); void object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); void object_setIvarWithStrongDefault(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0);
object_getIvar
這個方法是給ivar取值的函式。我們測試下:
-(void)getIvarValue { Cat* cat = [Cat new]; Ivar ivar = class_getInstanceVariable(objc_getClass("Cat"), "_name"); NSString* name = object_getIvar(cat, ivar); NSLog(@"賦值前:%@",name); cat.name = @"jack"; NSString* name2 = object_getIvar(cat, ivar); NSLog(@"賦值後:%@",name2); }
執行結果:
2019-02-26 15:44:11.758498+0800 Runtime-Demo[63973:5079569] 賦值前:(null) 2019-02-26 15:44:11.758541+0800 Runtime-Demo[63973:5079569] 賦值後:jack
後面我就要仔細說說 object_setIvar
和 object_setIvarWithStrongDefault
,這兩個函式都和記憶體管理有關係。先說下它們的共同點,如果記憶體管理屬於已知的記憶體管理方式(成員變數或屬性屬於 ARC
, strong
或者 weak
),它們都沒有區別。不同點就是如果是屬於未知的記憶體管理方式, object_setIvar
會把該例項變數被分配為 unsafe_unretain
,而 object_setIvarWithStrongDefault
會把該例項變數被分配為 strong
。
首先我們要清楚3個概念, strong
, weak
和 unsafe_unretain
。
strong
是強引用指向並擁有那個物件,根據 retainCount
是否為0來確定是否釋放記憶體
weak
是弱引用指向但並不擁有那個物件。釋放空間時會自動將指標設定成 nil
。
unsafe_unretain
和 weak
類似,只是釋放空間時不會將指標設定成 nil
,所以會有野指標的危害。
所以,在ARC下,這兩個方法的作用幾乎一模一樣。
新增2個屬性, @property(nonatomic, copy)NSString* style
和 @property(nonatomic, copy)NSString *breed
。
-(void)setIvar { Cat* cat = [Cat new]; Ivar ivar = class_getInstanceVariable(objc_getClass("Cat"), "_breed"); Ivar ivar2 = class_getInstanceVariable(objc_getClass("Cat"), "_style"); object_setIvar(cat, ivar,@"英短"); object_setIvar(cat, ivar2,@"活潑"); NSLog(@"breed = %@",cat.breed); NSLog(@"style = %@",cat.style); }
執行結果:
2019-02-26 17:53:10.013361+0800 Runtime-Demo[66371:5132652] breed = 英短 2019-02-26 17:53:10.013430+0800 Runtime-Demo[66371:5132652] style = 活潑
賦值功能完全好用。
下面這個方法是獲得例項變數的偏移量,也就是記憶體的偏移位置,我們就可以看到變數的記憶體地址。
ptrdiff_t ivar_getOffset(Ivar _Nonnull v) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
我們測試下 Cat
類,先看下 Cat
類的屬性和變數分佈:
Cat.h @interface Cat : NSObject { @public int _age; } @property(nonatomic, copy)NSString* name; @property(nonatomic, copy)NSString *breed; @property(nonatomic, copy)NSString* style; @end Cat.m @interface Cat() { BOOL _sex; } @property(nonatomic, strong)Person* master; @end @implementation Cat @end
我們看到 Cat
類裡面有4個屬性,2個成員變數,現在我們通過獲取變數列表,逐個列印每個變數的 ptrdiff_t
-(void)getOffset { unsigned int count; Ivar* ivars =class_copyIvarList(objc_getClass("Cat"), &count); for (unsigned int i = 0; i < count; i++) { Ivar ivar = ivars[i]; ptrdiff_t offset = ivar_getOffset(ivar); NSLog(@"%s = %td",ivar_getName(ivar),offset); } free(ivars); NSLog(@"Cat總位元組 = %lu",class_getInstanceSize(objc_getClass("Cat"))); }
執行結果:
2019-02-26 20:09:16.296160+0800 Runtime-Demo[17275:490666] _age = 8 2019-02-26 20:09:16.296274+0800 Runtime-Demo[17275:490666] _sex = 12 2019-02-26 20:09:16.296364+0800 Runtime-Demo[17275:490666] _name = 16 2019-02-26 20:09:16.296452+0800 Runtime-Demo[17275:490666] _breed = 24 2019-02-26 20:09:16.296525+0800 Runtime-Demo[17275:490666] _style = 32 2019-02-26 20:09:16.296666+0800 Runtime-Demo[17275:490666] _master = 40 2019-02-26 20:09:16.296765+0800 Runtime-Demo[17275:490666] Cat總位元組 = 48
分析下列印的資料,看下順序,最先列印的是h檔案的例項變數,其次是m檔案的例項變數,然後是h檔案的屬性,最後列印m檔案的屬性。看下地址和大小, Cat
總共48位元組, _age
從第8位元組開始,佔4個位元組,然後第12位元組開始是 _sex
,佔4個位元組,到第16位是 _name
,佔8個位元組,到24位元組是 _breed
,佔8個位元組,到32位元組是 _style
,佔8個位元組,到40位元組是 _master
,佔8個位元組。它們所佔記憶體是由 本身型別 和 記憶體對齊 共同決定的。
下面這個函式是為動態類增加變數的,什麼是動態類呢?我們在第一篇的時候講了,動態建立類可以用 objc_allocateClassPair
函式去建立,而 class_addIvar
函式就必須要在 objc_allocateClassPair
後 objc_registerClassPair
前去新增變數。
BOOL class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size, uint8_t alignment, const char * _Nullable types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
我們來看看引數, cls
是你要加例項變數的類, size
是所佔記憶體的位元組數, types
是例項變數的型別, alignment
指的是對齊,官方文件有個公式 log2(sizeof(pointer_type))
。下面我們測試下:
-(void)addIvar { Class class = objc_allocateClassPair(objc_getClass("NSObject"), "Dog", 0); float alignment = log2f(sizeof(int)); class_addIvar(class, "age", sizeof(int), alignment, "int"); objc_registerClassPair(class); Ivar ivar = class_getInstanceVariable(class, "age"); NSLog(@"name = %s",ivar_getName(ivar)); NSLog(@"size = %zu",class_getInstanceSize(objc_getClass("Dog"))); }
執行結果:
2019-02-26 20:44:46.198155+0800 Runtime-Demo[19229:519808] name = age 2019-02-26 20:44:46.198295+0800 Runtime-Demo[19229:519808] size = 16
能打印出來新建類的例項變數。
下面四個方法和變數佈局有關係,這是我感覺最難理解的方法。 IvarLayout
這個概念在 runtime.h
裡面並沒有進行說明。
const uint8_t * _Nullable class_getIvarLayout(Class _Nullable cls) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); const uint8_t * _Nullable class_getWeakIvarLayout(Class _Nullable cls) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); void class_setIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); void class_setWeakIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
這四個函式從字面意思,我們可以看到是關於 weak
或者 strong
的 Ivar
的佈局,知道這些,我們先寫一個例子,建立一個新類 Lion
,新建兩個變數, __weak NSString* weak1
和 __strong NSString* strong1
。
@interface Lion : NSObject { __weak NSString* weak1; __weak NSString* weak2; __strong NSString* strong1; __strong NSString* strong2; __weak NSString* weak3; }
-(void)test { const uint8_t *strongLayout =class_getIvarLayout(objc_getClass("Lion")); printf("strongLayout = #%02x\n",*strongLayout); const uint8_t *weakLayout =class_getWeakIvarLayout(objc_getClass("Lion")); printf("weakLayout = #%02x\n",*weakLayout); }
執行結果:
strongLayout = #22 weakLayout = #02
完全不懂這個含義,那麼,我們就要從原始碼看看有沒有相關的資訊。
void fixupCopiedIvars(id newObject, id oldObject) { for (Class cls = oldObject->ISA(); cls; cls = cls->superclass) { if (cls->hasAutomaticIvars()) { // Use alignedInstanceStart() because unaligned bytes at the start // of this class's ivars are not represented in the layout bitmap. size_t instanceStart = cls->alignedInstanceStart(); const uint8_t *strongLayout = class_getIvarLayout(cls); if (strongLayout) { id *newPtr = (id *)((char*)newObject + instanceStart); unsigned char byte; while ((byte = *strongLayout++)) { unsigned skips = (byte >> 4); unsigned scans = (byte & 0x0F); newPtr += skips; while (scans--) { // ensure strong references are properly retained. id value = *newPtr++; if (value) objc_retain(value); } } } const uint8_t *weakLayout = class_getWeakIvarLayout(cls); // fix up weak references if any. if (weakLayout) { id *newPtr = (id *)((char*)newObject + instanceStart), *oldPtr = (id *)((char*)oldObject + instanceStart); unsigned char byte; while ((byte = *weakLayout++)) { unsigned skips = (byte >> 4); unsigned weaks = (byte & 0x0F); newPtr += skips, oldPtr += skips; while (weaks--) { objc_copyWeak(newPtr, oldPtr); ++newPtr, ++oldPtr; } } } } } }
這段是 runtime
原始碼裡面一段對 weakLayout
和 strongLayout
操作,裡面的 weakLayout
和 strongLayout
也是用 class_getWeakIvarLayout
和 class_getIvarLayout
方法獲得的,先看下 strongLayout
,我們截取出來:
if (strongLayout) { id *newPtr = (id *)((char*)newObject + instanceStart); unsigned char byte; while ((byte = *strongLayout++)) { unsigned skips = (byte >> 4); unsigned scans = (byte & 0x0F); newPtr += skips; while (scans--) { // ensure strong references are properly retained. id value = *newPtr++; if (value) objc_retain(value); } } }
我們來分析一下這一段, while ((byte = *strongLayout++))
這是一段迴圈,其中 byte = *strongLayout++
是將 strongLayout
裡的值取出來賦值給 byte
,並且下次迴圈的時候,指標地址+1,那麼,我們可以知道 strongLayout
所指向是一個 uint8_t
的陣列( uint8_t
在原始碼中解釋為 typedef unsigned char uint8_t;
,佔1個位元組,相當於2位16進位制),也就是 strongLayout
存有多段 layout
。 unsigned skips = (byte >> 4)
是位移操作,二進位制向右移動4位,也相當於16進位制向右移動一位,那麼, skips
也就是取 byte
左邊那一位。 unsigned scans = (byte & 0x0F)
正好相反,和0F 相與 ,取 byte
的右邊那一位。 newPtr += skips
這一句說明 newPtr
向後 skips
個地址是不需要的,直接跳過。 while (scans--)
是又迴圈了 scans
次,迴圈裡面就是取新地址的值並且進行其他操作, scans
是我們需要的。同樣的原理同學們可以自己分析 weakLayout
的那段程式碼。
根據原始碼,應該有多段 layout
,所以,我們改下程式碼:
-(void)getIvarLayout { const uint8_t *strongLayout =class_getIvarLayout(objc_getClass("Lion")); if (!strongLayout) { return; } uint8_t byte; while ((byte = *strongLayout++)) { printf("strongLayout = #%02x\n",byte); } const uint8_t *weakLayout =class_getWeakIvarLayout(objc_getClass("Lion")); if (!weakLayout) { return; } while ((byte = *weakLayout++)) { printf("weakLayout = #%02x\n",byte); } }
再執行一下:
strongLayout = #22 weakLayout = #02 weakLayout = #21
那麼,我們來分析一下:
已知先有2個 weak
變數,再有2個 strong
變數,最後還有1個 weak
變數。
strongLayout
的最低位是2,因為是 scan
值,所以,這是 strong
型別的數量。而最高位也是2,這是 skip
值,是 strong
的變數前面的 weak
變數的數量。但是,為什麼只有一段呢,因為後面已經沒有 strong
變數,我們已經知道了 strongLayout
的所有佈局了(所有strong變數的位置)。
weakLayout
的第一段中最低位是2,是 scan
值,所以應該是 weak
型別的數量,最高位是0,這是 skip
值,說明weak變數前面沒有 strong
變數,確實也如此。
似乎,我們找到了規律。每段 layout
我們都會打印出來 #xy
,在 strongLayout
中, y
就是連續 strong
變數的數目, x
就是 strong
變數前面的連續 weak
變數的數目。在 weakLayout
中, y
就是連續 weak
變數的數目, x
就是 weak
變數前面的連續 strong
變數的數目。
我們用一張圖來解釋:

螢幕快照 2019-02-27 下午5.13.25.png
綠色的代表 strongLayout
的第一段 layout
,裡面包含 strong
的變數,數目為2, strong
變數前面還有 weak
的變數,所以是22(第一個2是指 weak
的2,第二個2指的是 strong
的2)。
weakLayout
的第一段
layout
,裡面包含
weak
的變數,數目為2,
weak
變數前面沒有其他的變數,所以是02,橙色的代表
weakLayout
的第二段
layout
,裡面包含1個
weak
變數,
weak
前面有2個連續的
strong
變數,所以是21。這樣看是不是清晰很多。
突然我靈光一閃,列印的是十六進位制的值,如果先出現17個 weak
的變數會怎麼樣呢?
那我們來嘗試下:
@interface Lion : NSObject { __weak NSString* weak1; __weak NSString* weak2; __weak NSString* weak3; __weak NSString* weak4; __weak NSString* weak5; __weak NSString* weak6; __weak NSString* weak7; __weak NSString* weak8; __weak NSString* weak9; __weak NSString* weak10; __weak NSString* weak11; __weak NSString* weak12; __weak NSString* weak13; __weak NSString* weak14; __weak NSString* weak15; __weak NSString* weak16; __weak NSString* weak17; __strong NSString* strong1; __strong NSString* strong2; __strong NSString* strong3; __weak NSString* weak18; __strong NSString* strong4; __strong NSString* strong5; }
執行結果:
strongLayout = #f0 strongLayout = #23 strongLayout = #12 weakLayout = #0f weakLayout = #02 weakLayout = #31
我們可以看到分成3段 layout
,前17個 weak
變數中的前15個被拆出來,被當成第一段 layout
,第17個和後面的拼成一段 layout
,所以,我們得到結論,每段 layout
的 weak
變數或者 strong
變數不會超過15個。
4.objc_property or objc_property_t
屬性應該是我們最熟悉的了,相當於給例項變數加了修飾符,自動生成 set
和 get
方法,用起來很方便。
runtime
裡面關於屬性的結構體是 objc_property
或者 objc_property_t
,這個我們並不知道里面的結構,但是官方告訴我們另外一個:
typedef struct { const char * _Nonnull name;/**< The name of the attribute */ const char * _Nonnull value;/**< The value of the attribute (usually empty) */ } objc_property_attribute_t;
我們可以通過 objc_property_attribute_t
來間接獲得關於屬性的一些資訊。
而這個方法 property_copyAttributeList
方法就是通過傳入 objc_property_t
來獲得 objc_property_attribute_t
objc_property_attribute_t * _Nullable property_copyAttributeList(objc_property_t _Nonnull property, unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
我們寫個方法來封裝下這個方法:
-(void)logProperty:(objc_property_t)property { NSLog(@"-------------------"); unsigned int count; objc_property_attribute_t* attributeList = property_copyAttributeList(property, &count); for (unsigned int i = 0; i < count; i++) { objc_property_attribute_t attribute = attributeList[i]; NSLog(@"name = %s",attribute.name); NSLog(@"value = %s",attribute.value); } }
後面我們就用這個方法來列印屬性相關的資訊。那怎麼獲得 objc_property_t
呢?
objc_property_t _Nullable class_getProperty(Class _Nullable cls, const char * _Nonnull name) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
我們還是以Cat類為例,我們從上面可知有4個屬性 @property(nonatomic, copy)NSString* name
, @property(nonatomic, copy)NSString *breed
, @property(nonatomic, copy)NSString* style
, @property(nonatomic, strong)Person* master
。
下面我們分別獲取 name
這個屬性。
-(void)getProperty { objc_property_t property = class_getProperty(objc_getClass("Cat"), "name"); [self logProperty:property]; }
列印結果:
2019-02-27 09:37:17.172874+0800 Runtime-Demo[72525:5355290] name = T 2019-02-27 09:37:17.172916+0800 Runtime-Demo[72525:5355290] value = @"NSString" 2019-02-27 09:37:17.172929+0800 Runtime-Demo[72525:5355290] name = C 2019-02-27 09:37:17.172950+0800 Runtime-Demo[72525:5355290] value = 2019-02-27 09:37:17.172965+0800 Runtime-Demo[72525:5355290] name = N 2019-02-27 09:37:17.172975+0800 Runtime-Demo[72525:5355290] value = 2019-02-27 09:37:17.172985+0800 Runtime-Demo[72525:5355290] name = V 2019-02-27 09:37:17.172995+0800 Runtime-Demo[72525:5355290] value = _name
我們可以看到有 value
是的 name
為 T
和 V
,T代表 type
,屬性的型別, V
代表 ivar
,代表屬性的 ivar
的是 _name
。其他沒有值的代表,那些修飾符, C
代表 copy
, N
代表 nonatomic
。由此我們可以總結出來:
name | value | 含義 |
---|---|---|
T | 有 | 屬性的型別 |
V | 有 | 屬性所生成的例項變數的名稱 |
C | 無 | copy |
N | 無 | nonatomic |
W | 無 | weak |
& | 無 | 物件型別處於預設狀態是用&,比方strong和readwrite |
R | 無 | readonly |
注:如果沒有 N
,就說明是 atomic
。
同樣也可以獲得一個類的屬性列表。為了列印方便,我們這次只打印屬性的名字,就要用到 property_getName
這個方法:
const char * _Nonnull property_getName(objc_property_t _Nonnull property) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
下面我們列印下列表的名字:
objc_property_t _Nonnull * _Nullable class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
還是以 Cat
為例:
-(void)copyPropertyList { unsigned int count; objc_property_t* propertyList = class_copyPropertyList(objc_getClass("Cat"), &count); for (unsigned int i = 0; i < count; i++) { objc_property_t property = propertyList[i]; NSLog(@"name = %s",property_getName(property)); } free(propertyList); }
執行結果:
2019-02-27 10:30:33.006299+0800 Runtime-Demo[73443:5379227] name = master 2019-02-27 10:30:33.006338+0800 Runtime-Demo[73443:5379227] name = name 2019-02-27 10:30:33.006348+0800 Runtime-Demo[73443:5379227] name = breed 2019-02-27 10:30:33.006357+0800 Runtime-Demo[73443:5379227] name = style
把屬性名字都打印出來了,這裡要和 ivar
區分一下,如果通過已知屬性去找 ivar
,那麼找到的是帶有下劃線的。
之前我們可以打印出一個 property
的所有屬性,系統還提供了2個方法:
const char * _Nullable property_getAttributes(objc_property_t _Nonnull property) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); char * _Nullable property_copyAttributeValue(objc_property_t _Nonnull property, const char * _Nonnull attributeName) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
我們先測試 property_getAttributes
這個函式
-(void)getAttributes { objc_property_t property = class_getProperty(objc_getClass("Cat"), "name"); const char* attributes = property_getAttributes(property); NSLog(@"attributes = %s",attributes); }
執行結果:
attributes = T@"NSString",C,N,V_name
列印的結果和之前是一樣的,這次是以字串的形式列印。
再看下 property_copyAttributeValue
這個方法,這是通過 attributeName
獲得單獨的value。
-(void)copyAttributeValue { objc_property_t property = class_getProperty(objc_getClass("Cat"), "name"); //V我們已知是屬性所代表的ivar的名字,看列印是否是ivar char* value = property_copyAttributeValue(property,"V"); NSLog(@"value = %s",value); }
執行結果:
value = _name
從之前列印結果,這個列印結果是正確的。
下面這兩個方法是動態新增或者替換屬性
BOOL class_addProperty(Class _Nullable cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); void class_replaceProperty(Class _Nullable cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
我們還是以 Cat
為例,為他增加 Property
,目標:增加一個 @property(nonatomic, copy,readonly)NSString* mood
形式的屬性。
傳參需要傳 objc_property_attribute_t
的列表,分析一下, T
和 V
是必有的, T
的 value
是 NSString
, V
的 value
是 _mood
,然後 nonatomic
代表有 N
, copy
代表有 C
, readonly
代表有R,所以我們可以獲知 attribute
有 T
, V
, C
, N
, R
。好了,我們寫程式碼吧!
-(void)addProperty { unsigned int count = 5; objc_property_attribute_t attributeList[count]; objc_property_attribute_t attribute1 ; attribute1.name = "T"; attribute1.value = "NSString"; objc_property_attribute_t attribute2 ; attribute2.name = "V"; attribute2.value = "_mood"; objc_property_attribute_t attribute3 ; attribute3.name = "N"; attribute3.value = ""; objc_property_attribute_t attribute4 ; attribute4.name = "C"; attribute4.value = ""; objc_property_attribute_t attribute5 ; attribute5.name = "R"; attribute5.value = ""; attributeList[0] = attribute1; attributeList[1] = attribute2; attributeList[2] = attribute3; attributeList[3] = attribute4; attributeList[4] = attribute5; BOOL isSuccess = class_addProperty(objc_getClass("Cat"), "mood", (const objc_property_attribute_t *)&attributeList, count); NSLog(@"新增%@",isSuccess?@"成功":@"失敗"); [self copyPropertyList]; objc_property_t property = class_getProperty(objc_getClass("Cat"), "mood"); const char* attributes = property_getAttributes(property); NSLog(@"attributes = %s",attributes); }
執行結果:
2019-02-27 11:52:49.325561+0800 Runtime-Demo[74832:5417422] 新增成功 2019-02-27 11:52:49.325614+0800 Runtime-Demo[74832:5417422] name = mood 2019-02-27 11:52:49.325632+0800 Runtime-Demo[74832:5417422] name = master 2019-02-27 11:52:49.325650+0800 Runtime-Demo[74832:5417422] name = name 2019-02-27 11:52:49.325662+0800 Runtime-Demo[74832:5417422] name = breed 2019-02-27 11:52:49.325674+0800 Runtime-Demo[74832:5417422] name = style 2019-02-27 11:52:49.325709+0800 Runtime-Demo[74832:5417422] attributes = TNSString,V_mood,N,C,R
新增成功,並且列印的屬性列表也有 mood
。打印出來的 attributes
也是沒問題的。
再看看 class_replaceProperty
我打算把name這個屬性的屬性名改成catName。
同樣我們還是先分析下 objc_property_attribute_t
的列表, name
的屬性是 @property(nonatomic, copy)NSString* name
,只改變名字的話, T
, C
, N
都不變,變得是 V
, V
的 value
變成 _catName
。所以程式碼就是:
-(void)replaceProperty { unsigned int count = 4; objc_property_attribute_t attributeList[count]; objc_property_attribute_t attribute1 ; attribute1.name = "T"; attribute1.value = "NSString"; objc_property_attribute_t attribute2 ; attribute2.name = "V"; attribute2.value = "_mood"; objc_property_attribute_t attribute3 ; attribute3.name = "N"; attribute3.value = ""; objc_property_attribute_t attribute4 ; attribute4.name = "C"; attribute4.value = ""; attributeList[0] = attribute1; attributeList[1] = attribute2; attributeList[2] = attribute3; attributeList[3] = attribute4; class_replaceProperty(objc_getClass("Cat"), "name", (const objc_property_attribute_t*)&attributeList, count); [self copyPropertyList]; objc_property_t property = class_getProperty(objc_getClass("Cat"), "name"); const char* attributes = property_getAttributes(property); NSLog(@"attributes = %s",attributes); }
執行結果:
2019-02-27 11:58:46.341930+0800 Runtime-Demo[74939:5421075] name = master 2019-02-27 11:58:46.341970+0800 Runtime-Demo[74939:5421075] name = name 2019-02-27 11:58:46.341980+0800 Runtime-Demo[74939:5421075] name = breed 2019-02-27 11:58:46.341988+0800 Runtime-Demo[74939:5421075] name = style 2019-02-27 11:58:46.342016+0800 Runtime-Demo[74939:5421075] attributes = TNSString,V_mood,N,C
列印結果完全出乎我的意料,打印出來的屬性完全沒有 catName
,但是列印 attributes
卻是改變的 attributes
。為什麼呢?我們要從原始碼看起來了:
struct property_t { const char *name; const char *attributes; };
property_t
的結構體分為 name
和 attributes
。
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); } void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attrs, unsigned int n) { _class_addProperty(cls, name, attrs, n, YES); }
class_addProperty
和 class_replaceProperty
的底層都呼叫了 _class_addProperty
方法,只是裡面的布林值傳的不一樣。我們再看下 _class_addProperty
這個方法,
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) { // already exists, refuse to replace return NO; } else if (prop) { // replace existing 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 = strdupIfMutable(name); proplist->first.attributes = copyPropertyAttributeString(attrs, count); cls->data()->properties.attachLists(&proplist, 1); return YES; } }
裡面這一句 property_t *prop = class_getProperty(cls, name);
是取出要替換的屬性,接著後面就是一系列判斷,因為 prop
存在,並且 replace
為 YES
,所以會走到下面這一段:
else if (prop) { // replace existing rwlock_writer_t lock(runtimeLock); try_free(prop->attributes); prop->attributes = copyPropertyAttributeString(attrs, count); return YES; }
從這一段我們可以看到這一部分只改變了 prop->attributes
。也沒有改變 prop->name
。所以,我們列印屬性的 name
自然沒有改變。那麼, class_replaceProperty
的用途最好是修改型別或者修飾符。`