一:底層編譯原理
先編譯分類文件,會創建一個_Category_t結構體,多少個分類就會生成多少個結構體(屬性,協議,協議)都在分類里。
運行時,再把各個分類的結構體添加合并到原本類中。方法,屬性和協議添加到原類的順序是,先把原有的方法屬性協議的數組指針在數組中的位置后移(后移位置取決于多少個分類),再把各分類的方法,屬性,協議加入原數組中,位置處于原原類方法列表指針的前面,這里證明了,當對象調用分類和類中都有的同名方法時,先調用分類的方法(后編譯先調用)。
runtime源碼查看與證明,以下步驟是在源碼中一步步點的代碼
1:void _objc_init(void)這個是runtime代碼入口。
蘋果動態鏈接器注冊鏈接方法的代碼2:_dyld_objc_notify_register(&map_images, load_images, unmap_image);
這里的images是鏡像的意思
點進_map_images查看
3:map_images_nolock點進去。
4:_read_images 點進去?
?5:remethodizeClass重新方法化,點進去
6:attachCategories(添加分類方法屬性協議)
7:rw->methods.attachLists(mlists, mcount);把方法添加進數組
8:把原來的方法列表向后移,移n個分類的位置,移動數據的長度是舊方法的數據長度的和
??memmove(array()->lists + addedCount, array()->lists,oldCount*?sizeof(array()->lists[0]));
?把所有分類的方法列表放在數組的前面
??memcpy(array()->lists, addedLists, addedCount*?sizeof(array()->lists[0]));
?由8里以上兩個操作可以證明開發中類方法或者實例方法調用是后編譯先調用。
二:類的Load方法底層原理
是運行時,直接查找到類方法列表和分類的方法列表里的函數指針直接調用load,所以會出現父類,分類同名方法都調用的情況。與平常我們調用方法-objc_msgSend(”class”,”methodName”);不同,消息發送是通過對象的isa指針去尋找類或元類,然后在類或元類的方法列表里查找方法由于編譯時的原因(上一段),會優先執行分類中的后編譯方法。
1:void _objc_init(void)入口
2:_dyld_objc_notify_register(&map_images, load_images, unmap_image);
點進load_images查看
3: call_load_methods();?調用方法,具體調用的代碼是(*load_method)(cls, SEL_load);
4: prepare_load_methods里的代碼決定調用順序?
5: _getObjc2NonlazyClassList(獲取類里不是懶加載的方法,這里面的順序和編譯順序有關)
?_getObjc2NonlazyCategoryList(獲取分類里不是懶加載的方法)
6: schedule_class_load(規劃類的加載,遞歸方法)先調用父類的load再調用自己的load
7:add_category_to_loadable_list(cat);(按什么順序添加分類的方法在此)
?loadable_categories[loadable_categories_used].cat = cat;
?loadable_categories[loadable_categories_used].method = method;
?loadable_categories_used++;這三句證明了把分類方法添加進數組的順序,一個個往后添加
三:load方法和initialze的區別
一)調用方式
1:load是根據函數地址調用 ;2:initialize是通過objc_msgSend調用
二)調用時刻
1:load是runtime加載類,分類的時候調用(只會調用一次)
2:initialize是類第一次接收到消息的時候調用,每個類只會initialize一次(父類的initialize可能會被子類調用多次。當子類沒有實現initialize的時候)
三)load、initialize的調用順序
1:load?
1)先調用類的load(a先編譯的類,優先調用load;b調用子類的load之前,會先調用父類的load)
2)再調用分類的load 先編譯的分類,優先調用load
2:initialize
1)先初始化父類 2)再初始化子類(可能最終調用的是父類的initialize方法);
源碼部分
1:class_getInstanceMethod->lookUpImpOrNil->lookUpImpOrForward
if(initialize? &&? !cls->isInitialized()) {
? ? ? ? runtimeLock.unlock();
? ? ? ? _class_initialize (_class_getNonMetaClass(cls, inst));
? ? ? ? runtimeLock.lock();
?? ?}源碼->如果自己需要初始化并且沒有初始化,就調用_class_initialize初始化
initialize函數里:
? ? supercls = cls->superclass;
? ? if(supercls? &&? !supercls->isInitialized()) {
? ? ? ? _class_initialize(supercls);
? ? }如果父類存在并且沒有初始化,則再次調用父類initialize,遞歸函數,一直到基類。
然后由頂層往下調用callInitialize(cls);
這個函數里代碼是((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
給這個類發送消息調用initialize方法
四、關聯對象
objc_setAssociatedObject(id_Nonnull object,const void*_Nonnull key, ?id_Nullable value, objc_AssociationPolicy policy)
objc_AssociationPolicy policy:關聯策略
OBJC_ASSOCIATION_ASSIGN = 0,???????????????????????????? ????????assign;
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, ? ? ? ????? strong,nonatomic
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,? ? ? ????????copy,nonatomic
OBJC_ASSOCIATION_RETAIN = 01401,? ? ? ? ? ????????????????strong atomic
OBJC_ASSOCIATION_COPY = 01403? ? ? ? ? ? ????????????????copy,atomic
1、分類添加屬性
在分類h文件里,聲明屬性
@property (nonatomic,copy)NSString *name;
在m文件里寫set和get方法
- (void)setName:(NSString*)name{
objc_setAssociatedObject(self, MJNameKey, name,OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString*)name{
? return objc_getAssociatedObject(self, MJNameKey);
}
其中也可以把,方法的隱式參數當key傳進去綁定對象?- (NSString*)name:(id)self _cmd:(SEL)_cmd ?get方法中self和—_cmd是隱式參數.
2、關聯對象底層原理
實現關聯對象技術的核心對象有
1)AssociationsManager //管理所有關聯的屬性的類,全局的
2)AssciationsHashMap ?//存著所有關聯屬性的鍵值對,他存在于manager里
3)ObjectAssociationMap //一個對象的所有關聯屬性鍵值對,根據傳進來的self即object(我們調用runtime方法所傳)從AssciationsHashMap取得,這個提供的iterator
4)ObjcAssociation//每個關聯屬性的鍵值隊----值和策略。根據傳進來的key(我們調用runtime方法所傳)從ObjectAssociationMap取得
****************關聯對象屬性并不是存儲在被關聯對象本身內存中,而是存在全局的統一的一個AssciationsManager中的AssociationsHasMap中**********************************************
源碼路徑:
void objc_setAssociatedObject ->_object_set_associative_reference 在這個方法里面有上面四個核心對象。
這四個對象關系:
AssociationsManager 里存著AssciationsHashMap,AssciationsHashMap里存著我們傳進去的key:disguised_ptr_t和value:ObjectAssociationMap;ObjectAssociationMap里面存著key: void * 和value:ObjcAssociation,ObjcAssociation里存著我們傳的策略uintptr_t _policy 和值id value。
實現原理源代碼
//獲取全局關聯屬性管理器manager
AssociationsHashMap &associations(manager.associations());
//獲取Manager內的AssociationsHashMap(存著所有對象的關聯屬性map)
AssociationsHashMap &associations(manager.associations());
根據傳進來的object生成一個key
disguised_ptr_t disguised_object =DISGUISE(object);
//找到AssociationsHashMap里與disguised_object 相關的一個遍歷器,即與我們傳進去的object相關的一個遍歷器,這個遍厲器里的某項成員就是我們傳進去這個類的所有關聯屬性列表
AssociationsHashMap::iterator i = associations.find(disguised_object);
從這個遍歷器中找到ObjectAssociationMap
ObjectAssociationMap *refs = i->second;
根據key從ObjectAssociationMap對應的單個關聯屬性信息的對象
ObjectAssociationMap::iterator j = refs->find(key);
//從這個iterator里面拿到其中的second便是我們設置的ObjcAssociation(存著這個關聯屬性的策略和值)
old_association = j->second;
把新的值覆蓋進去
(*refs)[key] = ObjcAssociation(policy, new_value);
源碼里associations.end()//判斷整個項目里有沒有添加過關聯屬性,refs->end()判斷這個對象是否頭一次添加屬性。
結構如下圖: