iOS 分類(Category)知識點總結

1.Category是什么?重寫一個類的方法用繼承好還是分類好?

? ?Category是類別;一般情況用分類好,用Category去重寫類的方法,僅對本Category有效,不會影響到其他類與原有類的關系。

category好處:

可以減少單個文件的體積

可以把不同的功能組織到不同的category里

可以由多個開發者共同完成一個類

可以按需加載想要的category

聲明私有方法

category特點

category只能給某個已有的類擴充方法,不能擴充成員變量。

category中也可以添加屬性,只不過@property只會生成setter和getter的聲明,不會生成setter和getter的實現以及成員變量。

如果category中的方法和類中原有方法同名,運行時會優先調用category中的方法。也就是,category中的方法會覆蓋掉類中原有的方法。所以開發中盡量保證不要讓分類中的方法和原有類中的方法名相同。避免出現這種情況的解決方案是給分類的方法名統一添加前綴。比如category_。

如果多個category中存在同名的方法,運行時到底調用哪個方法由編譯器決定,最后一個參與編譯的方法會被調用。

2.Category的實現原理或者本質 ?

? ?Category編譯之后的底層結構是struct category_t,里面存儲著分類的對象方法、類方法、屬性、協議信息

? ? 在程序運行的時候,runtime會將Category的數據合并到類信息中(類對象、元類對象中)

? ? 分類的實現原理是將category中的方法,屬性,協議數據放在category_t結構體中,然后將結構體內的方法列表拷貝到類對象的方法列表中。?

3.Category的加載處理過程是什么?

? ?通過Runtime加載某個類的所有Category數據

? ? 把所有Category的方法、屬性、協議數據,合并到一個大數組中

? ? 后面參與編譯的Category數據,會在數組的前面

? ? 將合并后的分類數據(方法、屬性、協議),插入到類原來數據的前面

可以理解為:

? ? 把 category 的實例方法、協議以及屬性添加到類上。

? ? 把 category 的類方法和協議添加到類的 metaclass 上。

其中需要注意的是:

category 的方法沒有「完全替換掉」原來類已經有的方法,也就是說如果 category 和原來類都有 methodA,那么 category 附加完成之后,類的方法列表里會有兩個 methodA。

category 的方法被放到了新方法列表的前面,而原來類的方法被放到了新方法列表的后面,這也就是我們平常所說的category 的方法會「覆蓋」掉原來類的同名方法,這是因為運行時在查找方法的時候是順著方法列表的順序查找的,它只要一找到對應名字的方法,就會返回,不會管后面可能還有一樣名字的方法。

4.Category為什么不能添加成員變量?

Category 可以給類增加方法和屬性,但是并不會自動生成成員變量及set/get方法。因為category_t結構體中并不存在成員變量。我們知道成員變量是存放在實例對象中的,并且編譯的那一刻就已經決定好了。而分類是在運行時才去加載的。那么我們就無法再程序運行時將分類的成員變量中添加到實例對象的結構體中。因此分類中不可以添加成員變量。

在 Objective-C 提供的 runtime 函數中,確實有一個 class_addIvar() 函數用于給類添加成員變量,但是閱讀過蘋果的官方文檔的人應該會看到:

This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.


大概的意思說,這個函數只能在“構建一個類的過程中”調用。當編譯類的時候,編譯器生成了一個實例變量內存布局 ivar layout,來告訴運行時去那里訪問類的實例變量們,一旦完成類定義,就不能再添加成員變量了。經過編譯的類在程序啟動后就被 runtime 加載,沒有機會調用 addIvar。程序在運行時動態構建的類需要在調用 objc_registerClassPair 之后才可以被使用,同樣沒有機會再添加成員變量。

不能為一個類動態的添加成員變量,可以給類動態增加方法和屬性。因為方法和屬性并不“屬于”類實例,而成員變量“屬于”類實例。我們所說的“類實例”概念,指的是一塊內存區域,包含了isa指針和所有的成員變量。所以假如允許動態修改類成員變量布局,已經創建出的類實例就不符合類定義了,變成了無效對象。但方法定義是在objc_class中管理的,不管如何增刪類方法,都不影響類實例的內存布局,已經創建出的類實例仍然可正常使用。

5.Category中有load方法嗎?load方法是什么時候調用的?load 方法能繼承嗎?

在類和 category 中都可以有?+load方法,load方法在程序啟動裝載類信息的時候就會調用。load方法可以繼承。調用子類的load方法之前,會先調用父類的load方法.

6.在類的?+load方法調用的時候,我們可以調用 category 中聲明的方法么?

可以調用,因為附加 category 到類的工作會先于?+load方法的執行。

7.這么些個+load方法,調用順序是咋樣的呢?

+load的執行順序是先類,后 category,而 category 的+load?執行順序是根據編譯順序決定的。雖然對于?+load的執行順序是這樣,但是對于「覆蓋」掉的方法,則會先找到最后一個編譯的 category 里的對應方法。

子類的+load方法會在它的所有父類的+load方法之后執行,而分類的+load方法會在它的主類的+load方法之后執行,但是不同的類之間的+load方法的調用順序是不確定的,在Compile Sources中,文件的引入的先后順序決定不同的類之間的+load方法的調用順序。

注意:+load方法調用時,如果調用別的類的方法,且該方法依賴于那個類的load方法進行初始化設置,那么必須確保那個類的+load方法已經調用了,比如在Other類中調用Child類里面的方法,則打印出的字符串就為null。


Compile Sources 引入文件列表

8.怎么調用到原來類中被 category 覆蓋掉的方法?

對于這個問題,我們已經知道 category 其實并不是完全替換掉原來類的同名方法,只是 category 在方法列表的前面而已,所以我們只要順著方法列表找到最后一個對應名字的方法,就可以調用原來類的方法

9.load、initialize的區別,以及它們在category重寫的時候的調用的次序。

區別在于調用方式和調用時刻?

參考https://sunjinshuai.github.io/2016/08/16/iOS%E4%B9%8B-load%E5%92%8C-initialize%E7%9A%84%E5%8C%BA%E5%88%AB/

調用方式:load是根據函數地址直接調用,initialize是通過objc_msgSend調用?

調用時刻:load是runtime加載類、分類的時候調用(只會調用1次),initialize是類第一次接收到消息的時候調用,每一個類只會initialize一次(父類的initialize方法可能會被調用多次)

調用順序:

先調用類的load方法,先編譯那個類,就先調用load。在調用load之前會先調用父類的load方法。分類中load方法不會覆蓋本類的load方法,先編譯的分類優先調用load方法。

initialize先初始化父類,之后再初始化子類。如果子類沒有實現+initialize,會調用父類的+initialize(所以父類的+initialize可能會被調用多次),如果分類實現了+initialize,就覆蓋類本身的+initialize調用。

參考:http://blog.leichunfeng.com/blog/2015/05/02/objective-c-plus-load-vs-plus-initialize/

10.category是如何為一個類添加屬性的

利用關聯對象

#import

"MyClass.h"

@interface MyClass (Category1)

@property(nonatomic,copy)NSString*name;

@end

#import "MyClass+Category1.h"

#import <objc/runtime.h>

@implementation MyClass (Category1)

+ (void)load

{

? ? NSLog(@"%@",@"load in Category1");

}

- (void)setName:(NSString *)name

{

? ? objc_setAssociatedObject(self,

? ? ? ? ? ? ? ? ? ? ? ? ? ? "name",

? ? ? ? ? ? ? ? ? ? ? ? ? ? name,

? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC_ASSOCIATION_COPY);

}

- (NSString*)name

{

? ? NSString *nameObject = objc_getAssociatedObject(self, "name");

? ? return nameObject;

}

@end

11.Category(類別)、 Extension(擴展)和繼承的區別?分別用來做什么?

區別:

1. 分類有名字,類擴展沒有分類名字,是一種特殊的分類。

2. 分類只能擴展方法(屬性僅僅是聲明,并沒真正實現),類擴展可以擴展屬性、成員變量和方法。

3. 繼承可以增加,修改或者刪除方法,并且可以增加屬性。

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

extension在編譯期決議,它就是類的一部分,但是category則完全不一樣,它是在運行期決議的。extension在編譯期和頭文件里的@interface以及實現文件里的@implement一起形成一個完整的類,它、extension伴隨類的產生而產生,亦隨之一起消亡。

extension一般用來隱藏類的私有信息,你必須有一個類的源碼才能為一個類添加extension,所以你無法為系統的類比如NSString添加extension,除非創建子類再添加extension。而category不需要有類的源碼,我們可以給系統提供的類添加category。

extension可以添加實例變量,而category不可以。

extension和category都可以添加屬性,但是category的屬性不能生成成員變量和getter、setter方法的實現。

(二)Extension

1、 什么是extension

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

2、 extension的存在形式

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

@interface?ViewController?()


@end

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容