Category的實現原理
- Category編譯之后的底層結構是struct category_t,里面存儲着分類的對象方法、類方法、屬性、協議信息
- 在程序運行的時候,runtime會將Category的數據,合並到類信息中(類對象、元類對象中)
Category和Class Extension的區別是什么?
- Class Extension在編譯的時候,它的數據就已經包含在類信息中
- Category是在運行時,才會將數據合並到類信息中
Category中有load方法嗎?load方法是什么時候調用的?load 方法能繼承嗎?
- 有load方法
- load方法在runtime加載類、分類的時候調用
- load方法可以繼承,但是一般情況下不會主動去調用load方法,都是讓系統自動調用 (子類不會覆蓋父類的load方法 因為load是根據函數地址直接調用 而不是是通過objc_msgSend調用具體的可以看這個鏈接 里面有詳細的load 和 initialize的區別)
Category能否添加成員變量?如果可以,如何給Category添加成員變量?
- 不能直接給Category添加成員變量,但是可以間接實現Category有成員變量的效果 實現方式
- 因為分類中的屬性只是生成了 set 和 get 方法的聲明,其實現和下划線變量都沒有生成 所以我們需要將為做的事情完成
#import "MJPerson.h" @interface MJPerson (Test) //{ // int _weight; //} //分類里面聲明屬性 只是生成 屬性的set 和 get 方法 但是並沒有去實現和生成_變量 @property (assign, nonatomic) int weight; @property (copy, nonatomic) NSString* name; //- (void)setWeight:(int)weight; //- (int)weight; @end
#define MJKey [NSString stringWithFormat:@"%p", self] @implementation MJPerson (Test) NSMutableDictionary *names_; NSMutableDictionary *weights_; + (void)load { weights_ = [NSMutableDictionary dictionary]; names_ = [NSMutableDictionary dictionary]; } - (void)setName:(NSString *)name { // NSString *key = [NSString stringWithFormat:@"%p", self]; names_[MJKey] = name; } - (NSString *)name { // NSString *key = [NSString stringWithFormat:@"%p", self]; return names_[MJKey]; } - (void)setWeight:(int)weight { // NSString *key = [NSString stringWithFormat:@"%p", self]; weights_[MJKey] = @(weight); } - (int)weight { // NSString *key = [NSString stringWithFormat:@"%p", self]; return [weights_[MJKey] intValue]; } @end
//用字典來實現有已下問題 /*1.存儲的地方不一樣 //person2.age = 20; // 20是存儲在peron2對象內部 //person2.weight = 50; // 50是存放在全局的字典對象里面 2.線程安全問題 (可以加鎖解決) */ #import <Foundation/Foundation.h> #import "MJPerson.h" #import "MJPerson+Test.h" int main(int argc, const char * argv[]) { @autoreleasepool { MJPerson *person = [[MJPerson alloc] init]; person.age = 10; person.weight = 40; MJPerson *person2 = [[MJPerson alloc] init]; person2.age = 20; // 20是存儲在peron2對象內部 person2.weight = 50; // 50是存放在全局的字典對象里面 NSLog(@"person - age is %d, weight is %d", person.age, person.weight); NSLog(@"person2 - age is %d, weight is %d", person2.age, person2.weight); } return 0; }
- 使用關聯對象來實現
#import "MJPerson+Test.h" #import <objc/runtime.h> @implementation MJPerson (Test) - (void)setName:(NSString *)name { // 關聯對象 就是將傳進來的name 和 person對象(self) 關聯起來 ,達到 一個person對象對應一個name 就不用使用字典的方式來做了 /* 參數講解 <#id _Nonnull object#> 你要給哪一個對象添加關聯對象 person對象(self) <#const void * _Nonnull key#> 關聯的key 取的時候需要用相當於內部應該有個字典 <#id _Nullable value#> 關聯對象是什么(關聯的值) name <#objc_AssociationPolicy policy#> 關聯策略 objc_AssociationPolicy 對應的修飾符 OBJC_ASSOCIATION_ASSIGN assign OBJC_ASSOCIATION_RETAIN_NONATOMIC strong, nonatomic OBJC_ASSOCIATION_COPY_NONATOMIC copy, nonatomic OBJC_ASSOCIATION_RETAIN strong, atomic OBJC_ASSOCIATION_COPY copy, atomic // objc_setAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>, <#id _Nullable value#>, <#objc_AssociationPolicy policy#>) */ objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)name { // 隱式參數 // _cmd == @selector(name) return objc_getAssociatedObject(self, _cmd); } - (void)setWeight:(int)weight { objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (int)weight { // _cmd == @selector(weight) return [objc_getAssociatedObject(self, _cmd) intValue]; } //- (NSString *)name //{ // return objc_getAssociatedObject(self, @selector(name)); //} //- (int)weight //{ // return [objc_getAssociatedObject(self, @selector(weight)) intValue]; //} //#define MJNameKey @"name" //#define MJWeightKey @"weight" //- (void)setName:(NSString *)name //{ // objc_setAssociatedObject(self, MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC); //} // //- (NSString *)name //{ // return objc_getAssociatedObject(self, MJNameKey); //} // //- (void)setWeight:(int)weight //{ // objc_setAssociatedObject(self, MJWeightKey, @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC); //} // //- (int)weight //{ // return [objc_getAssociatedObject(self, MJWeightKey) intValue]; //} //static const void *MJNameKey = &MJNameKey; //static const void *MJWeightKey = &MJWeightKey; //- (void)setName:(NSString *)name //{ // objc_setAssociatedObject(self, MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC); //} // //- (NSString *)name //{ // return objc_getAssociatedObject(self, MJNameKey); //} // //- (void)setWeight:(int)weight //{ // objc_setAssociatedObject(self, MJWeightKey, @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC); //} // //- (int)weight //{ // return [objc_getAssociatedObject(self, MJWeightKey) intValue]; //} //static const char MJNameKey; //static const char MJWeightKey; //- (void)setName:(NSString *)name //{ // objc_setAssociatedObject(self, &MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC); //} // //- (NSString *)name //{ // return objc_getAssociatedObject(self, &MJNameKey); //} // //- (void)setWeight:(int)weight //{ // objc_setAssociatedObject(self, &MJWeightKey, @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC); //} // //- (int)weight //{ // return [objc_getAssociatedObject(self, &MJWeightKey) intValue]; //} @end
#import <Foundation/Foundation.h> #import "MJPerson.h" #import "MJPerson+Test.h" #import <objc/runtime.h> int main(int argc, const char * argv[]) { @autoreleasepool { MJPerson *person = [[MJPerson alloc] init]; person.age = 10; person.name = @"jack"; person.weight = 30; MJPerson *person2 = [[MJPerson alloc] init]; person2.age = 20; person2.name = @"rose"; person2.name = nil; person2.weight = 50; NSLog(@"person - age is %d, name is %@, weight is %d", person.age, person.name, person.weight); NSLog(@"person2 - age is %d, name is %@, weight is %d", person2.age, person2.name, person2.weight); } return 0; }
關聯對象的原理
Category的底層結構
分類的本質(實現原理):
- Category編譯之后的底層結構是 struct category_t (結構體),里面存儲着分類的對象方法、類方法、屬性、協議信息
- 在程序運行的時候,runtime會將Category的數據,合並到類信息中(類對象、元類對象中)
分類底層結構定義的如下:
//objc-runtime-new.h 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; // Fields below this point are not always present on disk. struct property_list_t *_classProperties; method_list_t *methodsForMeta(bool isMeta) { if (isMeta) return classMethods; else return instanceMethods; } property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi); };
項目中每定義一個分類,底層都會增加一個category_t對象。
Category源碼閱讀順序:
- objc-os.mm (runtime入口)
- _objc_init (runtime初始化)
- map_images
- map_images_nolock
- objc-runtime-new.mm
- _read_images
- remethodizeClass
- attachCategories
- attachLists
- realloc、memmove、 memcpy
category的加載過程:
- 通過runtime加載類的所有分類數據
- 將所有分類的方法,屬性,協議數據分別合並到一個數組 (后面參與編譯的Category數據,會在數組的前面)
- 將合並后的分類數據(方法,屬性,協議)插入到類原來到數據之前
由源碼可見,對同名方法而言,會優先調用分類中的方法。如果多個分類中包含同名方法,則會調用最后參與編譯的分類中的方法。
摘錄源碼中核心的attachCategories實現如下(objc4-756.2):
// Attach method lists and properties and protocols from categories to a class. // Assumes the categories in cats are all loaded and sorted by load order, // oldest categories first. static void attachCategories(Class cls, category_list *cats, bool flush_caches) { if (!cats) return; if (PrintReplacedMethods) printReplacements(cls, cats); bool isMeta = cls->isMetaClass(); // fixme rearrange to remove these intermediate allocations //方法二維數組 //[[method_t,method_t],[method_t,method_t,method_t]] //二維數組中的一個元素(數組)存放一個分類中的方法列表 method_list_t **mlists = (method_list_t **) malloc(cats->count * sizeof(*mlists)); //屬性二維數組 property_list_t **proplists = (property_list_t **) malloc(cats->count * sizeof(*proplists)); //協議二維數組 protocol_list_t **protolists = (protocol_list_t **) malloc(cats->count * sizeof(*protolists)); // Count backwards through cats to get newest categories first int mcount = 0; int propcount = 0; int protocount = 0; int i = cats->count; bool fromBundle = NO; while (i--) { //取出分類 auto& entry = cats->list[i]; //取出分類中的方法列表 method_list_t *mlist = entry.cat->methodsForMeta(isMeta); if (mlist) { mlists[mcount++] = mlist; fromBundle |= entry.hi->isBundle(); } property_list_t *proplist = entry.cat->propertiesForMeta(isMeta, entry.hi); if (proplist) { proplists[propcount++] = proplist; } protocol_list_t *protolist = entry.cat->protocols; if (protolist) { protolists[protocount++] = protolist; } } //取出(元)類對象中的數據(class_rw_t) auto rw = cls->data(); prepareMethodLists(cls, mlists, mcount, NO, fromBundle); //將所有分類的方法添加到(元)類對象的方法列表中 rw->methods.attachLists(mlists, mcount); free(mlists); if (flush_caches && mcount > 0) flushCaches(cls); //將所有分類的屬性添加到(元)類對象的屬性列表中 rw->properties.attachLists(proplists, propcount); free(proplists); //將所有分類的協議添加到(元)類對象的協議列表中 rw->protocols.attachLists(protolists, protocount); free(protolists); }
Category和Extension的區別
區別一
- Category
- Extension
- 可以說是特殊的分類,也稱作匿名分類
- 可以給類添加成員屬性,但是是私有變量
- 可以給類添加方法,也是私有方法
區別二
雖然有人說Extension是一個特殊的Category,也有人將Extension成為匿名分類,但是兩者的區別很大。
-
Category
- 是運行期決定的
- 類擴展可以添加實例變量,分類不能添加實例變量(原因:因為在運行期,對象的內存布局已經確定,如果添加實例變量會破壞類的內部布局,這對編譯性語言是災難性的。)
-
Extension
- 在編譯器決定,是類的一部分,在編譯器和頭文件的
@interface
和實現文件里的@implement
一起形成了一個完整的類。 - 伴隨着類的產生而產生,也隨着類的消失而消失。
- Extension一般用來隱藏類的私有消息,必須有一個類的源碼才能添加一個類的Extension,所以對於系統的一個類,比如NSString,就無法添加類擴展。
- 在編譯器決定,是類的一部分,在編譯器和頭文件的
1.Category 基本使用場合
經常用的是給類添加方法,協議、屬性,編寫的分類里面的方法, 最終是在運行過程中的時候合並到類對象(對象方法)/元類對象(類方法)里面 (不是編譯的時候合並的)