0:基本知識
1:分類添加的屬性系統(tǒng)只會自動的生成方法該屬性的方法聲明;
并不會生成帶下劃線的成員變量和對應(yīng)的方法實現(xiàn);
1:分類(category)和類擴展(extension )的區(qū)別
1:分類在運行的時候通過runtime將分類的信息合并到類中去的;
2:類擴展在編譯的時候就合并到類里面去了;
2:項目里查看文件的編譯順序
1:查看
項目的最左邊有個類似聊天這個圖片,在點擊項目里你想看的那個編譯,點擊以后就可以在右邊看到該項目文件的編譯順序;
2:調(diào)整編譯順序
點擊最左邊的項目名字,在點擊TARGETS->Build Phases ->Compile
Sources,在這里面拖動文件的順序,就可以改變項目文
件編譯的順序了。越是后編譯,分類中的方法越是排
列在類對象的前面;
3:Category的實現(xiàn)原理
1:Category在編譯之后的底層結(jié)構(gòu)是struct category_t,里面
存儲著分類的對象方法、類方法、屬性、協(xié)議信息;
2:在程序運行的時候,runtime會將Category的數(shù)據(jù)合并到
類信息中(類對象和元類對象);
3.1:Category的加載過程處理
蘋果官方源碼類文件objc-os.mm-> objc_init->map_images->map_images_nolock
蘋果官方源碼類文件objc-runtime-new.mm
_read_images->remethodizeClass->attachCategories->attachLists->realloc、memove、memcpy
4:Category和Extension的區(qū)別
1:Extension在編譯的時候,它的數(shù)據(jù)就被包含在類信息中;
2:Category是在運行時才會將信息合并到類信息中去;
5:Category中有l(wèi)oad方法嗎?load方法是什么時候調(diào)用?load方法能繼承嗎?
1:分類有l(wèi)oad方法;
2:load方法在runtime加載類、分類的時候調(diào)用;
3:load方法嚴格意義上來說是可以繼承的;但是一般情況下時
不會主動的去調(diào)用load方法的,都是讓系統(tǒng)調(diào)用的;
6:load方法的調(diào)用順序?
1:先調(diào)用類的load方法,然后當(dāng)前類的分類按照編譯的優(yōu)先順
序調(diào)用(先編譯、先調(diào)用),調(diào)用在類的load方法,會先調(diào)用父
類的load方法;
2:另外load方法是通過方法的地址進行調(diào)用的,并不是通過
objc_msgSend函數(shù)調(diào)用;
3:objc_msgSend方法一般是去對象的元類對象或類對象中按
照方法列表查找方法并調(diào)用;
4:原因:
蘋果官方源碼類文件objc-os.mm->_objc_init->load images->
prepare_load_methods(schedule_class_load、add_class_to_loadable_list、add_category_to_loadable_list)
->call_load_methods(call_class_loads、call_category_loads)
7: initialize方法的調(diào)用順序以及源碼解讀過程
1:調(diào)用順序
initialize方法會在類第一次接收到消息的時候調(diào)用,先調(diào)用
父類的initialize方法,在調(diào)用子類的initialize方法;
(先初始化父類,在初始化子類,每一個類只會初始化一次);
2:源碼解讀過程
objc-runtime-new.mm->class_getInstanceMethod->
loopUpImpOrNil->loopUpImpOrForward->callInitialize->
objc_msgSend(cls,SEL_initialize)
3: initialize方法的特點
3.1:如果子類沒有實現(xiàn)initialize方法,就會調(diào)用父類的
initialize方法(所以父類的initialize方法可能調(diào)用多次);
3.2:如果當(dāng)前類的分類實現(xiàn)了initialize方法,那么就會調(diào)用分類的initialize方法;
8:load和initialize方法的區(qū)別是什么?它們在Category中的調(diào)用順序以及在出現(xiàn)繼承時它們之間的調(diào)用順序;
1:區(qū)別
(1):調(diào)用時間
load方法:在程序運行的時候通過runtime加載類、分類的時
候就會調(diào)用該方法(只會調(diào)用一次);
initialize方法:在類第一次接收到消息的時候調(diào)用,每一個類
只會調(diào)用一次initialize方法(父類的initialize方法可能被調(diào)用
多次)
(2):調(diào)用的方式
load方法:load是根據(jù)函數(shù)的地址來進行調(diào)用的;
initialize方法:initialize是根據(jù)objc_msgSend()調(diào)用的;
2:調(diào)用順序
load方法:
(1):先調(diào)用類的load方法,先編譯的類優(yōu)先調(diào)用load方法,
調(diào)用load方法之前會先調(diào)用父類的load方法;
(2):根據(jù)開源的源碼發(fā)現(xiàn)會先調(diào)用父類的load方法,然后在
調(diào)用分類的load方法(先編譯的先調(diào)用),在調(diào)用子類的load
方法之前會先調(diào)用父類的load方法;
initialize方法:
(1):先初始化父類;
(2):在初始化子類(可能最終調(diào)用的是父類的initialize方法);
父類的initialize可能被調(diào)用多次的原因
因為initialize方法是通過objc_msgSend(cls,SEL_Method),在
objc-runtime-new.mm的這個源碼里的
class_getInstanceMethod方法里面,在調(diào)用initialize方法之
前會判斷當(dāng)前的類有沒有被初始化,在沒有被初始化的情況
下最后就會去進入callInitialize方法,在這個方法里會判斷父
類有沒有被初始化,沒有被初始化,就會將父類初始化并調(diào)
用父類的initialize方法,然后在調(diào)用當(dāng)前類的initialize方法
(子類沒有實現(xiàn)initialize方法,就又通過superclass指針去調(diào)
用父類的initialize方法)
9:Category是否能添加成員變量?如果可以,如何給Category添加成員變量?
9.1:是否能添加成員變量
分類不可以直接添加成員變量,但是可以間接給類添加類似于成員變量的字段;
通過字典間接添加
在分類里面添加一個全局字典變量,并在load方法中實現(xiàn),
使用self的內(nèi)存地址作為key,在屬性的存取方法中使用字典來實現(xiàn)
#define KEY [NSString stringWithFormat:@"%p",self]
@implementation Person (Test)
NSMutableDictionary *ages_;
+ (void)load{
ages_ = [NSMutableDictionary dictionary];
}
-(void)setAge:(int )age{
[ages_ setValue:@(age) forKey:KEY];
}
-(int)age{
return [[ages_ objectForKey:KEY] intValue];
}
@end
弊端:
<1>:存在線程安全問題;
通過rumtime間接添加
1:設(shè)置關(guān)聯(lián)對象的方法
object:需要關(guān)聯(lián)的對象
key:關(guān)聯(lián)對象對應(yīng)的地址
value:關(guān)聯(lián)對象的值
policy:關(guān)聯(lián)策略
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
2:獲取關(guān)聯(lián)對象的值的方法
object:關(guān)聯(lián)的對象
key:關(guān)聯(lián)對象對應(yīng)的key
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
以上兩個方法的key一定要是相同的,不然的會造成數(shù)據(jù)的錯誤;
3:下面給某一個類添加分類添加name屬性為例
@property(copy,nonatomic)NSString *sex;
(1):方式一:使用傳入的name的局部變量來作為key
缺點: 字符串容易寫錯
-(void)setSex:(NSString *)sex{
objc_setAssociatedObject(self, "sex", sex, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)sex{
return objc_getAssociatedObject(self,"sex");
}
(2):方式二:使用當(dāng)前屬性的get方法來作為key
優(yōu)點:系統(tǒng)有提示
-(void)setSex:(NSString *)sex{
objc_setAssociatedObject(self, @selector(sex), sex, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)sex{
return objc_getAssociatedObject(self, @selector(sex));
}
(3):方式三:使用_cmd來作為get方法的key
在get方法里面的_cmd代表當(dāng)前方法的@selector()
-(void)setSex:(NSString *)sex{
objc_setAssociatedObject(self, @selector(sex), sex, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)sex{
return objc_getAssociatedObject(self, _cmd);
}
關(guān)聯(lián)對象的原理
源碼在objc-references.mm
AssociationsManager 通過傳出的對象(self)作為key存儲一個
AssociationsHashMap的對象,這個AssociationsHashMap對象通過給對
象添加的字段(name)的這個key來存儲一個ObjectAssociationMap對象,這個
ObjectAssociationMap利用對象傳入的key存儲了一個ObjectAssociation
對象,這個對象存儲了字段對應(yīng)的值和策略;
源碼分析結(jié)果
1:以上通過關(guān)聯(lián)對象添加的屬性并沒有添加到類對象中;
2:在當(dāng)前對象被銷毀以后,runtime會自動的釋放內(nèi)存;
3:關(guān)聯(lián)對象統(tǒng)一存儲在AssociationsManager中;

屏幕快照 2018-05-10 14.38.56.png
runtime關(guān)聯(lián)對象的其他方法
1:移除所有的關(guān)聯(lián)對象
objc_removeAssociatedObjects(id _Nonnull object)
2:給objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
里面的value傳出nil,系統(tǒng)會刪除AssociationsHashMap這個對象;(相當(dāng)于移除關(guān)聯(lián)對象(對象的屬性))