1. 程式人生 > IOS開發 >【iOS面試糧食】OC語言—Category(分類)和類擴充套件(extension)、關聯物件

【iOS面試糧食】OC語言—Category(分類)和類擴充套件(extension)、關聯物件

本文章將記錄有關 Category(分類)和類擴充套件(extension)、關聯物件的特性,如有錯誤歡迎指出~

Category(分類)

分類的應用在App的開發中是非常廣泛的,它可以動態地為已有類新增新行為。

我們平常都是使用分類來對系統的類封裝一些小功能,如NSString判空處理等,可以看下 ibireme大神開源的這個庫YYCategories,都是針對系統的類使用分類拓展的小功能,很實用。再來看看業界聞名的空白頁框架DZNEmptyDataSet,它就是通過對 UIScrollView使用分類功能,非常完美、無侵入的解決了無資料時,避免白屏的尷尬,改善使用者體驗。真的是居家旅行必備之利器,強大~

隨著業務的增長,.m檔案會越來越膨脹,膨脹到你開始撓頭。我們也可以利用分類的特性,將類的實現分開在幾個不同的檔案裡面,好處顯而易見:

  • 減少單個檔案的體積
  • 把不同的功能分配到不同的分類裡,便於管理
  • 可以按需載入想要的分類

通常,我會新增一些 AppDelegate的分類,比如AppDelegate+Services,專門用來初始化各種第三方等等。

Category 的結構

Objective-C中,所有的類和物件在runtime層都是用struct表示的,category也是如此,它的結構體為category_t

typedef struct category_t {
    const char *name;		                	//	類名
    classref_t cls;				        //	類
    struct method_list_t *instanceMethods;		//	所有給類新增的例項方法的列表
    struct method_list_t *classMethods;			//	所有新增的類方法的列表
    struct protocol_list_t *protocols;			//	實現的所有協議的列表
    struct property_list_t *instanceProperties;		//	新增的所有屬性
} category_t;
複製程式碼

從結構體可以看出,分類能

  • 給類新增例項方法 (instanceMethod)
  • 給類新增類方法 (classMethod)
  • 實現協議 (protocol)
  • 新增屬性 (instancePropertie)

但是不能新增例項變數,即無法自動生成例項變數的setter和getter方法。當然,我們可以通過關聯物件來實現分類對例項變數的新增,請看後面章節~

Category的方法會“覆蓋”掉原來類的同名方法?

這個問題的分析,可以看下美圖技術團隊的這篇文章,很詳細。這裡只記錄一下總結:

  • Category的方法沒有“完全替換掉”原來類已經有的方法,也就是說如果Category和原來類都有methodA

    ,那麼Category附加完成之後,類的方法列表裡會有兩個methodA

  • Category的方法被放到了新方法列表的前面,而原來類的方法被放到了新方法列表的後面,這也就是我們平常所說的Category的方法會“覆蓋”掉原來類的同名方法,這是因為執行時在查詢方法的時候是順著方法列表的順序查詢的,它只要一找到對應名字的方法,很開心的返回了,不會在理會後面的同名方法。

  • 同名方法的呼叫,是根據編譯順序決定的,對於“覆蓋”掉的方法,會先找到最後一個編譯的category裡的對應方法。可檢視專案的 Build Phases -> Compile Sources,位置越往後,越晚編譯。

類擴充套件(extension)

extension的別名有很多,擴充套件、延展、匿名分類。它就是類的一部分,在編譯期和標頭檔案裡的@interface以及實現檔案裡的@implement一起形成一個完整的類,它伴隨類的產生而產生,亦隨之一起消亡。在viewController.m檔案中它長這個樣子:

@interface ViewController ()
@end
複製程式碼

是不是特別的熟悉~

沒錯,它看起來很像一個匿名的category。我們一般用來宣告私有方法,私有屬性和私有成員變數。

extension的應用很簡單, 我們基本天天都在用。需要注意的點是它和category的區別:

  • extension 在編譯期決議, category在執行期決議

關聯物件(Associated Object)

如上所述,category是無法為分類新增例項變數的,但是在實際開發中往往需要在分類中新增和物件關聯的值。這時我們可以利用runtime的特性,通過關聯物件來實現為分類新增例項變數的功能。

關聯物件是指某個物件通過一個唯一的key連線到一個類的例項上。

看下runtime提供給我們的3個API方法:

// 關聯物件,傳入 nil 則可以移除已有的關聯物件
void objc_setAssociatedObject(id object,const void *key,id value,objc_AssociationPolicy policy)
  
// 獲取關聯的物件
id objc_getAssociatedObject(id object,const void *key)
  
// 移除關聯的物件。這個方法會移除一個物件的所有關聯物件,將該物件恢復成“原始”狀態。這樣做就很有可能把別人新增的關聯物件也一併移除,所以我們通常用不上這個方法。一般的做法是通過給 objc_setAssociatedObject 方法傳入 nil 來移除某個已有的關聯物件。
void objc_removeAssociatedObjects(id object)
複製程式碼

引數說明:

  • id object:被關聯的物件
  • const void *key:關聯的key,要求唯一。一般用 selector ,使用 getter 方法的名稱作為 key 值。
  • id value:關聯的物件
  • objc_AssociationPolicy policy:記憶體管理的策略

objc_AssociationPolicy的列舉值和相關說明:

typedef OBJC_ENUM(uintptr_t,objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,// 指定一個弱引用相關聯的物件
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,// 指定相關物件的強引用,非原子性
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,// 指定相關的物件被複制,非原子性
    OBJC_ASSOCIATION_RETAIN = 01401,// 指定相關物件的強引用,原子性
    OBJC_ASSOCIATION_COPY = 01403           // 指定相關的物件被複制,原子性   
};
複製程式碼

在絕大多數情況下,我們都會使用 OBJC_ASSOCIATION_RETAIN_NONATOMIC 的關聯策略,這可以保證我們持有關聯物件。

看一下簡單的應用:

ViewController的分類 ViewController+AssociatedObjects新增一個字串型別的成員變數。

@interface ViewController (AssociatedObjects)

@property (strong,nonatomic) NSString *associatedObject;

@end

@implementation ViewController (AssociatedObjects)


- (NSString *)associatedObject {
    return objc_getAssociatedObject(self,_cmd);
}

- (void)setassociatedObject:(NSString *)associatedObject {
    objc_setAssociatedObject(self,@selector(associatedObject),associatedObject,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end
複製程式碼

如果想了解一下關聯物件底層的實現原理,可以看下雷純峰大神Objective-C Associated Objects 的實現原理,這裡只記錄一些總結:

Q : 關聯物件被儲存在什麼地方,是不是存放在被關聯物件本身的記憶體中?

A : 關聯物件與被關聯物件本身的儲存並沒有直接的關係,它是儲存在單獨的雜湊表中的。所有的關聯物件都由AssociationsManager管理,它是由一個靜態AssociationsHashMap來儲存所有的關聯物件的。這相當於把所有物件的關聯物件都存在一個全域性map裡面。而map的的key是這個物件的指標地址(任意兩個不同物件的指標地址一定是不同的),而這個map的value又是另外一個AssociationsHashMap,裡面儲存了關聯物件的key對。

Q : 關聯物件的生命週期是怎樣的,什麼時候被釋放,什麼時候被移除?

A : 關聯物件的釋放時機與移除時機並不總是一致。

參考

深入理解Objective-C:Category

Objective-C Associated Objects 的實現原理