一.探索前需知swift
1.1 程序在啓動時,編譯好的mach-o文件內容如何加載到App內存裏?
bash
其實上篇文章已經介紹了程序一運行如何將編譯好的mach-o文件內容如何加載到App內存 裏,一開始回來到 _objc_init(void),程序初始化函數,在這裏會進行整個程序的一些環境配置、異常配置、靜態構析函數配置等,而後會map_images讀取鏡像文件的過程.函數
1.2 map_images 作了哪些工做?ui
void _read_images {
// 1:第一次進來 - 開始建立表
// gdb_objc_realized_classes : 全部類的表 - 包括實現的和沒有實現的
// allocatedClasses: 包含用objc_allocateClassPair分配的全部類(和元類)的表。(已分配)
if (!doneOnce) {
doneOnce = YES;
// namedClasses
// Preoptimized classes don't go in this table. // 4/3 is NXMapTable's load factor
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
}
// 2:類處理
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[i];
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
}
// 3: 方法編號處理
for (EACH_HEADER) {
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
sels[i] = sel_registerNameNoLock(name, isBundle);
}
}
// 4: 協議處理
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
NXMapTable *protocol_map = protocols();
protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
// 5: 非懶加載類處理
for (EACH_HEADER) {
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
addClassTableEntry(cls);
realizeClassWithoutSwift(cls);
}
// 6: 待處理的類
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
Class cls = resolvedFutureClasses[i];
if (cls->isSwiftStable()) {
_objc_fatal("Swift class is not allowed to be future");
}
realizeClassWithoutSwift(cls);
cls->setInstancesRequireRawIsa(false/*inherited*/);
}
free(resolvedFutureClasses);
}
// 7:分類處理
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);
}
}
}
複製代碼
gdb_objc_realized_classes
表中。SEL
都註冊到namedSelectors
表中。Protocol
都添加到protocol_map
表中。Protocol
作重映射。rw
、ro
等操做。Category
,包括Class
和Meta Class
。1.3 什麼是非懶加載類和懶加載類?this
咱們看到map_images裏有一個步驟是初始化全部非懶加載類,進行rw
、ro
等操做.那麼到底什麼狀況會初始化非懶加載類?咱們如今一個工程裏建立LGPerson、LGStudent、LGTeacher 三個類.atom
而後在待處理類裏面打印出非加載的類:printf("non-lazy Class:%s\n",cls->mangledName());spa
打印結果以下:ssr
發現打印了LGStudent、LGTeacher 和系統的一大堆類,可是LGPerson爲何沒打印呢?3d
原來LGStudent、LGTeacher裏實現了+(void)load的方法,而LGPerson卻沒有實現+(void)load的方法.因此能夠得出結論:非懶加載的類,就是實現了+(void)load的方法和靜態實例變量,其實也好理解+(void)load 方法調用在main函數調用以前,因此係統確定先幫你這個類先初始化了,因此就是非懶加載的,而懶加載的類就是沒有實現+(void)load,系統不幫你加載,等到你何時用何時加載,這就是懶加載的類.指針
1.4 懶加載的類什麼時候讀取並進行rw
、ro
等操做?
懶加載的類是何時讀取的呢,是你第一次用到的時候,也就是你第一次建立並使用的時候.也就是
看過我以前文章的盆友必定會知道,它在底層會發送消息,會先進行快速查找和慢速查找流程,第一次進來的話會走慢速查找流程,來到 lookUpImpOrForward
if (!cls->isRealized()) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
複製代碼
由於這是懶加載的類,以前並無被實現因此必然回來到這個判斷裏來.
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
lock.assertLocked();
if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
// Non-Swift class. Realize it now with the lock still held.
// fixme wrong in the future for objc subclasses of swift classes
realizeClassWithoutSwift(cls);
if (!leaveLocked) lock.unlock();
} else {
// Swift class. We need to drop locks and call the Swift
// runtime to initialize it.
lock.unlock();
cls = realizeSwiftClass(cls);
assert(cls->isRealized()); // callback must have provoked realization
if (leaveLocked) lock.lock();
}
return cls;
}
複製代碼
看裏面又來到了 realizeClassWithoutSwift,和非懶加載類後面的流程是如出一轍的 .
二.分類加載的初探
2.1 分類的結構
若是一個開發者沒有底層objc的源碼,怎麼分析分類的結構,其實以前的文章也介紹這個方法 clang.
首先咱們在工程裏建立個分類:
而後打開終端 輸入命令:clang -rewrite-objc LGTeacher+test.m -o LGteacherTest.cpp(輸出個.cpp編譯後的文件)
發現這個文件裏有 :
static struct _category_t _OBJC_$_CATEGORY_LGTeacher_$_test __attribute__ ((used, section ("__DATA,__objc_const")))
test 表明類別的名字,_category_t 這個就是類別的結構體. 咱們在objc源碼裏搜索c:
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 底層是一個結構體 ,裏面有name、cls、instanceMethods、classMethods、protocols、instanceProperties、_classProperties, name 就是類別的名字,
cls就是類,instanceMethods 就是實例方法,classMethods就是類方法,protocols就是類別裏的協議.
2.2 分類的加載
既然分類的結構已經清楚了,下面咱們就要探索分類是如何加載到主類裏面的.
LGTeacher.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGTeacher : NSObject
@property (nonatomic, copy) NSString *name;
+ (void)sayMaster;
@end
NS_ASSUME_NONNULL_END
複製代碼
LGTeacher.m
#import "LGTeacher.h"
@implementation LGTeacher
+ (void)load{
NSLog(@"load");
}
+ (void)sayMaster{
NSLog(@"%s",__func__);
}
@end
複製代碼
LGTeacher+test.h
#import <AppKit/AppKit.h>
#import "LGTeacher.h"
NS_ASSUME_NONNULL_BEGIN
@interface LGTeacher (test)
@property (nonatomic, copy) NSString *cate_p1;
//@property (nonatomic, copy) NSString *cate_p2;
//- (void)cate_instanceMethod1;
- (void)cate_instanceMethod2;
//+ (void)cate_classMethod1;
+ (void)cate_classMethod2;
@end
複製代碼
LGTeacher+test.m
#import "LGTeacher+test.h"
#import <objc/runtime.h>
#import <AppKit/AppKit.h>
@implementation LGTeacher (test)
+ (void)load{
NSLog(@"分類 load");
}
- (void)setCate_p1:(NSString *)cate_p1{
}
- (NSString *)cate_p1{
return @"cate_p1";
}
- (void)cate_instanceMethod2{
NSLog(@"%s",__func__);
}
+ (void)cate_classMethod2{
NSLog(@"%s",__func__);
}
@end
複製代碼
有些盆友會說,探究前已經分析過 read_images 裏有個方法,
// 7:分類處理
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; } if (PrintConnecting) { _objc_inform("CLASS: found category -%s(%s) %s", cls->nameForLogging(), cat->name, classExists ? "on existing class" : ""); } } 複製代碼
由於category 裏是實例方法,因此必然會走 if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties) 這裏面,addUnattachedCategoryForClass 添加沒有加進去的分類,
在這裏進行添加的唄.那咱們就來驗證下, 首先咱們在addUnattachedCategoryForClass) 這句代碼中打上斷點,
來分析此時的class結構:
此時類裏面rw的method裏有三個方法: cxx_destruct、 setName、getName,
而主類裏是:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGTeacher : NSObject
@property (nonatomic, copy) NSString *name;
+ (void)sayMaster;
@end
NS_ASSUME_NONNULL_END
複製代碼
說明此時主類的方法以及被加載到rw裏了,由於第五步是非懶加載類已經初始化過了,主類的rw、ro都有東西啦,有的盆友可能會問: + (void)sayMaster 沒有加到呀,
其實仍是之前文章提到過的問題, 類方法添加到元類裏,並非自己的類裏.
咱們接着往下分析進入到addUnattachedCategoryForClass 裏面:
這一步主要是將沒有加載的categoryList 和 類關聯起來 而且加到NXMap 表裏.
緊接着 又來到(第五步非懶加載已經初始化過,判斷裏必走):
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
複製代碼
remethodizeClass --> attachCategories -- >進入方法實現
在這代碼最後打下斷點再分析 cls 結構:
結構以下:
分類裏的方法是否是也加載到rw 裏了,
全部的一切一切都獲得了驗證,都是那麼看得見摸得着, 那麼分類的加載就分析完畢了嗎?NO,NO,NO 這纔是冰山一角, 當前類、和分類都實現了load 方法, 說明這是非懶加載類 和非懶加載分類的搭配使用,還有更多狀況請看下面.
三.分類加載的進階
在上面咱們介紹了 懶加載類和非懶加載類,那麼一樣分類也分懶加載和非懶加載,因此它們一塊兒搭配使用就有四種狀況:
懶加載的分類(不實現 load 方法 )
3.11 懶加載的分類(不實現 load 方法 )與非懶加載的類搭配使用(實現 load 方法 )
咱們知道非懶加載的類加載時,必然會走read_images - realizeClassWithoutSwift - methodlizeClass
咱們這打個斷點看看此時的類結構:
個人天,此時ro裏面有分類的方法、屬性了,這該怎麼解釋呢? 其實這是由於在編譯的時候已經把分類的信息給讀取到ro裏面了.
3.12 懶加載的分類(不實現 load 方法 )與懶加載的類搭配使用 (不實現 load 方法 )
本身實現懶加載 :Class cls = [LGTeacher class];
懶加載的類第一次調用時,它在底層會發送消息,會先進行快速查找和慢速查找流程,第一次進來的話會走慢速查找流程,來到 lookUpImpOrForward
分析此時的LGPerson結構, 注意哦 咱們上面調用的是對象方法,在方法查找中會在元類裏進行查找,因此這個方法中cls 參數表明 元類, 咱們來看下元類的結構:
個人天, 元類的ro有 cate_classMethod2,也就是分類的方法.我把分類的代碼粘一下:
#import "LGTeacher+test.h"
#import <objc/runtime.h>
#import <AppKit/AppKit.h>
@implementation LGTeacher (test)
//+ (void)load{
// NSLog(@"分類 load");
//}
- (void)setCate_p1:(NSString *)cate_p1{
}
- (NSString *)cate_p1{
return @"cate_p1";
}
- (void)cate_instanceMethod2{
NSLog(@"%s",__func__);
}
+ (void)cate_classMethod2{
NSLog(@"%s",__func__);
}
@end
複製代碼
lookUpImpOrForward 剛進來時 ,元類裏的ro就已經有分類的方法了.由此能夠得出結論:
懶加載的分類 方法- 編譯處理 - 直接處理 data() - ro
非懶加載的分類(實現 load 方法 )
3.21 非懶加載的分類(實現 load 方法 )與非懶加載的類搭配使用(實現 load 方法 )
上面初探時已經分析過, read_images - realizeClassWithoutSwift - methodlizeClass - addUnattachedCategoryForClass - 判斷是否實現 - 這裏看到上面一行就在read_images 實現了:
if (cls->isRealized()) {
remethodizeClass(cls); -> 實現類信息
} attachCategories 加載分類數據進來
3.22 非懶加載的分類(實現 load 方法 )與懶加載的類搭配使用(不實現 load 方法 )
這種狀況就比較特殊, 按以前分析的 非懶加載的分類 會走到 read_images -->
// 分類的處理
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);
}
}
}複製代碼
懶加載的類以前分析會第一次調用時,它在底層會發送消息,會先進行快速查找和慢速查找流程,第一次進來的話會走慢速查找流程,來到 lookUpImpOrForward.
但在這裏想一下:運行時分類都加載好了,主類第一次加載還要等到第一次消息發送時,是否是太慢了?
因此這裏和其它流程不同的地方是:先走個 prepare_load_methods :
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
const class_ro_t *ro = (const class_ro_t *)cls->data();
const char *cname = ro->name;
const char *oname = "LGTeacher";//LGTeacher
// printf("類名 :%s \n",cname);
if (cname && (strcmp(cname, oname) == 0)) {
// printf("prepare_load_methods :非懶加載分類名 :%s \n",cname);
}
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls);
}
}
複製代碼
在這個方法裏面會有: realizeClassWithoutSwift(cls). 在這裏進行初始化類信息, rw的賦值.
四.總結
本篇文章先介紹了懶加載類和非懶加載類,而後分4種組合狀況深刻研究了類的加載和分類加載的原理.其中:
1.分類的懶加載在編譯期就已經肯定了.
2. 分類的非懶加載在read_images 在非懶加載分類中addUnattachedCategoryForClass 添加沒有加進去的分類,最後remethodizeClass --> attachCategories,纔會對rw進行操做.
3.類的非懶加載也是在read_images裏進行非懶加載的類初始化.
4.最後類的懶加載分爲兩種狀況:若是分類也是懶加載的話,它在底層會發送消息,會先進行快速查找和慢速查找流程,第一次進來的話會走慢速查找流程,來到 lookUpImpOrForward,進行初始化類,若是分類也是非懶加載的話,先走個 prepare_load_methods 在這個方法裏面會有: realizeClassWithoutSwift(cls). 在這裏進行初始化類信息, rw的賦值.
說明:文章中不恰當的地方,還但願看到的盆友提出,本身會及時改正