Category & Extension &關(guān)聯(lián)對象

Category

1、什么是Category?
category是Objective-C 2.0之后添加的語言特性,別人口中的分類、類別其實都是指的category。category的主要作用是為已經(jīng)存在的類添加方法。除此之外,apple還推薦了category的另外兩個使用場景。

可以把類的實現(xiàn)分開在幾個不同的文件里面。這樣做有幾個顯而易見的好處。

  • 可以減少單個文件的體積
  • 可以把不同的功能組織到不同的category里
  • 可以由多個開發(fā)者共同完成一個類
  • 可以按需加載想要的category
  • 聲明私有方法
    apple 的SDK中就大面積的使用了category這一特性。比如UIKit中的UIView。apple把不同的功能API進行了分類,這些分類包括UIViewGeometry、UIViewHierarchy、UIViewRendering等。

不過除了apple推薦的使用場景,廣大開發(fā)者腦洞大開,還衍生出了category的其他幾個使用場景:

  • 模擬多繼承(另外可以模擬多繼承的還有protocol)
  • 把framework的私有方法公開

2、category特點

  • category只能給某個已有的類擴充方法,不能擴充成員變量。
  • category中也可以添加屬性,只不過@property只會生成setter和getter的聲明,不會生成setter和getter的實現(xiàn)以及成員變量。
  • 如果category中的方法和類中原有方法同名,運行時會優(yōu)先調(diào)用category中的方法。也就是,category中的方法會覆蓋掉類中原有的方法。所以開發(fā)中盡量保證不要讓分類中的方法和原有類中的方法名相同。避免出現(xiàn)這種情況的解決方案是給分類的方法名統(tǒng)一添加前綴。比如category_。
  • 如果多個category中存在同名的方法,運行時到底調(diào)用哪個方法由編譯器決定,最后一個參與編譯的方法會被調(diào)用。

3、調(diào)用優(yōu)先級
分類(category) > 本類 > 父類。即,優(yōu)先調(diào)用cateory中的方法,然后調(diào)用本類方法,最后調(diào)用父類方法。
注意:category是在運行時加載的,不是在編譯時。

4、為什么category不能添加成員變量?
Objective-C類是由Class類型來表示的,它實際上是一個指向objc_class結(jié)構(gòu)體的指針。它的定義如下:
typedef struct objc_class *Class;
objc_class結(jié)構(gòu)體的定義如下:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                       OBJC2_UNAVAILABLE;  // 父類
    const char *name                        OBJC2_UNAVAILABLE;  // 類名
    long version                            OBJC2_UNAVAILABLE;  // 類的版本信息,默認(rèn)為0
    long info                               OBJC2_UNAVAILABLE;  // 類信息,供運行期使用的一些位標(biāo)識
    long instance_size                      OBJC2_UNAVAILABLE;  // 該類的實例變量大小
    struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 該類的成員變量鏈表
    struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定義的鏈表
    struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法緩存
    struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;

在上面的objc_class結(jié)構(gòu)體中,ivars是objc_ivar_list(成員變量列表)指針;methodLists是指向objc_method_list指針的指針。在Runtime中,objc_class結(jié)構(gòu)體大小是固定的,不可能往這個結(jié)構(gòu)體中添加數(shù)據(jù),只能修改。所以ivars指向的是一個固定區(qū)域,只能修改成員變量值,不能增加成員變量個數(shù)。methodList是一個二維數(shù)組,所以可以修改*methodLists的值來增加成員方法,雖沒辦法擴展methodLists指向的內(nèi)存區(qū)域,卻可以改變這個內(nèi)存區(qū)域的值(存儲的是指針)。因此,可以動態(tài)添加方法,不能添加成員變量。

5、category中能添加屬性嗎?
Category不能添加成員變量(instance variables),那到底能不能添加屬性(property)呢?

這個我們要從Category的結(jié)構(gòu)體開始分析:

typedef struct category_t {
    const char *name;  //類的名字
    classref_t cls;  //類
    struct method_list_t *instanceMethods;  //category中所有給類添加的實例方法的列表
    struct method_list_t *classMethods;  //category中所有添加的類方法的列表
    struct protocol_list_t *protocols;  //category實現(xiàn)的所有協(xié)議的列表
    struct property_list_t *instanceProperties;  //category中添加的所有屬性
} category_t;

從Category的定義也可以看出Category的可為(可以添加實例方法,類方法,甚至可以實現(xiàn)協(xié)議,添加屬性)和不可為(無法添加實例變量)。

但是為什么網(wǎng)上很多人都說Category不能添加屬性呢?
實際上,Category實際上允許添加屬性的,同樣可以使用@property,但是不會生成_變量(帶下劃線的成員變量),也不會生成添加屬性的getter和setter方法的實現(xiàn),所以,盡管添加了屬性,也無法使用點語法調(diào)用getter和setter方法(實際上,點語法是可以寫的,只不過在運行時調(diào)用到這個方法時候會報方法找不到的錯誤,如下圖)。但實際上可以使用runtime去實現(xiàn)Category為已有的類添加新的屬性并生成getter和setter方法。

注意的有兩點:
1)、category的方法沒有“完全替換掉”原來類已經(jīng)有的方法,也就是說如果category和原來類都有methodA,那么category附加完成之后,類的方法列表里會有兩個methodA。
2)、category的方法被放到了新方法列表的前面,而原來類的方法被放到了新方法列表的后面,這也就是我們平常所說的category的方法會“覆蓋”掉原來類的同名方法,這是因為運行時在查找方法的時候是順著方法列表的順序查找的,它只要一找到對應(yīng)名字的方法,就會罷休,殊不知后面可能還有一樣名字的方法。

Extension

1、 什么是extension
extension被開發(fā)者稱之為擴展、延展、匿名分類。extension看起來很像一個匿名的category,但是extension和category幾乎完全是兩個東西。和category不同的是extension不但可以聲明方法,還可以聲明屬性、成員變量。extension一般用于聲明私有方法,私有屬性,私有成員變量。

2、 extension的存在形式
category是擁有.h文件和.m文件的東西。但是extension不然。extension只存在于一個.h文件中,或者extension只能寄生于一個類的.m文件中。比如,viewController.m文件中通常寄生這么個東西,其實這就是一個extension:

@interface ViewController ()
@end
當(dāng)然我們也可以創(chuàng)建一個單獨的extension文件

注意:extension常用的形式并不是以一個單獨的.h文件存在,而是寄生在類的.m文件中。

三)category和extension的區(qū)別
就category和extension的區(qū)別來看,我們可以推導(dǎo)出一個明顯的事實,extension可以添加實例變量,而category是無法添加實例變量的(因為在運行期,對象的內(nèi)存布局已經(jīng)確定,如果添加實例變量就會破壞類的內(nèi)部布局,這對編譯型語言來說是災(zāi)難性的)。

  • extension在編譯期決議,它就是類的一部分,但是category則完全不一樣,它是在運行期決議的。extension在編譯期和頭文件里的@interface以及實現(xiàn)文件里的@implement一起形成一個完整的類,它、extension伴隨類的產(chǎn)生而產(chǎn)生,亦隨之一起消亡。
  • extension一般用來隱藏類的私有信息,你必須有一個類的源碼才能為一個類添加extension,所以你無法為系統(tǒng)的類比如NSString添加extension,除非創(chuàng)建子類再添加extension。而category不需要有類的源碼,我們可以給系統(tǒng)提供的類添加category。
  • extension可以添加實例變量,而category不可以。
  • extension和category都可以添加屬性,但是category的屬性不能生成成員變量和getter、setter方法的實現(xiàn)。

關(guān)聯(lián)對象

關(guān)聯(lián)對象是指某個OC對象通過一個唯一的key連接到一個類的實例上。
舉個例子:xiaoming是Person類的一個實例,他的dog(一個OC對象)通過一根繩子(key)被他牽著散步,這可以說xiaoming和dog是關(guān)聯(lián)起來的,當(dāng)然xiaoming可以牽著多個dog。

設(shè)置關(guān)聯(lián)對象
void objc_setAssociatedObject(id object,const void*key, id value, objc_AssociationPolicy policy)

獲取關(guān)聯(lián)對象
void objc_getAssociatedObject(id object, const void *key)
    
移除關(guān)聯(lián)對象
void objc_removeAssociatedObjects(id object)

參數(shù)
id object 被關(guān)聯(lián)的對象(如xiaoming)
const void*key 關(guān)聯(lián)的key
 id value 關(guān)聯(lián)對象(如dog)
objc_AssociationPolicy policy 內(nèi)存管理策略

當(dāng)對象被釋放時,會根據(jù)這個策略來決定是否釋放關(guān)聯(lián)的對象,當(dāng)策略是RETAIN/COPY時,會釋放(release)關(guān)聯(lián)的對象,當(dāng)是ASSIGN,將不會釋放。
值得注意的是,我們不需要主動調(diào)用removeAssociated來接觸關(guān)聯(lián)的對象,如果需要解除指定的對象,可以使用setAssociatedObject置nil來實現(xiàn)。

應(yīng)用實例(Category添加屬性并生成getter和setter方法)
如何給NSArray添加一個屬性(不能使用繼承)?
我們現(xiàn)在為NSArray增加一個blog屬性:

NSArray+MyCategory.h文件:

#import <Foundation/Foundation.h>
@interface NSArray (MyCategory)
// 不會生成添加屬性的getter和setter方法,必須手動生成
@property(nonatomic,copy) NSString *blog;
@end

NSArray+MyCategory.m

#import "NSArray+MyCategory.h"
#import <objc/runtime.h>

@implementation NSArray (MyCategory)
// 定義關(guān)聯(lián)key
static const char *key = "blog";

/**
 blog的getter和setter方法
 */
- (NSString *)blog{
    // 根據(jù)關(guān)聯(lián)的key,獲取關(guān)聯(lián)的值
    return objc_getAssociatedObject(self, key);
}
- (void)setBlog:(NSString *)blog{
   
    objc_setAssociatedObject(self, key, blog, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

調(diào)用

 NSArray *myarray = [[NSArray alloc] init];
 myarray.blog = @"123";
 NSLog(@"%@",myarray.blog);
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • (一)Category category是Objective-C 2.0之后添加的語言特性,別人口中的分類、類別其...
    小李龍彪閱讀 3,037評論 0 10
  • 自己做筆錄 用來后來回顧。。(一) Category1、什么是categorycategory是objective...
    41c48b8df394閱讀 3,778評論 1 8
  • 一 類別的簡介 在開發(fā)中有時會用到Category,類別有三個作用: (1)可以將類的實現(xiàn)分散到多個不同文件或多個...
    々莫等閑々閱讀 494評論 0 0
  • 面試題參考1 : 面試題[http://www.cocoachina.com/ios/20150803/12872...
    江河_ios閱讀 1,825評論 0 4
  • 今天是四個實習(xí)生在報社實習(xí)的最后一天。 他們是當(dāng)?shù)匾凰究茖W(xué)校文學(xué)院的大四學(xué)生。想必平日在學(xué)校里要么表現(xiàn)很優(yōu)秀,要...
    ld熊壯壯閱讀 267評論 0 1

友情鏈接更多精彩內(nèi)容