文中咖啡圖片及第一個圖片來源百度圖片豺撑,如涉及到侵權,請聯(lián)系我刪除圖片
原創(chuàng)文章俩块,轉載請注明:轉自:Try_Try_Try
更新
時間:2018.07.06
內容:添加幾張結論圖,使得結論更加的直觀
背(吐)景(槽)
最近被問到了一個category問題,把我問的暈頭轉向帜矾,當時就很(想)佩(打)服(人),無疑是灰禿禿滾回去跪著搓板屑柔,面壁思過屡萤。
把這一塊知識惡狠狠的補了一桶,奶奶的掸宛,了解完之后死陆,發(fā)現(xiàn)so easy,被自己蠢哭了唧瘾。
引用
網上也有相關的文章寫的很好措译。我完全讀下來的就是美團
的那篇深入理解Objective-C:Category别凤。
感覺寫的很好。我寫這篇文章時领虹,對著讀了好多遍规哪。
所以這次徹底對這篇文章分析一下(其實讀很多遍,是因為寫的很精簡塌衰,內部的實現(xiàn)細節(jié)需要自己對照源碼進行一一查看由缆。這樣才能把美團的這篇短短的文章讀成一個體系,然后再進行精簡猾蒂,知識就變成我的了---想太多了可能)均唉。
文章內容結構
- o 代碼結構
- 1 分類的結構
- 1.1 題外話
- 1.2 撕破你這層面紗(讓你再給我矯情)
- 1.3 .cpp文件
- 1.4 添加了category的消息發(fā)送流程總結
- 2 +load 方法的原理
- 2.1 題外話
- 2.2 一探究竟
- 3 initialize還有誰(有點捏花惹草)
- 3.1 添加initialize方法進行測試
- 3.2 猜測
- 3.3 源碼分析-走-起-來
- 3.4 正經點
- 3.5 話外
- 4 聯(lián)合起來才會更強(關聯(lián)對象)
- 4.1 查看分類的結構
- 4.1.1 查看類的結構
- 4.1.2 分類結構
- 4.2 關聯(lián)對象搞起來
- 4.2.1 扒關聯(lián)對象的源碼
- 4.2.2 內部具體實現(xiàn)細節(jié)分析
- 4.1 查看分類的結構
0. 代碼結構
Dog 繼承自Animate類,Dog中也有父類play方法肚菠,其中cat1和cat2中的方法一樣舔箭,都有play方法,只是打印的內容不一致蚊逢;
Animate.h
#import <Foundation/Foundation.h>
@interface Animate : NSObject
- (void)play;
@end
Animate.m
#import "Animate.h"
@implementation Animate
- (void)play
{
NSLog(@"Animate--%@", NSStringFromSelector(_cmd));
}
@end
Animate+cat1.h
#import "Animate.h"
@interface Animate (cat1)
- (void)play;
@end
Animate+cat1.m
#import "Animate+cat1.h"
@implementation Animate (cat1)
- (void)play
{
NSLog(@"Animate (cat1)--%@", NSStringFromSelector(_cmd));
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Dog.h"
#import "Dog+cat1.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog *dog = [[Dog alloc] init];
[dog play];
}
return 0;
}
此后代碼的分析层扶,也都是基于上述的結構,進行測試烙荷。
-
compile 順序1:
圖0.2 代碼編譯順序1
圖0.2對應的執(zhí)行結果:
Dog (cat2)--play
-
compile 順序2:
圖0.3 代碼編譯順序2
圖0.3對應的執(zhí)行結果:
Dog (cat1)--play
ARE YOU READY镜会?接下來是正文:
1. 分類的結構
1.1 題外話
之前看別人的代碼時候,一直出現(xiàn) clang -rewrite-objc filename.m
终抽,這里是clang -rewrite-objc Animate+cat1.m
命令戳表,然后就出現(xiàn)了神奇的.cpp文件,但是自己在terminal敲了一下昼伴,報了一堆錯(尼瑪匾旭,就失去了對.cpp的興趣了)。
后來發(fā)現(xiàn):電腦上安裝了多個不同版本的Xcode圃郊,又更改了名稱价涝,所以就無法找到。當更換成Xcode
的真名時持舆,重新在代碼所處文件clang一下色瘩,神奇的.cpp出來了??(哥終于要研究一番感(懵)人(逼)的c++代碼了)。
生成的文件名稱為Animate+cat1.cpp逸寓,層級結構如下:
1.2 撕破你這層面紗(讓你再給我矯情)
我了個曹操居兆,竟然96763行左右,要嚇死了(要是工資能達到96k該多好啊席覆,我又做夢了)史辙。這要從哪行開始看啊,拖著滾動條看了一圈,發(fā)現(xiàn)前面都是聲明和定義聊倔,到最后才是真身(這庇護夠強盎薇小!你以為你是孫悟空啊耙蔑,躲在這花果山的最深處)见妒。
查了一下,發(fā)現(xiàn)之所以生成的文件這么大甸陌,可能的原因是不同的arm架構都有须揣。
1.3 .cpp文件
以下就是編譯后的部分關鍵代碼:
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Animate_$_cat1 __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"play", "v16@0:8", (void *)_I_Animate_cat1_play}}
};
static struct _category_t _OBJC_$_CATEGORY_Animate_$_cat1 __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Animate",
0, // &OBJC_CLASS_$_Animate,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Animate_$_cat1,
0,
0,
0,
};
- 通過查看 libobjc.order文件,有之后加載的順序钱豁。objc_init->map_images->map_images_nolock->_read_images耻卡。
以下是_read_images中,讀取分類的部分代碼(處理后的):
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
}
}
}
- 為了找到本質牲尺,還需要繼續(xù)沿著方法向下走:
_read_images->addUnattachedCategoryForClass->remethodizeClass ->attachCategories->attachLists
attachCategories():將分類compile的順序進行逆序
重組到數組中,這里決定了最后編譯的分類可能最先執(zhí)行卵酪。
while (i--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
}
attachLists():將class中的方法和分類中的方法進行移動的操作,使得類中的方法放到數組的尾部谤碳,分類放到數組的頭部溃卡;
{
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
至此,類按照編譯順序逆序
的過程結束蜒简,然后開始繼續(xù)消息發(fā)送制瘸羡。(關于消息發(fā)送的具體流程,網上很多資料搓茬,可以自己分析一波犹赖,源碼好像是匯編)
1.4 添加了category的消息發(fā)送流程總結
通過上述分析:這些添加分類神馬的,都是在編譯階段垮兑,編譯器幫我們完成的冷尉。因此至于最終如何執(zhí)行漱挎,就得按照運行時正常的消息發(fā)送流程系枪,添加上分類后即:
- 類(array[cmplN,cmplN_1,cmplN_2.....,cls]->method)->
父類(array[cmplN,cmplN_1,cmplN_2.....,cls]->method)->
->
......
->
msgForward->...... - 先執(zhí)行當前類對應的所有分類中最后編譯的那個分類方法直到結束,否則一直按照數組順序向后找磕谅。父類也類似私爷。
好了,休息一下膊夹。
2. +load 方法的原理
2.1 題外話
如果在上述的6個類中都添加load方法,那么實現(xiàn)的邏輯又是怎樣的放刨?
在6個類中都添加如下代碼:
+ (void)load
{
NSLog(@"%@--%@", 類名/分類名, NSStringFromSelector(_cmd));
}
運行結果:2.2 一探究竟
正如圖2.2.1顯示工秩,關鍵代碼如紅色所示,prepare_load_methods
加載完才會對call_load_methods
進行調用。
如下是prepare_load_methods()
部分代碼:
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertWriting();
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
從上述代碼中可以并不能看出最終加載的順序助币,但是能夠看到class loads的順序浪听。
其中schedule_class_load
是個遞歸的調用,代碼如下:
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
schedule_class_load
代碼分析:
遞歸調用相當于數據結構中的棧的操作結構眉菱。棧底存放的可以看做是schedule_class_load(cls)迹栓,每次將cls->supercls作為
schedule_class_load
的參數,然后將其入棧俭缓,繼續(xù)判斷cls是否為nil克伊,如果為nil,則出棧华坦,進行處理add_class_to_loadable_list
, 然后繼續(xù)出棧愿吹,直到棧空為止惜姐。從源碼可以看出來:關于類的load方法調用洗搂,存儲順序是
先父類,再子類load
载弄。
上述代碼中的add_class_to_loadable_list
的部分源碼如下:
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
...
...
...
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
從上述代碼中耘拇,有一點是注意的,if (!method) return;
如果該類中沒有實現(xiàn)load方法宇攻,則直接返回惫叛,進行出棧的其他操作。
上述schedule_class_load
結束之后逞刷,開始add_category_to_loadable_list
嘉涌。該方法的加載就是按照編譯時的順序進行存儲。
當上述操作完成后夸浅,load的預加載也結束了仑最;接下來就是真正的call_load_methods
的調用。
call_load_methods才能決定真正的調用流程有沒有在這一步分生變化帆喇,如下所示(精簡):
void call_load_methods(void)
{
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
}
從上述的call_load_methods方法可以看出警医,關鍵的代碼是外層的do-while語句。
明顯的可以看出來坯钦,是先call_class_loads
预皇,然后call_category_loads
。因此婉刀,可以確定:cls -> category為大致的順序
吟温。
通過查看這兩個函數的源碼,內部是很常規(guī)的for循環(huán)突颊,從頭到尾鲁豪。這里至少說是沒有顛倒順序的操作潘悼。
還發(fā)現(xiàn)了另一種情況。就是:load方法是主動執(zhí)行的爬橡,就算什么消息都不手動發(fā)送挥等,當程序運行起來的時候,它也會執(zhí)行堤尾。畢竟如果按照消息發(fā)送機制的邏輯肝劲,得我調你,你才執(zhí)行啊郭宝。它是運行時辞槐,系統(tǒng)進行調用的。
綜上所述粘室,可以得出結論:
- load方法的調用順序為: 類->分類;
- 類中l(wèi)oad調用順序為:父類->類榄檬;
- 分類load調用順序為:按照編譯的順序;
- 類(call[supercls,curcls])->分類(call[cmpl0.....cmplN])衔统。
綜上鹿榜,就是load的完整流程。
好了锦爵,休息一下舱殿。
3. initialize還有誰(有點捏花惹草)
3.1 添加initialize方法進行測試
在前邊6個類中分別添加如下的測試代碼
+ (void)initialize
{
NSLog(@"%@--%@", 類名/分類名, NSStringFromSelector(_cmd));
}
運行結果:3.2 猜測
從結果可以預估:
initialize先執(zhí)行父類镰吵,再執(zhí)行類孩革。
而且都只是執(zhí)行了分類爷耀,且執(zhí)行的分類的順序是按照編譯的逆序進行的,且只執(zhí)行了一次豺瘤。
從1的分析可以看出來加載load的影子家淤。如果按照先加載父類方法這個尿性的話乎莉,是不是內部也通過一個遞歸實現(xiàn)的埠啃。那么它和load的遞歸有區(qū)別嗎死宣?(媽的,別再yy了碴开。滾去看蘋果粑粑的源碼??)
從2的分析可以看出,是消息發(fā)送的機制叹螟。 如果真真的如猜測的一樣,關于3的消息發(fā)送機制罢绽,其實還有個小陷阱良价。即如果當前類沒有實現(xiàn)initialize方法,那么按照消息機制的尿性明垢,是不是要找他爹給擺平(畢竟官場氣息太重,這社會沒爹也是不行啊抵蚊。沒想到代碼中早已告訴了我這個道理溯革。[蠢哭])贞绳。
綜上來說,initialize確實有點騷致稀。這里摸一下冈闭,那里摸一下。所以一會得對照源碼進行分析一波(希望臉不要太疼)抖单。
3.3 源碼分析-走-起-來
皮一下
寫到這個標題萎攒,突然想到了我大渤哥(黃渤)在18年春晚的那首跳起來
。寫到這矛绘,我腦袋里毅然神浮了這個魔曲耍休。
我屁股就坐在辦公的凳子上,一邊敲著代碼货矮,一邊左右擺動的甩了起來羹应。突然,被地上的幾個輪子親了一下次屠,疼的腳想踹人园匹。
畢竟我再晃兩下,我司的工學辦公椅劫灶,它那僅留的兩個輪子終將被我通通拋棄??(看來越發(fā)展裸违,這美曰其名的物件,壞在了質量做工啊供汛。是道德的淪喪怔昨,還是人性的缺失趁舀?歡迎收看今晚的xxxx)矮烹。
3.4 正經點
當我運行initialize方法時,發(fā)現(xiàn)與load方法不一樣卤唉。load方法是在運行時桑驱,主動調用的熬的。而initialize悦析,如果將main.m的代碼段注釋后强戴,是不會執(zhí)行骑歹。
這說明從調用的機制可以看出來兩者加載的方式是不一樣的道媚。當還原.m后最域,類第一次發(fā)送消息時镀脂,又開始調用了薄翅。從這可以看出翘魄,是進行了_objc_msgSend()調用暑竟,和剛才的猜想相照應光羞。
通過查找源碼中的libobjc.order文本可以看出來具體的執(zhí)行順序纱兑。對于我等屌絲來說潜慎,他可是個萬能的寶(寶铐炫?我信了你的襪)倒信。
- 從圖圖3.4.1中發(fā)現(xiàn)綠色部分代碼鳖悠,也使用了迭代操作乘综。(哈哈卡辰,此刻也應征了它確實偷偷摸了一下load這家伙)九妈。
_class_lookupMethodAndLoadCache3方法中的關鍵代碼如下:
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
lookUpImpOrForward方法中的關鍵代碼如下:
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
_class_initialize() 的關鍵代碼如下:
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
// Try to atomically set CLS_INITIALIZING.
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
}
}
callInitialize(cls);
}
該方法主要是為了讓父類先執(zhí)行,直到沒有父類呆贿,或者父類初始化完成if (supercls && !supercls->isInitialized())
,然后執(zhí)行callInitialize(cls)冒晰。
而callInitialize(cls)代碼如下所示:
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
哇塞(賽哇)壶运,真面目出來了蒋情,最后一步就是objc_msgSend
,哈哈辕翰,看你再躲喜命。因此接下來走消息發(fā)送的流程(分類initialize-> else -> 類
)壁榕。這樣所有問題到這里就又結束了牌里。
綜上得出以下結論:
- 父類(分類 else 類)->類(分類 else 類)`牡辽;
- 其中()的內容 array[cmplN,cmplN_1,cmplN_2.....,cls]->method催享。
- 即:supercls(array[cmplN,cmplN_1,cmplN_2.....,cls]->method)-curcls(array[cmplN,cmplN_1,cmplN_2.....,cls]->method)
3.5 話外
等等......
還有一個問題,就是剛才在代碼中看到的一幕票髓。
- 1.在上述
lookUpImpOrForward
貼出的源碼中的注釋洽沟,引起了我的興趣(我相信也引起了你的興趣裆操。如果沒有引起踪区,再去看一遍??):如果當前發(fā)的消息不是[[Dog alloc] init]
缎岗,而換成[Dog initialize]
。
結果如下所示:
圖3.5.1 主動調用initialize
對于圖3.5.1所示鸭巴,多出了一次鹃祖,無疑是狗狗主動發(fā)送initialize引起的惯豆。
其實源碼中的注釋也解釋的非常清楚了(我從上邊搬到了下邊)。
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
得出如下結論:initialize方法的調用是在該類在第一次使用時华临,調用的雅潭。而后該類再次使用時扶供,是不會調用的(好像如有所思)椿浓。
等等......(艸扳碍,我的橫杠分隔符都打上了笋敞,你才說還有)
還有一個問題(別墨跡夯巷,快說...)趁餐?
如果當前的子類澎怒、子分類中都沒有實現(xiàn)initialize方法喷面,只有父類惧辈、父分類中實現(xiàn)了initialize方法盒齿,那么運行結果如下圖所示:
至于出現(xiàn)這種情況的原因叨咖,也是很容易分析的甸各。
其實就是源碼中一個很小的細節(jié)趣倾。這也是和load方法的區(qū)別儒恋。在遞歸調用方法時有一個條件判斷碧浊;
在load中遞歸調用到最頂層時箱锐,開始執(zhí)行add_class_to_loadable_list
方法驹止,其中有一個代碼片段是如下情況:
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
在load中臊恋,如果父類中沒有l(wèi)oad方法抖仅,就直接返回撤卢,出棧子類放吩,進行下次的操作渡紫。而看一下initialize遞歸處相應的源碼:
callInitialize(cls);
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
從initialize源碼中可以看到惕澎,并沒有出現(xiàn)load中的沒有該方法時跳出的情況集灌,而是直接繼續(xù)暢通無阻的執(zhí)行(因為我是消息發(fā)送機制靶佬)。從而一路到達objc_msgSend锈锤。
這樣即使子分類久免、子類都沒有阎姥,此時也可以繼續(xù)尋找父類.
??呼巴,這次應該不用等了衣赶,沒了府瞄。
以上。
好了敲街,休息一下多艇。
4 聯(lián)合起來才會更強(關聯(lián)對象)
在Animate+cat1.h中添加如下的測試代碼:
#import "Animate.h"
@interface Animate (cat1)
/** name */
@property (nonatomic, copy) NSString *name;
- (void)play;
@end
Animate+cat1.m
#import "Animate+cat1.h"
#import <objc/runtime.h>
@implementation Animate (cat1)
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return (NSString *)objc_getAssociatedObject(self, "name");
}
@end
上述的第二個參數"name"也可以換成@selector(name),因為第二個參數的類型是:const void * _Nonnull key拨匆, 是void *類型惭每,所以在C中台腥,可以看做執(zhí)行函數的指針類型黎侈,因此可以直接換成OC中的SEL類型峻汉,且這樣寫會有提示休吠。
在main.m中進行簡單的測試瘤礁,賦值蔚携,取值操作酝蜒,就可以看出來使用起來和屬性差不多亡脑。
4.1 查看分類的結構
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
上述category結構中包含了:名稱、所屬類別拍屑、對象方法列表僵驰、類方法列表蒜茴、協(xié)議列表粉私、屬性列表诺核。
于是可以給分類中添加方法窖杀,協(xié)議和屬性陈瘦。但是好像沒有實例變量列表,那是不是說明分類中不可以添加實例變量呢锅风?
可以從以下兩方面入手:
4.1.1 查看類的結構
如果在Animate基類中皱埠,添加一個屬性和一個實例變量边器。代碼如下:
Animate.h
#import <Foundation/Foundation.h>
@interface Animate : NSObject
/** name */
@property (nonatomic, copy) NSString *name;
- (void)play;
@end
Animate.m
#import "Animate.h"
@interface Animate()
{
NSString *_height;
}
@end
@implementation Animate
+ (void)load
{
NSLog(@"Animate--%@", NSStringFromSelector(_cmd));
}
+ (void)initialize
{
NSLog(@"Animate--%@", NSStringFromSelector(_cmd));
}
- (void)play
{
NSLog(@"Animate--%@", NSStringFromSelector(_cmd));
}
@end
從代碼中恒界,可以看到添加了一個屬性name和一個實例變量_height十酣,其中還包含有2個類方法,1個實例方法兴泥。
由屬性name的特性可知搓彻,會自動生成實例變量:_name旭贬,-setName:和-name方法的聲明及其實現(xiàn)骑篙。
因此:包含的內容應該是:1個屬性靶端,2個實例變量杨名,2個類方法台谍,3個對象方法的實現(xiàn)
趁蕊。
通過將Animate.m文件進行clang掷伙,可以查看生成的c++關鍵源碼如下(精簡后):
struct _class_t OBJC_CLASS_$_Animate = {
0, // &OBJC_METACLASS_$_Animate,
0, // &OBJC_CLASS_$_NSObject,
0, // (void *)&_objc_empty_cache,
0, // unused, was (void *)&_objc_empty_vtable,
&_OBJC_CLASS_RO_$_Animate,
};
struct _class_t {
struct _class_t *isa;
struct _class_t *superclass;
void *cache;
void *vtable;
struct _class_ro_t *ro;
};
struct _class_ro_t {
unsigned int flags;
unsigned int instanceStart;
unsigned int instanceSize;
unsigned int reserved;
const unsigned char *ivarLayout;
const char *name;
const struct _method_list_t *baseMethods;
const struct _objc_protocol_list *baseProtocols;
const struct _ivar_list_t *ivars;
const unsigned char *weakIvarLayout;
const struct _prop_list_t *properties;
};
從源碼中可以看出任柜,結構體的聲明和賦值操作宙地;我們關心的內容在結構體中_class_ro_t
中:方法列表宅粥、協(xié)議列表蓖柔、屬性列表风纠、實例變量列表竹观、屬性列表臭增;然后看一下對應的各個列表的源碼:
- 屬性列表:
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Animate = {
sizeof(_prop_t),
1,
{{"name","T@\"NSString\",C,N,V_name"}}
};
struct _prop_t {
const char *name;
const char *attributes;
};
看到存儲了一個結構體的屬性數組中存放著唯一的name屬性列牺。
- 實例變量列表:
static struct /*_ivar_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count;
struct _ivar_t ivar_list[2];
} _OBJC_$_INSTANCE_VARIABLES_Animate = {
sizeof(_ivar_t),
2,
{{(unsigned long int *)&OBJC_IVAR_$_Animate$_height, "_height", "@\"NSString\"", 3, 8},
{(unsigned long int *)&OBJC_IVAR_$_Animate$_name, "_name", "@\"NSString\"", 3, 8}}
};
struct _ivar_t {
unsigned long int *offset; // pointer to ivar offset location
const char *name;
const char *type;
unsigned int alignment;
unsigned int size;
};
看到存儲了2個結構體的數組包含了原有的_height和屬性生成的_name瞎领。
- 對象方法列表:
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[3];
} _OBJC_$_INSTANCE_METHODS_Animate __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
3,
{{(struct objc_selector *)"play", "v16@0:8", (void *)_I_Animate_play},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_Animate_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_Animate_setName_}}
};
struct _objc_method {
struct objc_selector * _cmd;
const char *method_type;
void *_imp;
};
存儲了3個結構體的數組九默,是原有的play方法和屬性生成的setName和name方法驼修。
也可以看到其他的類方法列表存儲在metacls中等乙各。通過查看類的結構觅丰,就能對之前的猜測進行有力的驗證简卧。
接下來就據此迷捧,對比的看一下,如果在分類中添加屬性列表和實例變量唇牧,又如何呢?
4.1.2 分類結構
- 布置內容
首先在Animate+cat1.h中添加屬性name杆查。當我嘗試添加實例變量時亲桦,發(fā)現(xiàn)沒法添加客峭,一寫就報錯??舔琅;代碼如下:
Animate+cat1.h
#import "Animate.h"
@interface Animate (cat1)
/** name */
@property (nonatomic, copy) NSString *name;
- (void)play;
@end
- 通過clang查看Animate+cat1.m的源碼
我們只關心屬性列表洲劣、方法列表闪檬、方法實現(xiàn)
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Animate_$_cat1 = {
sizeof(_prop_t),
1,
{{"name","T@\"NSString\",C,N"}}
};
wtf虚循,瞅了一圈横缔,愣是沒有看到實例變量列表結構茎刚,不死心又去查看了一波實例方法列表如下:
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Animate_$_cat1 = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"play", "v16@0:8", (void *)_I_Animate_cat1_play}}
};
好吧膛锭,啥都沒有初狰,只有之前寫的play方法奢入。服了腥光,其實這也可以理解的。
從4.1 查看分類的結構
開頭议双,就直接說明了_category_t結構包含的內容聋伦。里邊確實是沒有ivar_lists
觉增。這樣一來逾礁,即時結構中給了一個屬性列表嘹履,用處也不是很大啊砾嫉。
沒法使用其中的set和get方法進行操作焕刮,不能夠存儲內容配并。直接輸入_name或者self->_name也是行不通的溉旋。因此為了能夠存儲數據观腊,蘋果粑粑又跳出來了恕沫。
4.2 關聯(lián)對象搞起來
蘋果粑粑托夢
粑粑
:小子,我給你屬性列表了偷霉,你只需要重寫相應的set和get方法。
我
:好像是啊??叙身。那......信轿,那我該怎么實現(xiàn)呢财忽?
我又沒辦法定義一個實例變量即彪,莫非再讓我定義一個屬性隶校,這樣有沒有set深胳,get方法舞终,這樣又開始循環(huán)了(子子孫孫权埠,無窮匱也H帘巍)满俗。
但我又沒辦法聲明一個實例變量唆垃。在分類中聲明一個實例變量辕万,想想就別扭啊醉途。
如果在.m中聲明一個實例變量隘擎,一般都是extention货葬,()中也沒有名稱震桶。在分類中尼夺,()中又是有內容的淤堵。如果這樣寫拐邪,又報了一對錯誤(啊啊啊啊扎阶,我瘋了)东臀。
粑粑
:傻兒子惰赋,繼續(xù)想赁濒。拿出你C語言中長久不用的大招拒炎。
我
: 啥击你?奧(dingdong)我知道了,你讓我用全局變量嗎球切,這樣也行。但是以后我每新加了一個屬性户辱,都要重新定義一個全局變量庐镐。這樣粑粑會不會打死我必逆,搶你太多的飯了(內存)名眉。
那我定義一個字典就好了八鹇!(等待粑粑夸我)福压。
粑粑
:不要搶老子的飯荆姆。
我
: 大哭(心想:屎粑粑胆筒,你那么有錢腐泻,已經從你的開發(fā)者中通過內購剝削了3分派桩,還這樣對我......)铆惑。
粑粑
:好吧丑蛤,不逗你了受裹,其實我已經給你提供了一個關聯(lián)對象棉饶,方便你管理分類中的屬性照藻。至于在哪幸缕,你小子自己去找吧发乔。畢竟粑粑有些東西是不能夠給你說太清楚的列疗,否則都要來我這里更改東西了抵栈。
我
: 恩古劲,謝謝粑粑(??产艾,沒有我找不到的東西)。
4.2.1 扒關聯(lián)對象的源碼
通過搜索associated關鍵字可以找到疑故,步驟:objc_setAssociatedObject()->_object_set_associative_reference(), 如下:
/**********************************************************************
* Associative Reference Support
**********************************************************************/
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}
跳進_object_get_associative_reference
踱阿,可以看到里邊出現(xiàn)了四個類:AssociationsManager、AssociationsHashMap才漆、ObjectAssociationMap醇滥、ObjcAssociation腺办。查看它們的結構如下:
class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
class ObjcAssociation {
uintptr_t _policy;
id _value;
public:
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
ObjcAssociation() : _policy(0), _value(nil) {}
uintptr_t policy() const { return _policy; }
id value() const { return _value; }
bool hasValue() { return _value != nil; }
};
這段代碼怎么理解?
可以看到里邊有兩個map船响,類似于OC中的字典见间,后邊加了類似泛型的東西米诉。
再仔細一看史侣,這不就是一個二維數組嗎惊橱?
奶奶的税朴,代碼寫這么多正林,為啥不加上一個注釋說:都看好了觅廓,這一堆代碼像極了一個二維數組哪亿∮蓿總結之后篡殷,結構如下圖所示:
可以看出:
AssociationsManager中有一個對象AssociationsHashMap指針奇瘦,它是二維數組的地址耳标,相當于二位數組的名稱次坡。該值也是AssociationsManager的地址。他管理著內存中所有的關聯(lián)對象症脂。
縱坐標為當前的分類對象object,object下標對應的整行為ObjectAssociationMap兴蒸。而該內部就是該分類對象下所有的關聯(lián)對象橙凳〉盒ィ可能有name的關聯(lián)對象坚踩,age瞬铸,size等等嗓节。
橫坐標為當前的具體key拦宣。而如果能夠查找到鸵隧,該單元格就是ObjcAssociation珊蟀。它是value與policy通過運算的出的值系洛。
以上就是基本的結構。如果繼續(xù)擴展趟薄,可以將它看到兩個表的組合杭煎。而object是最外層表的主鍵也切,而內層可以看成內部表的主鍵雷恃。這樣也能說得通附井。
也可以將他看成一張表,只不過是雙主鍵罷了(object,key)太雨。
綜上狭瞎,通過上邊的圖碗殷,很容易看清楚其結構。至于其添加值员咽,獲取值捡偏,銷毀對象的過程傅物,通過上表也可以很容易的分析卒暂。
4.2.2 內部具體實現(xiàn)細節(jié)分析
以objc_setAssociatedObject()為例進行分析:
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
// 其中acquireValue()函數是為了對value根據相應的內存策略進行處理
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
// DISGUISE() ,是將object轉換為另一種類型:disguised_ptr_t
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
}
前邊的代碼加上了注釋谭跨。從if開始繼續(xù)分析:
如果new_value有值,則要存進去挂捅;否則相當于清空表中對應的數據;
if分析:如果AssociationsHashMap列表中有disguised_object這條記錄蒙谓。取出該條記錄對應的ObjectAssociationMap指針舵揭,再根據key取得ObjcAssociation對象。然后將新的 ObjcAssociation(policy, new_value)填充到該位置即可墅垮。如果沒有根據找到該key對應的值灾梦,則直接手動添加即可寞宫。
else分析:相當于拿到清空原先關聯(lián)對象的值(或者成為初始化)篷就。
相應的objc_getAssociatedObject钾麸、objc_removeAssociatedObjects也是如此,可以自己查看相應源碼進行分析负敏。
好了,休息一下。
- 本文相關的demo已放置github.
- 在閱讀中,如果發(fā)現(xiàn)文章有不合理的地方,歡迎提出疑問至郵箱:B12050217@163.com.
- 原創(chuàng)文章,轉載請注明:轉自:Try_Try_Try
- OK, game over.