【iOS】有關Category我的理解

一匠璧、Category(類別揪阶,分類)的使用場景

  • 為已經(jīng)存在的類添加方法和屬性
  • 把不同的功能組織到不同的category
  • 模擬多繼承(另外模擬多繼承的還有protocol

1)給類擴展:類別或者繼承

category相比繼承的缺點

1.新的擴展方法還需要父類的實現(xiàn),與原方法重名患朱。若使用類別則會覆蓋鲁僚。所謂覆蓋其實也不是很準確,具體解釋見原理裁厅。
2.category不能添加實例變量冰沙,具體解釋見原理。
3.如果一個類有多個category执虹,不同的類別同時定義同一個方法拓挥,則調用哪個不一定,具體解釋見原理袋励。

category相比inherit的優(yōu)點

1.category可以修改原類基礎上擴展類的方法侥啤,實現(xiàn)私有類 方法擴展以及一些不能修改源碼類的擴展当叭,也就是說不會入侵原類的代碼結構,比繼承更為輕量級盖灸。
2.category可以將類的實現(xiàn)分散到多個文件蚁鳖。
3.category可以創(chuàng)建私有方法的前向引用:在類別里聲明一個類的私有方法,不需要實現(xiàn)赁炎,編譯不會報錯醉箕,運行時會調用私有方法。繼承不能繼承私有方法徙垫。
4.category可以向類添加非正式協(xié)議:因為Objective-C里面的類大都是NSObject的子類讥裤,所以在NSObject的類別里定義函數(shù),所有對象都能使用

2)添加屬性:runtime

直接在category.h中添加這時候會有warning

?? Property 'name' requires method 'setName:' to be defined - use @dynamic or provide a method implementation in this category

類別不會自動幫屬性生成getter/setter方法姻报,也不能生成帶下劃線的實例變量己英。我們可以用runtime關聯(lián)對象】去實現(xiàn)category唯一有的類添加新的屬性并生成getter/settet方法。

runtime我們提供了以下方法
參數(shù):  id object:被關聯(lián)的對象  
          const void *key:關聯(lián)的key吴旋,要求唯一
          id value:關聯(lián)的對象(如dog)
          objc_AssociationPolicy policy:內(nèi)存管理的策略

//關聯(lián)對象 
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
 
//獲取關聯(lián)的對象
id objc_getAssociatedObject(id object, const void *key)
 
//移除關聯(lián)的對象
void objc_removeAssociatedObjects(id object)

當對象被釋放時损肛,會根據(jù)這個策略來決定是否釋放關聯(lián)的對象,當策略是RETAIN/COPY時邮府,會釋放(release)關聯(lián)的對象,當是ASSIGN溉奕,將不會釋放褂傀。
值得注意的是,我們不需要主動調用removeAssociated來接觸關聯(lián)的對象加勤,如果需要解除指定的對象仙辟,可以使用setAssociatedObjectnil來實現(xiàn)。

舉個??

我為Student+teach.h類添加了個name屬性鳄梅,.h文件如下

#import "Student.h"

@interface Student (teach)

//不會生成添加屬性的getter和setter方法叠国,必須我們手動生成
@property (nonatomic, copy) NSString * name;

@end

.m文件如下

#import "Student+teach.h"
//導入頭文件
#import <objc/runtime.h>

@implementation Student (teach)
//定義關聯(lián)的key
static const char *key = "name";

//getter方法
- (NSString *)name {
    //根據(jù)key獲取關聯(lián)的值
    return objc_getAssociatedObject(self, key);
}

//setter方法
- (void)setName:(NSString *)name {
    //參數(shù)設置
    objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

運行測試代碼

   Student *xiaoming = [[Student alloc] init];
    xiaoming.name = @"xiaoming";
    NSLog(@"%@",xiaoming.name);

打印結果

2017-05-23 13:49:52.166 RuntimeTest[3009:178563] xiaoming
注:這里區(qū)分一下屬性和成員變量的區(qū)別
1.我聲明了一個屬性name
@property (nonatomic, copy) NSString * name;

會為我生成一個以下劃線為開頭的實例變量_name戴尸,也不需要寫@synthesize name粟焊;,會自動還是你歌城getter/setter方法孙蒙。那么在.m文件中可以直接使用_name實例變量项棠,也可以self.name通過屬性的getter/setter設置。

2.大括號
@interface MyViewController:UIViewController
{
 NSString *name;
}
@end

此時用點語法會提示,編輯器會提示讓你把.改為->挎峦。因為點語法是調用方法香追,為代碼里沒有getter/setter方法。

3.點語法的說明

如果點表達式出現(xiàn)在=左邊坦胶,則該屬性的setter的方法將會被調用透典。右邊則為getter方法晴楔。

4.synthesize的用法

@synthesize name = myname;
會自動生成實例變量myname而不是默認的_name,自動生成getter/setter方法,所以它的作用是指定實例變量名稱峭咒。

3)模擬多繼承:消息轉發(fā)税弃、delegate和protocol、類別

oc不支持多線程讹语,由于消息機制消息名字查找發(fā)生在運行時而非編譯時钙皮,多個基類可能導致二義性問題。模擬多線程有以下幾個方法: 消息轉發(fā)顽决,delegateprotocol短条,類別。

類別:

如上??才菠。

delegate和protocol:

委托協(xié)助主體完成操作任務茸时,將需要定制化的操作預留給委托對象來自定義實現(xiàn),類似子類化主體赋访。
除此之外可都,可以用作事件監(jiān)聽。

消息轉發(fā):

當向某對象發(fā)送消息但是runtime system在當前類和父類中都找不到該方法的實現(xiàn)時蚓耽,runtime不會立即報錯渠牲,而是有以下步驟,

模擬多線程.png

簡述流程:
1.動態(tài)方法解析:向當前類發(fā)送resolveInstanceMethod:的信號步悠,檢查是否像這個類動態(tài)添加了方法签杈。
相關鏈接:http://blog.csdn.net/haishu_zheng/article/details/12873151
2.快速消息發(fā)送:檢查這個類是否實現(xiàn)了forwardingTargetForSelector:方法,是吸納了就調用鼎兽。若返回值對象非空nil或者非self答姥,則向該返回對象重新發(fā)送信息。
利用快速消息轉發(fā)forwardingTargetForSelector:方法重寫谚咬,結合【類別】或者【類型強轉】可以實現(xiàn)多繼承方法

思考問題:如果將其類型轉成`id 鹦付,也可以編譯通過,并實現(xiàn)轉發(fā)择卦∏贸ぃ可是會帶來什么隱患呢?

3.標準信息發(fā)送:runtime發(fā)送methodSignatureForSelector:消息獲取Selector對應的方法簽名秉继。返回值非空則通過forwardInvocation:轉發(fā)消息潘明,返回值為空則向當前對象發(fā)送doesNotRecognizeSelector:消息,程序崩潰退出秕噪。
標準消息轉發(fā)需要重寫 methodSignatureForSelector: 和 forwardInvocation: 兩個方法即可钳降。

二、category的原理和特性

1)原理

runtime的源碼里可以找到_read_images函數(shù):

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) {
...
    // Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);

            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class's method lists (etc) if 
            // the class is realized. 
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }

            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }
...
}

里面提到了兩個比較重要的函數(shù):

  1. addUnattachedCategoryForClass()
  2. remethodizeClass()
  • addUnattachedCategoryForClass()主要是構建class和category的哈希表腌巾,如果不存在則生成遂填,如果存在則重新構建铲觉,主要是class到category的一個映射關系。
  • remethodizeClass調用了attachCategories方法吓坚,這個方法里面把category里的方法撵幽,屬性和協(xié)議列表都添加到相應的類里面,從本質來說礁击,調用類的方法和分類的方法都是一樣的盐杂。

2)category重名函數(shù)覆蓋

在看源碼的attachLists方法

 void attachLists(List* const * addedLists, uint32_t addedCount) {
      if (addedCount == 0) return;

      if (hasArray()) {
          // many lists -> many lists
          uint32_t oldCount = array()->count;
          uint32_t newCount = oldCount + addedCount;
          setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
          array()->count = newCount;
          memmove(array()->lists + addedCount, array()->lists, 
                  oldCount * sizeof(array()->lists[0]));
          memcpy(array()->lists, addedLists, 
                 addedCount * sizeof(array()->lists[0]));
      }
      else if (!list  &&  addedCount == 1) {
          // 0 lists -> 1 list
          list = addedLists[0];
      } 
      else {
          // 1 list -> many lists
          List* oldList = list;
          uint32_t oldCount = oldList ? 1 : 0;
          uint32_t newCount = oldCount + addedCount;
          setArray((array_t *)malloc(array_t::byteSize(newCount)));
          array()->count = newCount;
          if (oldList) array()->lists[addedCount] = oldList;
          memcpy(array()->lists, addedLists, 
                 addedCount * sizeof(array()->lists[0]));
      }
  }

第一個判斷語句中C函數(shù) memmove(dest, src, len)memcpy(des, src, size)完成了將舊函數(shù)數(shù)組后移空出位置,把新的函數(shù)數(shù)組添加到原數(shù)組空位的過程哆窿。
下一個判斷語句意思為如果list沒有值链烈,那么直接賦值給它
最后一個判斷語句是找到多個函數(shù)名一樣的挚躯,進行插入强衡。

綜上所述,category里新增的和原來類重名的函數(shù)將會被添加在原函數(shù)列表的前面码荔,并不是覆蓋漩勤。如果想要調用原函數(shù)的方法,只需要便利函數(shù)列表缩搅,調用最后一個來執(zhí)行越败。這就是category重名函數(shù)覆蓋,并且同個類多個category重名調用不確定的原因硼瓣。(有些文章指出執(zhí)行順序跟編譯順序有關)

3)Category和Extension的區(qū)別

1究飞、Extension在編譯期決議,它就是類的一部分巨双,在編譯期和頭文件里的@interface以及實現(xiàn)文件里的@implement一起形成一個完整的類噪猾,它伴隨類的產(chǎn)生而產(chǎn)生霉祸,亦隨之一起消亡筑累。Extension一般用來隱藏類的私有信息,你必須有一個類才能為這個類添加Extension丝蹭,所以你無法為系統(tǒng)的類比如NSString添加Extension慢宗。
2、Category則完全不一樣奔穿,它是在運行期決議的镜沽。
3、Extension可以添加屬性贱田、成員變量缅茉,而Category一般不可以。

總之男摧,就CategoryExtension的區(qū)別來看蔬墩,Extension可以添加實例變量译打,而Category是無法添加實例變量的。因為Category在運行期拇颅,對象的內(nèi)存布局已經(jīng)確定奏司,如果添加實例變量就會破壞類的內(nèi)部布局,這對編譯型語言來說是災難性的樟插。

4)Category不能添加成員變量

因為方法和屬性并不“屬于”類實例韵洋,而成員變量“屬于”類實例。我們所說的“類實例”概念黄锤,指的是一塊內(nèi)存區(qū)域搪缨,包含了isa指針和所有的成員變量。所以假如允許動態(tài)修改類成員變量布局猜扮,已經(jīng)創(chuàng)建出的類實例就不符合類定義了勉吻,變成了無效對象。但方法定義是在objc_class中管理的旅赢,不管如何增刪類方法齿桃,都不影響類實例的內(nèi)存布局,已經(jīng)創(chuàng)建出的類實例仍然可正常使用煮盼。

第一次發(fā)簡書文章短纵,如果所述有錯誤或者不明確的地方,望大家不吝賜教?? ~

參考:
美團技術文檔: http://tech.meituan.com/DiveIntoCategory.html

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末僵控,一起剝皮案震驚了整個濱河市香到,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌报破,老刑警劉巖悠就,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異充易,居然都是意外死亡梗脾,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門盹靴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炸茧,“玉大人,你說我怎么就攤上這事稿静∷蠊冢” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵改备,是天一觀的道長控漠。 經(jīng)常有香客問我,道長悬钳,這世上最難降的妖魔是什么盐捷? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任柬脸,我火速辦了婚禮,結果婚禮上毙驯,老公的妹妹穿的比我還像新娘倒堕。我一直安慰自己,他們只是感情好爆价,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布垦巴。 她就那樣靜靜地躺著,像睡著了一般铭段。 火紅的嫁衣襯著肌膚如雪骤宣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天序愚,我揣著相機與錄音憔披,去河邊找鬼。 笑死爸吮,一個胖子當著我的面吹牛芬膝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播形娇,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼锰霜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了桐早?” 一聲冷哼從身側響起癣缅,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哄酝,沒想到半個月后友存,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡陶衅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年屡立,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片万哪。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡侠驯,死狀恐怖抡秆,靈堂內(nèi)的尸體忽然破棺而出奕巍,到底是詐尸還是另有隱情,我是刑警寧澤儒士,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布的止,位于F島的核電站,受9級特大地震影響着撩,放射性物質發(fā)生泄漏诅福。R本人自食惡果不足惜匾委,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望氓润。 院中可真熱鬧赂乐,春花似錦、人聲如沸咖气。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽崩溪。三九已至浅役,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間伶唯,已是汗流浹背觉既。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留乳幸,地道東北人瞪讼。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像粹断,于是被迫代替她去往敵國和親尝艘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

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

  • 1.項目經(jīng)驗 2.基礎問題 3.指南認識 4.解決思路 ios開發(fā)三大塊: 1.Oc基礎 2.CocoaTouch...
    陽光的大男孩兒閱讀 4,983評論 0 13
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,139評論 30 470
  • 一 類別的簡介 在開發(fā)中有時會用到Category姿染,類別有三個作用: (1)可以將類的實現(xiàn)分散到多個不同文件或多個...
    々莫等閑々閱讀 442評論 0 0
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理背亥,服務發(fā)現(xiàn),斷路器悬赏,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • 本文目錄一.category簡介二.category和extension三.category真面目四.catego...
    boundlessocean閱讀 1,223評論 1 8