刨根究底之Category

起因
我們項目中很多公用的類都封裝在framework中富雅,以便iPhone裆熙、iPad共同調(diào)用邓夕。某些邏輯不一樣的東西我們會在主工程用category實現(xiàn)掰茶,然而category默認(rèn)是不能定義屬性的萤厅。我們來看看怎么在category中添加屬性
AssociatedObject
iOS有些基礎(chǔ)的朋友應(yīng)該會知道橄抹,runtime里有objc_setAssociatedObject和objc_getAssociatedObject兩個方法,可以將屬性掛到Object上:

@interface Model (Property)
@property (nonatomic, strong)NSString *name;
@end

@implementation Model (Property)
static void *kName = &kName;
- (void)setName:(NSString *)name{ objc_setAssociatedObject(self, kName, name, OBJC_ASSOCIATION_COPY_NONATOMIC);}
- (NSString *)name{ return objc_getAssociatedObject(self, kName);}
@end

重寫property的setter和getter方法惕味,在setter的時候調(diào)用objc_setAssociatedObject將屬性掛到self上楼誓,在getter的時候,從self身上將屬性取出來赦拘。

這樣的實現(xiàn)已經(jīng)是可以用了慌随,事實上大多數(shù)為category添加屬性的代碼都是這樣寫的。不過我還是太懶躺同,每添加一個屬性阁猜,就要寫這么大一堆代碼。想想要是加上十個八個屬性蹋艺,頓時整個人都覺得不好了...

class_addMethod
既然能用runtime動態(tài)將屬性掛在class上剃袍,我們也可以用runtime動態(tài)將setter和getter方法插入到class中。runtime提供了class_addMethod方法動態(tài)插入method

2.jpg

class_addMethod需要4個參數(shù)捎谨。class可以通過[self class]獲取民效,SEL可以通過property的name拼接出對應(yīng)的SEL,types由于參數(shù)的類型固定涛救,所以也是可以直接確定畏邢。但是IMP怎么辦?
imp_implementationWithBlock
說到IMP检吆,我們先來了解一下IMP是個什么東西

3.jpg

IMP是一個函數(shù)指針舒萎,指向相應(yīng)的函數(shù)實現(xiàn),函數(shù)一般會有2個默認(rèn)參數(shù):id類型的self和SEL類型的_cmd蹭沛。平時我們之所以能在OC函數(shù)中調(diào)用self臂寝,也是因為函數(shù)中有隱藏起來了的self參數(shù)
翻閱runtime的文檔,我們找到了通過block轉(zhuǎn)換成IMP的API:

4.jpg

封裝
一切都準(zhǔn)備就緒了摊灭,那我們就來封裝一個動態(tài)添加屬性的方法吧咆贬,為了簡化流程,我們暫時先只考慮id類型的屬性帚呼。

+ (void)addObjectProperty:(NSString *)name
{ 
//1. 通過class的指針和property的name掏缎,創(chuàng)建一個唯一的key NSString *key = [NSString stringWithFormat:@"%p_%@",self,name]; 
//2. 用block實現(xiàn)setter方法
 id setblock = ^(id self,id value){ objc_setAssociatedObject(self, (__bridge void *)key, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }; 
//3. 將block的實現(xiàn)轉(zhuǎn)化為IMP 
IMP imp = imp_implementationWithBlock(setblock);
 //4. 用name拼接出setter方法 
NSString *selString = [self setMethodNameWithProperty:name]; 
//5. 將setter方法加入到class中 
BOOL result = class_addMethod([self class], NSSelectorFromString(selString), imp, "v@:@"); 
//6. getter 
id getBlock = ^id(id self){ return objc_getAssociatedObject(self, (__bridge void*)key); };
 IMP getImp = imp_implementationWithBlock(getBlock); 
result = class_addMethod([self class], NSSelectorFromString(name), getImp, "@@:");}

通過class的指針和property的name,創(chuàng)建一個唯一的key。用來在AssociatedObject的時候存取屬性御毅。
在block中實現(xiàn)setter方法
通過block創(chuàng)建IMP
通過name將setter的方法名拼接出來根欧,-setMethodNameWithProperty是自己寫的方法,這里沒有貼出來
將setter方法加入到class中端蛆。其中@"v@:@":v表示空凤粗,setter的返回值為空。@表示id類型今豆,第一個參數(shù)嫌拣,也就是self為id類型。:表示SEL類型呆躲,第二個參數(shù)為method的selector异逐。@表示id類型,第三個參數(shù)也就是setter方法真正要傳入的參數(shù)為id類型插掂。
后面是相應(yīng)的getter方法灰瞻,與setter方法類似

使用
這時候,再也不用擔(dān)心有很多property辅甥,寫一堆重復(fù)的代碼酝润。我們只需要調(diào)一個函數(shù)就可以將在category插入屬性

@interface Model (Property)
@property (nonatomic, strong)NSString *name;
@property (nonatomic, strong)NSURL *URL;
@property (nonatomic, strong)NSDate *date;
@end

@implementation Model (Property)
+ (void)load{ [self addObjectProperty:@"name"]; 
[self addObjectProperty:@"URL"];
 [self addObjectProperty:@"date"];}
@end

+load方法在程序運行之前會調(diào)用,不用擔(dān)心在用的時候璃弄,property還未插入進(jìn)去要销。所有的category的+load方法系統(tǒng)都會自動調(diào)用。也不用擔(dān)心+load方法在category中被覆蓋夏块。
開始考慮過在+initialize中使用疏咐,不過由于+initialize一個class只會調(diào)用一次,多個category的時候會有覆蓋脐供。所以+load中使用是最好的選擇

你以為這樣就ok了么浑塞?過幾天有用戶反饋說App啟動的時候有點卡啊,因為+load方法是在app啟動的時候調(diào)用的政己,里面執(zhí)行的代碼越多酌壕,App啟動越慢,(說得有點夸張匹颤,實際這點代碼影響不了什么)。我們知道除了+load之外托猩,還有一個+initialize方法印蓖,+initialize會在第一次使用這個類的時候調(diào)用,我們完全可以在+initialize中添加屬性京腥。然而+initialize有個最大的問題就是赦肃,他跟普通方法一樣,當(dāng)有多個category實現(xiàn)的時候,會發(fā)生覆蓋他宛,系統(tǒng)只會調(diào)用一個Category中的+initialize船侧,那該怎么辦呢?
消除Category同名方法覆蓋
sunnyxx大神在objc category的秘密里介紹過厅各,category的同名方法覆蓋并不是真的其他同名方法就消失了镜撩,而是因為系統(tǒng)調(diào)用方法的時候根據(jù)方法名在method_list中查找方法,找到第一個名字匹配的方法之后就不繼續(xù)往下找了队塘。所以每次調(diào)用的都是method_list中最前面的同名方法袁梗。實際其他同名方法還在method_list中so...我們可以根據(jù)selector查找到所有的同名method,然后調(diào)用:

static inline void __invoke_all_method(id self, SEL selecotr)
{ 
//1. 根據(jù)self憔古,獲取
class Class class = object_getClass(self); 
//2. 獲取方法列表 
uint count; 
Method *methodList = class_copyMethodList(class, &count);
 //3. 遍歷方法列表 
for (int i = 0; i < count; i++)
 { Method method = methodList[i]; 
//4. 根據(jù)SEL查找方法 
if (!sel_isEqual(selecotr, method_getName(method))) 
{ continue; }
 //5. 獲取方法的實現(xiàn)
 IMP implement = method_getImplementation(method); 
//6. 直接調(diào)用方法的實現(xiàn)
 ((void(*)(id,SEL))implement)(self, selecotr); }}
+ (void)invokeAllClassMethodWithSelector:(SEL)selector
{ 
  __invoke_all_method(self, selector);
}

根據(jù)剛剛介紹的原理遮怜,我們封裝了一個通過selector調(diào)用所有同名method的方法。

根據(jù)self鸿市,獲取class锯梁,如果self是實例方法的self,這里獲取的是普通的class焰情,如果self是類方法的self陌凳,這里獲取的是metaClass。實例方法存放在普通class中烙样,類方法存放在metaClass中冯遂。了解更多請看iOS開發(fā)RunTime之函數(shù)調(diào)用

通過class_copyMethodList獲取class的方法列表。如果class傳的是metaClass谒获,獲取的是類方法的方法列表蛤肌,如果class是普通class,獲取的是實例方法的方法列表批狱。
遍歷methodList
根據(jù)SEL查找method
獲取IMP
直接調(diào)用IMP

在系統(tǒng)的+initialize中裸准,我們用invokeAllClassMethodWithSelector調(diào)用自定義的+categoryInitialize。這時候赔硫,在category的+categoryInitialize中添加屬性炒俱,就不怕Category覆蓋了

@implementation Model
+ (void)initialize{
 [self invokeAllClassMethodWithSelector:@selector(categoryInitialize)];}
@end

@implementation Model (Property1)
+ (void)categoryInitialize{ [self addBasicProperty:@"point" encodingType:@encode(CGPoint)];
 [self addBasicProperty:@"myRect" encodingType:@encode(CGRect)];}
@end

@implementation Model (Property2)
+ (void)categoryInitialize{ [self addBasicProperty:@"f" encodingType:@encode(float)]; 
[self addBasicProperty:@"a" encodingType:@encode(int)];}
@end

Extension
文章主要為了說明思路,很多代碼沒貼出來爪膊。也沒考慮接口設(shè)計和不是id類型的問題权悟。如果想在項目中使用這個方法。大家可以去我的github上下載完整的代碼推盛。LcCategoryProperty

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末峦阁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子耘成,更是在濱河造成了極大的恐慌榔昔,老刑警劉巖驹闰,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異撒会,居然都是意外死亡嘹朗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門诵肛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屹培,“玉大人,你說我怎么就攤上這事曾掂”拱” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵珠洗,是天一觀的道長溜歪。 經(jīng)常有香客問我,道長许蓖,這世上最難降的妖魔是什么蝴猪? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮膊爪,結(jié)果婚禮上自阱,老公的妹妹穿的比我還像新娘。我一直安慰自己米酬,他們只是感情好沛豌,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著赃额,像睡著了一般加派。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上跳芳,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天芍锦,我揣著相機(jī)與錄音,去河邊找鬼飞盆。 笑死娄琉,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吓歇。 我是一名探鬼主播孽水,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼城看!你這毒婦竟也來了女气?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤析命,失蹤者是張志新(化名)和其女友劉穎主卫,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鹃愤,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡簇搅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了软吐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瘩将。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖凹耙,靈堂內(nèi)的尸體忽然破棺而出姿现,到底是詐尸還是另有隱情,我是刑警寧澤肖抱,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布备典,位于F島的核電站,受9級特大地震影響意述,放射性物質(zhì)發(fā)生泄漏提佣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一荤崇、第九天 我趴在偏房一處隱蔽的房頂上張望拌屏。 院中可真熱鬧,春花似錦术荤、人聲如沸倚喂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽端圈。三九已至,卻和暖如春带兜,著一層夾襖步出監(jiān)牢的瞬間枫笛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工刚照, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留刑巧,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓无畔,卻偏偏與公主長得像啊楚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子浑彰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉恭理,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,679評論 0 9
  • 起因 我們項目中很多公用的類都封裝在framework中,以便iPhone郭变、iPad共同調(diào)用颜价。某些邏輯不一樣的東西...
    小笨狼閱讀 7,078評論 22 71
  • 1涯保、category和extension的區(qū)別 category是分類,可以為類增加自定義方法 extension...
    大猿媛閱讀 361評論 1 1
  • 今日清明節(jié)周伦,這是一個既有祭掃新墳生離死別的悲酸淚夕春,也有踏青游玩的歡笑聲的節(jié)日!朋友圈都是曬的春色無限专挪,歡送笑...
    胡永群閱讀 735評論 0 49
  • 精神文化方面的差距永比金錢上帶來的差距更認(rèn)人難受及志。 世間到底是怎么了,身邊越腐朽的人充滿負(fù)能量的人會拉關(guān)系的人寨腔,似...
    儒湘閱讀 151評論 0 2