啟動流程
objc-os.mm的init方法是初始化的入口禽翼。
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
幾個初始化
- environ_init族跛,讀取環(huán)境配置方法礁哄,在這個方法里會讀取在Xcode 中配置的環(huán)境變量參數(shù)
- tls_init桐绒,用于初始化不是使用pthread_key_create()創(chuàng)建的線程的析構(gòu)函數(shù)
- static_init之拨,執(zhí)行 C++ 靜態(tài)構(gòu)造函數(shù)功能
- lock_init蚀乔,初始化后臺線程和主線程優(yōu)先級
- exception_init菲茬,異常初始化
map_images
在map_images函數(shù)中,內(nèi)部也是做了一個調(diào)用中轉(zhuǎn)。然后調(diào)用到map_images_nolock函數(shù)爵赵,內(nèi)部核心就是_read_images函數(shù)。先整體梳理一遍_read_images函數(shù)內(nèi)部的邏輯:
- 加載所有類到類的gdb_objc_realized_classes表中。
- 對所有類做重映射威彰。
- 將所有SEL都注冊到namedSelectors表中穴肘。
- 修復(fù)函數(shù)指針遺留评抚。
- 將所有Protocol都添加到protocol_map表中。
- 對所有Protocol做重映射邢笙。
- 初始化所有非懶加載的類氮惯,進(jìn)行rw想暗、ro等操作说莫。
- 遍歷已標(biāo)記的懶加載的類,并做初始化操作互婿。
- 處理所有Category擒悬,包括Class和Meta Class。
- 初始化所有未初始化的類侈净。
load_images
在load_images函數(shù)中主要做了兩件事畜侦,
- 首先通過prepare_load_methods函數(shù)準(zhǔn)備Class load list和Category load list躯保,
- 然后通過call_load_methods函數(shù)調(diào)用已經(jīng)準(zhǔn)備好的兩個方法列表
isa
在ARM 64之前途事,isa是一個指針尸变,存儲著Class
、Meta-Class
對象的內(nèi)存地址碱工。
ARM 64之后怕篷,isa是一個共用體酗昼,除了存儲Class
麻削、Meta-Class
對象的內(nèi)存地址,還保存了更多的信息电抚。ARM 64情況下蝙叛,isa共用體的定義公给。
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
}
isa的位域的意義
- nonpointer
- 0 ,表示指針蔫缸,存儲Class
對象地址
- 1拾碌,使用共用體街望,存儲更多信息 - has_assoc
- 是否設(shè)置過關(guān)對象灾前。如果沒有,該對象會釋放地更快 - has_cxx_dtor
- 是否有C++析構(gòu)函數(shù)蔫敲。如果沒有奈嘿,該對象會釋放地更快 - shiftcls
- 類對象指么,元類對象的內(nèi)存地址 - magic
- 對象是否完成初始化 - weakly_referenced
- 是否被若引用指向過榴鼎。如果沒有巫财,該對象會釋放地更快 - deallocating
- 是否正在被釋放 - has_sidetable_rc
- 是否引用計數(shù)器過大哩陕,不能保存在isa中悍及。如果過大心赶,會保存在SideTable
中 - extra_rc
- 保存該對象的引用計數(shù)減1
method_t
struct method_t {
SEL name; // 底層結(jié)構(gòu)類似于char *
const char *types;
IMP imp; // 該表函數(shù)的具體實現(xiàn)
};
SEL
代表方法\函數(shù)名缨叫,一般叫做選擇器荔燎,底層結(jié)構(gòu)跟char *類似
- 可以通過
@selector()
和sel_registerName()
獲得 - 可以通過
sel_getName()
和NSStringFromSelector()
- 不同類中相同名字的方法有咨,所對應(yīng)的方法選擇器是相同的
types
包含了函數(shù)返回值座享,參數(shù)編碼的字符串
// i 24 @ 0 : 8 i 16 f 20
/* i 24纵装,表示所有參數(shù)占據(jù)的字節(jié)數(shù)
@ 0橡娄,表示第一個參數(shù)挽唉,self,從第0個參數(shù)開始
: 8匠童,表示第二個參數(shù)汤求,SEL严拒,從第8個開始
i 16裤唠,表示(int)age
f 20种蘸,表示(float)height
*/
- (void)test:(int)age height:(float)height {
}
IMP
表示函數(shù)的具體實現(xiàn)
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
cache_t
struct cache_t {
struct bucket_t *_buckets; // 數(shù)組
mask_t _mask; // 數(shù)組長度
mask_t _occupied; // 已經(jīng)使用的長度
}
struct bucket_t {
private:
cache_key_t _key; // SEL
IMP _imp;
}
散列表使用的hash算法,就是&運算
static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{
return (mask_t)(key & mask);
}
如果發(fā)生Hash碰撞诫硕,則從前一個開始找章办,直到索引為0纲菌。如果還找不到,就再從數(shù)組最后一個嚣潜,倒序開始找懂算。
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m);
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}
子類調(diào)用父類方法之后计技,先在子類找不到方法山橄,然后去父類找航棱,
- 也是先找父類的緩存饮醇,再找父類的方法列表
- 找到之后朴艰,會把方法緩存到子類的cache中
緩存的時候,如果緩存滿了侮穿,則清除所有緩存撮珠,并且2倍擴(kuò)容
void cache_t::expand()
{
cacheUpdateLock.assertLocked();
uint32_t oldCapacity = capacity();
uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
if ((uint32_t)(mask_t)newCapacity != newCapacity) {
// mask overflow - can't grow further
// fixme this wastes one bit of mask
newCapacity = oldCapacity;
}
reallocate(oldCapacity, newCapacity);
}
obj_msgSend()
OC中的方法調(diào)用,都是轉(zhuǎn)換成調(diào)用obj_msgSend()
函數(shù)驶俊。整個過程可以分成3個階段:
- 消息發(fā)送:根據(jù)OC對象模型圖免姿,從子類到父類,去找方法
- 動態(tài)方法解析:可能會向類對象添加方法
- 消息轉(zhuǎn)發(fā):可能將該方法調(diào)用想鹰,轉(zhuǎn)到其他對象去調(diào)用辑舷。
_class_lookupMethodAndLoadCache3
lookUpImpOrForward
getMethodNoSuper_nolock槽片、search_method_list、log_and_fill_cache
cache_getImp碌廓、log_and_fill_cache谷婆、getMethodNoSuper_nolock辽聊、log_and_fill_cache
_class_resolveInstanceMethod
_objc_msgForward_impcache
匯編調(diào)用
由于obj_msgSend()
調(diào)用十分頻繁身隐,所以obj_msgSend()
有一部分是使用匯編實現(xiàn)的贾铝。
一般如果是C函數(shù)是objc_msgSend
垢揩,則對應(yīng)的匯編函數(shù)就是加上下劃線_objc_msgSend
匯編做了如下工作:
- 判斷receiver是否為nil叁巨,如果是nil就直接返回
- 查找緩存,如果緩存沒有命中蚀瘸,就在方法列表查找方法
objc-msg-arm64.s
ENTRY _objc_msgSend
b.le LNilOrTagged
CacheLookup NORMAL
.macro CacheLookup
.macro CheckMiss
STATIC_ENTRY __objc_msgSend_uncached
.macro MethodTableLookup
__class_lookupMethodAndLoadCache3
查找方法
匯編函數(shù)是__class_lookupMethodAndLoadCache3
贮勃,則對應(yīng)的runtime方法是_class_lookupMethodAndLoadCache3
在C函數(shù)還是會查找一遍緩存寂嘉,原因是:再執(zhí)行到這次的查找緩存之前泉孩,可能動態(tài)添加一些方法,緩存方法變化显蝌。
- 在本類進(jìn)行查找
- 再次查找緩存曼尊,
- 查找方法骆撇,在本類對象的方法列表父叙,還可以分成二分查找(如果已經(jīng)排好序)和線性查找趾唱。如果找到了甜癞,會進(jìn)行緩存
- 不斷地向上悠咱,在父類進(jìn)行查找析既。不管是
- 先找父類的緩存。如果找到了拂玻,在自己的類緩存
- 在找父類的方法列表檐蚜。如果找到了熬甚,在自己的類緩存
動態(tài)方法解析
當(dāng)通過上面的方法查找,找不到方法智厌,就會進(jìn)入動態(tài)方法解析。
- 通過
triedResolver
敷扫,只解析一次 -
goto retry
會重新進(jìn)入方法查找:先查找本類緩存葵第,在找本類方法列表卒密;再父類
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
使用如下示例代碼哮奇,進(jìn)行動態(tài)方法解析
void c_anotherMethod(id self, SEL _cmd)
{
NSLog(@"c_another class method");
}
- (void)anotherTest {
NSLog(@"instance %s",__func__);
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 第一個參數(shù)是object_getClass(self)
class_addMethod(object_getClass(self), sel, (IMP)c_anotherMethod, "v16@0:8");
return YES;
}
return [super resolveClassMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
Method anotherMethod = class_getInstanceMethod(self, @selector(anotherTest));
class_addMethod(self,
sel,
method_getImplementation(anotherMethod),
method_getTypeEncoding(anotherMethod));
return YES;
}
return [super resolveInstanceMethod:sel];
}
消息轉(zhuǎn)發(fā)
首先是調(diào)用forwardingTargetForSelector:
,如果返回不為nil贸伐,就進(jìn)入target
的方法調(diào)用流程棍丐。forwarding:
沒有開源歌逢,可以通過逆向秘案,了解其實現(xiàn)的阱高。
如果最終能夠走到forwardInvocation:
赤惊,即使forwardInvocation:
是空實現(xiàn)凰锡,該方法調(diào)用也能成功完成。
- 調(diào)用forwardInvocation之前员串,系統(tǒng)會先調(diào)用
resolveInstanceMethod:
寸齐,傳入的selector
為_forwardStackInvocation:
- 如果實現(xiàn)
methodSignatureForSelector :
渺鹦,但是沒有實現(xiàn)forwardInvocation :
海铆,也會unrecognized selector
- (id)forwardingTargetForSelector:(SEL)aSelector {
return [[ForwardTarget alloc] init];
}
// 方法簽名:返回值類型卧斟、參數(shù)類型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v24@0:8@16"];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封裝了一個方法調(diào)用珍语,包括:方法調(diào)用者板乙、方法名拳氢、方法參數(shù)
// anInvocation.target 方法調(diào)用者
// anInvocation.selector 方法名
// anInvocation.methodSignature methodSignatureForSelector:方法返回的
// [anInvocation getArgument:NULL atIndex:0]
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation invokeWithTarget:[[ForwardTarget alloc] init]];
}
Super
[super message]的底層實現(xiàn),最初是objc_msgSendSuper(arg, sel)
馋评,其中第一個參數(shù)是結(jié)構(gòu)體放接,該結(jié)構(gòu)體包含兩個成員變量:
- 消息接收者,就是子類實例對象
- 父類類對象留特,找方法會從父類類對象開始找
struct objc_super {
__unsafe_unretained _Nonnull id receiver; // 消息接收者
__unsafe_unretained _Nonnull Class super_class; // 消息接收者的父類
};
后來實現(xiàn)變成objc_msgSendSuper2(arg, sel)
纠脾,第一個參數(shù)是結(jié)構(gòu)體,其成員變量是:
- 消息接收者蜕青,仍然是子類對象
- 消息接收者的類對象苟蹈。但是其內(nèi)部實現(xiàn)右核,仍然是獲取父類類對象慧脱,從父類開始找
struct objc_super2 {
__unsafe_unretained _Nonnull id receiver; // 消息接收者
__unsafe_unretained _Nonnull Class current_class; // 消息接收者的類對象
};
- (instancetype)init
{
if (self = [super init]) {
NSLog(@"[self class] = %@", [self class]); // MJStudent
NSLog(@"[self superclass] = %@", [self superclass]); // MJPerson
NSLog(@"--------------------------------");
// objc_msgSendSuper({self, [MJPerson class]}, @selector(class));
NSLog(@"[super class] = %@", [super class]); // MJStudent
NSLog(@"[super superclass] = %@", [super superclass]); // MJPerson
}
return self;
}
isKindOf
objc的源碼如下,要特別注意的是
-
+ (BOOL)isKindOfClass:
贺喝,要求傳入的是metaClass菱鸥,但是最后一步會走到NSObject的class對象宗兼。
// 這句代碼的方法調(diào)用者不管是哪個類(只要是NSObject體系下的),都返回YES
NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); // 1
NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]); // 0
NSLog(@"%d", [MJPerson isKindOfClass:[Person class]]); // 0
NSLog(@"%d", [MJPerson isMemberOfClass:[Person class]]); // 0
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
面試題
以下代碼中采缚,是否能夠正常打印
@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
- (void)print;
@end
@implementation MJPerson
- (void)print
{
NSLog(@"my name is %@", self->_name);
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [MJPerson class];
void *obj = &cls;
[(__bridge id)obj print];
}
首先分析针炉,變量之間的關(guān)系挠他,如下圖所示
- 與正常調(diào)用實例方法的結(jié)構(gòu)相同扳抽,都包含指針變量,對象的ISA指針殖侵,類對象
- 所以可以調(diào)用到
print
方法
執(zhí)行方法時贸呢,棧中變量地址的位置,如下圖所示
- 棧中的變量地址是從高到低拢军,即先出現(xiàn)的變量楞陷,占據(jù)高地址
-
[super viewDidLoad];
,上文說過茉唉,會有一個結(jié)構(gòu)體變量固蛾;接著是cls
,接著是obj
- 根據(jù)實例對象的結(jié)構(gòu)圖度陆,訪問
name
成員變量艾凯,就是訪問self
占據(jù)的指針
API使用
- 成員變量是基本數(shù)據(jù)類型,需要先轉(zhuǎn)成指針懂傀,再bridge
// 創(chuàng)建類
Class newClass = objc_allocateClassPair([NSObject class], "MJDog", 0);
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_weight", 4, 1, @encode(int));
class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
// 注冊類
objc_registerClassPair(newClass);
// 如果是基本數(shù)據(jù)類型趾诗,需要先轉(zhuǎn)成指針,再bridge
object_setIvar(person, ageIvar, (__bridge id)(void *)10);
具體應(yīng)用
- 利用關(guān)聯(lián)對象(AssociatedObject)給分類添加屬性
- 遍歷類的所有成員變量(修改textfield的占位文字顏色蹬蚁、字典轉(zhuǎn)模型恃泪、自動歸檔解檔)
- 交換方法實現(xiàn)(交換系統(tǒng)的方法,
method_exchangeImplementions
會清空方法緩存 - 利用消息轉(zhuǎn)發(fā)機(jī)制解決方法找不到的異常問題
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class aClass = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
// When swizzling a class method, use the following:
// Class aClass = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(aClass, originalSelector);
// Method swizzledMethod = class_getClassMethod(aClass, swizzledSelector);
BOOL didAddMethod =
class_addMethod(aClass,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(aClass,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
數(shù)組和字典的方法交換
集合類型犀斋,由于使用了類簇.
- 可變數(shù)組真正的類型是
__NSArrayM
- 可變字典真正的類型是
__NSDictionaryM
- 不可變字典真正的類型是
__NSDictionaryI
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 類簇:NSString贝乎、NSArray、NSDictionary叽粹,真實類型是其他類型
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(my_insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
});
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = NSClassFromString(@"__NSDictionaryM");
Method method1 = class_getInstanceMethod(cls, @selector(setObject:forKeyedSubscript:));
Method method2 = class_getInstanceMethod(cls, @selector(my_setObject:forKeyedSubscript:));
method_exchangeImplementations(method1, method2);
Class cls2 = NSClassFromString(@"__NSDictionaryI");
Method method3 = class_getInstanceMethod(cls2, @selector(objectForKeyedSubscript:));
Method method4 = class_getInstanceMethod(cls2, @selector(my_objectForKeyedSubscript:));
method_exchangeImplementations(method3, method4);
});