前言
日常開發(fā)中,為一個(gè)已有的類(比如說不想影響其文件結(jié)構(gòu))沪摄、第三方庫提供的類增加幾個(gè)property砸烦,已經(jīng)是十分常見且需要的操作了,有人會(huì)單獨(dú)起草一份category.m文件口锭,也有人直接繼承,像我一般會(huì)用category介杆,一是能減少類文件的數(shù)量提高編譯速度鹃操,二也是為了代碼結(jié)構(gòu)更加清晰。
這篇文章是用來寫Category的進(jìn)行屬性擴(kuò)展的行為的春哨,所以我還是言歸正傳荆隘,首先,我要闡述一下目前比較主流的幾個(gè)屬性擴(kuò)展形式赴背,再往下進(jìn)行分析:
利用 objc_setAssociatedObject函數(shù)進(jìn)行對(duì)象的聯(lián)合臭胜。
利用 class_addProperty 函數(shù)進(jìn)行類屬性的擴(kuò)展
通過內(nèi)部創(chuàng)建一個(gè)其他對(duì)象(比如字典),通過重寫本對(duì)象set和get或者消息轉(zhuǎn)發(fā)。
下面對(duì)這三種常用方法進(jìn)行分析癞尚,其實(shí)常見的都是前面兩種耸三,第三種也是比較非主流。在分析這三種之前浇揩,我要談一下為什么不能
class_addIvar 函數(shù)仪壮。
class_addIvar 函數(shù)
在蘋果文檔中,對(duì) class_addIvar 函數(shù)有下面一段話:
Thisfunctionmay?only?be?called?after?objc_allocateClassPair(_:_:_:)?and?before?objc_registerClassPair(_:).?Adding?an?instancevariable?to?an?existingclassisnot?supported.
Theclassmust?not?be?a?metaclass.?Adding?an?instancevariable?to?a?metaclassisnot?supported.
這個(gè)功能只能在?objc_allocateClassPair(_:_:_:)?之后和?objc_registerClassPair(_:)?之前調(diào)用胳徽。不支持將實(shí)例變量添加到現(xiàn)有的類积锅。
該類不能是元類。不支持將實(shí)例變量添加到元類养盗。
文檔是說不能將此函數(shù)用于已有的類缚陷,必須是動(dòng)態(tài)創(chuàng)建的類,為了能夠知道為何會(huì)這樣往核,我們需要翻閱一下蘋果開源的 runtime 源碼箫爷。
1.首先看一下關(guān)于 objc_allocateClassPair 函數(shù)的代碼實(shí)現(xiàn):
去除干擾代碼,我們尋找到下面的函數(shù)調(diào)用鏈條:
objc_allocateClassPair?->?objc_initializeClassPair_internal
//?下面的代碼已經(jīng)被我大部分剔除聂儒,只留下我們分析所需要用到的代碼
staticvoidobjc_initializeClassPair_internal(Class?superclass,constchar?*name,?Class?cls,?Class?meta)
{
//?Set?basic?info
cls->data()->flags?=?RW_CONSTRUCTING?|?RW_COPIED_RO?|?RW_REALIZED?|?RW_REALIZING;
meta->data()->flags?=?RW_CONSTRUCTING?|?RW_COPIED_RO?|?RW_REALIZED?|?RW_REALIZING;
cls->data()->version?=0;
meta->data()->version?=7;
//?RW_CONSTRUCTING?類已分配但還未注冊(cè)
//?RW_COPIED_RO?class_rw_t->ro?來自?class_ro_t?結(jié)構(gòu)的復(fù)制
//?RW_REALIZED?//??class_t->data?的結(jié)構(gòu)為?class_rw_t
//?RW_REALIZING?//?類已開始分配虎锚,但并未完成
//?以上幾個(gè)宏都是對(duì)新類的class_rw_t結(jié)構(gòu)設(shè)置基本信息
}
2.下面是class_addIvar的與我分析所需要的實(shí)現(xiàn)代碼
//?無關(guān)代碼已經(jīng)剔除
BOOL
class_addIvar(Class?cls,constchar?*name,?size_t?size,
uint8_t?alignment,constchar?*type)
{
if(!cls)returnNO;
if(!type)?type?="";
if(name??&&0==?strcmp(name,""))?name?=?nil;
rwlock_writer_t?lock(runtimeLock);
assert(cls->isRealized());
//?No?class?variables
if(cls->isMetaClass())?{
returnNO;
}
//?Can?only?add?ivars?to?in-construction?classes.
if(!(cls->data()->flags?&?RW_CONSTRUCTING))?{
returnNO;
}
}
//?重點(diǎn)在這最后一句,前面我們已經(jīng)看到?objc_allocateClassPair?函數(shù)所分配的新類的flags位信息衩婚,在此處?&?RW_CONSTRUCTING窜护,必定為真,取反后跳過大括號(hào)向下執(zhí)行非春。
3.已經(jīng)存在的類柱徙,經(jīng)過測(cè)試缓屠,flag位為 RW_REALIZED|RW_REALIZING,設(shè)置函數(shù)如下:
staticClass?realizeClass(Class?cls)
{
runtimeLock.assertWriting();
constclass_ro_t?*ro;
class_rw_t?*rw;
Class?supercls;
Class?metacls;
bool?isMeta;
if(!cls)returnnil;
if(cls->isRealized())returncls;
assert(cls?==?remapClass(cls));
//?fixme?verify?class?is?not?in?an?un-dlopened?part?of?the?shared?cache?
ro?=?(constclass_ro_t?*)cls->data();
if(ro->flags?&?RO_FUTURE)?{
//?This?was?a?future?class.?rw?data?is?already?allocated.
rw?=?cls->data();
ro?=?cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING,?RW_FUTURE);
}else{
//?Normal?class.?Allocate?writeable?class?data.
rw?=?(class_rw_t?*)calloc(sizeof(class_rw_t),1);
rw->ro?=?ro;
rw->flags?=?RW_REALIZED|RW_REALIZING;
cls->setData(rw);
}
}
所以在經(jīng)過條件?!((RW_REALIZED?|?RW_REALIZING)?&?RW_CONSTRUCTING)?時(shí)返回NO。
以上便是對(duì)已有類不能使用 class_addIvar 函數(shù)的分析
好了护侮,回到真正的話題藏研,對(duì)上面三種操作的分析:
objc_setAssociatedObject
我們都知道,在category中使用property概行,可以生成set和get的方法聲明,原因在此不做分析弧岳,一般為了方便的調(diào)用凳忙,我們都會(huì)寫上property,關(guān)鍵在于沒有set和get的實(shí)現(xiàn)禽炬,于是就會(huì)有下面這樣的代碼:
staticvoid*key?="key";
@implementation?Person?(Extra)
//?此處不考慮讀寫鎖的問題
-?(void)setName:(NSString?*)name{
objc_setAssociatedObject(self,?key,?name,?OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-?(NSString?*)name{
returnobjc_getAssociatedObject(self,?key);
}
@end
上面的 objc_setAssociatedObject 函數(shù)內(nèi)部的調(diào)用鏈條如下:
objc_setAssociatedObject?->?objc_setAssociatedObject_non_gc?->?_object_set_associative_reference
//?其中主要操作都在?_object_set_associative_reference?函數(shù)中涧卵,內(nèi)部實(shí)現(xiàn)類似一般屬性的set實(shí)現(xiàn)(保留新值,釋放舊值)腹尖,在此我們不進(jìn)行深究柳恐,具體可以參考業(yè)內(nèi)大佬的博客文章。
這種操作很直觀的表達(dá)了我們的需要热幔,且API十分友好乐设,僅僅是對(duì)于 weak 策略我們需要自己設(shè)計(jì)一個(gè)。
并且這種操作的好處是我們無需關(guān)系關(guān)聯(lián)對(duì)象的聲明周期绎巨,因?yàn)楹推胀ǖ膶傩砸粯咏校瑫?huì)隨著宿主對(duì)象的釋放而釋放,具體可以看以下代碼:
dealloc?->?_objc_rootDealloc?->?rootDealloc?->?object_dispose?->?objc_destructInstance
//?大部分釋放操作在?objc_destructInstance?函數(shù)中完成
//?下面是?objc_destructInstance?函數(shù)的實(shí)現(xiàn)代碼
void*objc_destructInstance(id?obj)
{
if(obj)?{
//?Read?all?of?the?flags?at?once?for?performance.
bool?cxx?=?obj->hasCxxDtor();
bool?assoc?=?!UseGC?&&?obj->hasAssociatedObjects();
bool?dealloc?=?!UseGC;
//?This?order?is?important.
//?內(nèi)部通過C++的析構(gòu)函數(shù)進(jìn)行對(duì)象屬性的釋放,具體可看sunny大神的博文
if(cxx)?object_cxxDestruct(obj);
//?此處會(huì)移除所有的關(guān)聯(lián)對(duì)象场勤,也就是objc_setAssociatedObject?函數(shù)所設(shè)置上去的對(duì)象
if(assoc)?_object_remove_assocations(obj);
//?清空引用計(jì)數(shù)與weak表
if(dealloc)?obj->clearDeallocating();
}
returnobj;
}
當(dāng)然也有不足之處戈锻,利用 objc_setAssociatedObject 生成的關(guān)聯(lián)對(duì)象無法直接利用目前主流的Json轉(zhuǎn)Model庫(原因是無法在ivar及property中遍歷出來)。
利用 class_addProperty 函數(shù)進(jìn)行類屬性的擴(kuò)展
class_addProperty 函數(shù)可以為我們生成類的property和媳,@property是編譯器的標(biāo)識(shí)符格遭,在普通類中可生成property、ivar留瞳、setMethod與getMethod拒迅,在我看來property的真實(shí)作用類似于方法的聲明,后面我會(huì)再談為什么她倘。
在分類中使用class_addProperty和普通類一樣坪它, 只能生成set和get方法的聲明,無論有沒有被實(shí)現(xiàn)帝牡,我們都可以用 class_copyMethodList 函數(shù)得到property的list往毡,如果這時(shí)候你想存儲(chǔ)屬性值,你依然必須手動(dòng)或動(dòng)態(tài)實(shí)現(xiàn)那些set和get方法靶溜,并且真實(shí)數(shù)據(jù)的存儲(chǔ)也必須由你自己提供實(shí)現(xiàn)开瞭,比如可以使用前面所說的objc_setAssociatedObject 函數(shù)懒震。
現(xiàn)在說說為啥property只是一個(gè)類似聲明的作用呢,我們可以從蘋果開源的代碼中找到蛛絲馬跡:
Class?是一個(gè)指向結(jié)構(gòu)體?objc_class?的指針嗤详,而此結(jié)構(gòu)體的結(jié)構(gòu)如下所示:
struct?objc_class?:?objc_object?{
//?Class?ISA;
Class?superclass;//?指向父類
cache_t?cache;//?緩存指針與vtable(沒學(xué)過C++,沒了解過虛函數(shù)這些)个扰,加速方法的調(diào)用
class_data_bits_t?bits;//?真正保存對(duì)象的ivar,property與method等信息的地方
}
在源碼中大部分時(shí)候表現(xiàn)為將類的大部分信息保存在?class_rw_t?*rw指針中葱色,不過內(nèi)部也是返回bits中處理后的信息
class_rw_t?*data()?{
returnbits.data();
}
在class_rw_t的結(jié)構(gòu)中递宅,結(jié)構(gòu)如下所示:
struct?class_rw_t?{
//?Be?warned?that?Symbolication?knows?the?layout?of?this?structure.
uint32_t?flags;//?類的信息標(biāo)記
uint32_t?version;//?當(dāng)前運(yùn)行時(shí)版本
constclass_ro_t?*ro;
method_array_t?methods;
property_array_t?properties;
protocol_array_t?protocols;
}
可以看到在class_rw_t的結(jié)構(gòu)中,包含了另一個(gè)十分相似的 const class_ro_t *ro 成員變量苍狰。
這個(gè)成員變量為一個(gè)不可修改內(nèi)容的結(jié)構(gòu)體指針办龄,其中存儲(chǔ)了類在編譯時(shí)就已經(jīng)確定好的ivar、 property淋昭、method俐填、protocol等信息,在類的初始化時(shí)會(huì)通過 methodizeClass 函數(shù)將其大部分內(nèi)容都拷貝到 class_rw_t *rw中翔忽,其中 ivar 不會(huì)被拷貝英融,這也是前面所說的不能在運(yùn)行時(shí)給已有的類增加 ivar的原因。
像property歇式、method驶悟、protocol都是可以在運(yùn)行時(shí)動(dòng)態(tài)添加的,且存儲(chǔ)到 rw 的結(jié)構(gòu)中去材失。
好像說的有點(diǎn)跑題了撩银,咱們還是一起看看property到底存儲(chǔ)了什么信息:
struct?property_t?{
constchar?*name;
constchar?*attributes;
};
可以看到,propperty中并沒有存儲(chǔ)很多信息豺憔,只有name和配置的屬性额获,也沒有實(shí)現(xiàn)函數(shù)的地址,所以前面我說property的作用其實(shí)和方法的聲明是差不多的恭应。
關(guān)于property的好處抄邀,也就是在使用網(wǎng)上json轉(zhuǎn)model庫時(shí)可以被遍歷到了,但是如果你沒有實(shí)現(xiàn)set和get昼榛,那依然會(huì)導(dǎo)致KVC的crash境肾。
通過內(nèi)部創(chuàng)建一個(gè)其他對(duì)象(比如字典),通過重寫本對(duì)象set和get或者消息轉(zhuǎn)發(fā)。
最后一種方法胆屿,也是比較少用的方式奥喻,說起來也比較簡(jiǎn)單,比如定義一個(gè)靜態(tài)的字典變量非迹,然后通過實(shí)現(xiàn)interface中聲明的set和get的實(shí)現(xiàn)對(duì)這個(gè)字典變量做存取操作环鲤,或者通過消息轉(zhuǎn)發(fā)中的 (id)forwardingTargetForSelector:(SEL)aSelector 方法返回這個(gè)字典變量,但是要注意本類中沒有對(duì)轉(zhuǎn)發(fā)做過什么事憎兽,不然這種方法也是不適用的冷离。
對(duì)上文的總結(jié)
其實(shí)剛剛所描述的三種分類策略并不是很嚴(yán)謹(jǐn)吵冒,因?yàn)槠渲袔追N總是會(huì)搭配著使用,所以在此也要選擇一個(gè)比較均衡的策略來實(shí)現(xiàn)Category屬性的綁定西剥。
建議的策略:
由于我們肯定會(huì)在interface 中提供生的property(由于沒有合成實(shí)現(xiàn)與ivar痹栖,在此稱為生的),所以這樣對(duì)于在外部訪問時(shí)和普通property相同瞭空。
由于缺乏的是實(shí)現(xiàn)以及可以存取的數(shù)據(jù)量揪阿,這里我們可以直接實(shí)現(xiàn)這些set與get。
set與get的實(shí)現(xiàn)可以通過 associatedObject 進(jìn)行對(duì)對(duì)象的存取操作咆畏。
好處: 這種操作由于提供了生的property南捂,所以在第三方的json轉(zhuǎn)model庫遍歷property時(shí)可以直接遍歷到,由于你手動(dòng)實(shí)現(xiàn)了set與get鳖眼,所以在遍歷后的KVC賦值時(shí)也能起到作用,保證了和普通成員變量的操作一致性嚼摩。
估計(jì)會(huì)有人看完結(jié)論后覺得:“ 我本來就是這么寫的啊钦讳,你寫這么多字到頭來得出的結(jié)論和我平時(shí)寫的也一樣≌砻妫”是的愿卒,我只能略表抱歉啦!