1图仓、什么是Runtime
- Objective-C是一門(mén)動(dòng)態(tài)性比較強(qiáng)的編程語(yǔ)言,跟C跳芳、C++等語(yǔ)言有著很大的不同
- Objective-C的動(dòng)態(tài)性是由Runtime API來(lái)支撐的
- Runtime API提供的接口基本都是C語(yǔ)言的洲脂,源碼由C\C++\匯編語(yǔ)言編寫(xiě)
2、isa詳解
- 在arm64架構(gòu)之前励饵,isa就是一個(gè)普通的指針驳癌,存儲(chǔ)著Class、Meta-Class對(duì)象的內(nèi)存地址
- 從arm64架構(gòu)開(kāi)始役听,對(duì)isa進(jìn)行了優(yōu)化颓鲜,變成了一個(gè)共用體(union)結(jié)構(gòu),還使用位域來(lái)存儲(chǔ)更多的信息
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
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;
};
};
-
nonpointer
0代表普通的指針典予,存儲(chǔ)著Class甜滨、Meta-Class對(duì)象的內(nèi)存地址; 1代表優(yōu)化過(guò),使用位域存儲(chǔ)更多的信息瘤袖。 -
has_assoc
是否有設(shè)置過(guò)關(guān)聯(lián)對(duì)象衣摩,如果沒(méi)有,釋放時(shí)會(huì)更快捂敌。 -
has_cxx_dtor
是否有C++的析構(gòu)函數(shù)(.cxx_destruct)艾扮,如果沒(méi)有,釋放時(shí)會(huì)更快占婉。 -
shiftcls
存儲(chǔ)著Class泡嘴、Meta-Class對(duì)象的內(nèi)存地址信息。 -
magic
用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化逆济。 -
weakly_referenced
是否有被弱引用指向過(guò)酌予,如果沒(méi)有,釋放時(shí)會(huì)更快奖慌。 -
deallocating
對(duì)象是否正在釋放霎终。 -
has_sidetable_rc
引用計(jì)數(shù)器是否過(guò)大無(wú)法存儲(chǔ)在isa中。如果為1升薯,那么引用計(jì)數(shù)會(huì)存儲(chǔ)在一個(gè)叫SideTable
的類(lèi)的屬性中 -
extra_rc
里面存儲(chǔ)的值是引用計(jì)數(shù)器減1莱褒。
3、Class的結(jié)構(gòu)
struct objc_class {
Class isa;
Class superclass;
cache_t cache; // formerly cache pointer and vtable涎劈, 方法緩存
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags广凸, 用于獲取具體的類(lèi)信息
class_rw_t *data() {
return bits.data();
}
};
class_rw_t
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro; //編譯期的只讀信息
method_array_t methods; //方法列表
property_array_t properties; //屬性列表
protocol_array_t protocols; //協(xié)議列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
-
class_rw_t
里面的methods阅茶、properties、protocols是二維數(shù)組谅海,是可讀可寫(xiě)的脸哀,包含了類(lèi)的初始內(nèi)容、分類(lèi)的內(nèi)容扭吁。
class_rw_t的method_array_t.jpg
class_ro_t
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; //instance對(duì)象占用的內(nèi)存空間
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; //類(lèi)名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars; //成員變量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
};
-
class_ro_t
里面的baseMethodList撞蜂、baseProtocols、ivars侥袜、baseProperties是一維數(shù)組蝌诡,是只讀的,包含了類(lèi)的初始內(nèi)容枫吧。
class_ro_t的method_list_t.jpg
method_t
-
method_t
是對(duì)方法\函數(shù)的封裝
struct method_t {
SEL name; //函數(shù)名
const char *types; //編碼 (返回類(lèi)型浦旱、參數(shù)類(lèi)型)
IMP imp; //指向函數(shù)的指針(函數(shù)地址)
};
-
IMP
代表函數(shù)的具體實(shí)現(xiàn)
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
-
SEL
代表方法\函數(shù)名,一般叫做選擇器九杂,底層結(jié)構(gòu)跟char *
類(lèi)似- 可以通過(guò)
@selector()
和sel_registerName()
獲得 - 可以通過(guò)
sel_getName()
和NSStringFromSelector()
轉(zhuǎn)成字符串 - 不同類(lèi)中相同名字的方法颁湖,所對(duì)應(yīng)的方法選擇器是相同的
- 可以通過(guò)
typedef struct objc_selector *SEL;
- types包含了函數(shù)返回值、參數(shù)編碼的字符串
返回值 | 參數(shù)1 | 參數(shù)2 | ...... | 參數(shù)n |
---|
-
Type Encoding
iOS中提供了一個(gè)叫做@encode
的指令例隆,可以將具體的類(lèi)型表示成字符串編碼
方法緩存
- Class內(nèi)部結(jié)構(gòu)中有個(gè)方法緩存
cache_t
甥捺,用散列表(哈希表)來(lái)緩存曾經(jīng)調(diào)用過(guò)的方法,可以提高方法的查找速度镀层。
struct cache_t {
struct bucket_t *_buckets; //散列表
mask_t _mask; //散列表的長(zhǎng)度減1
mask_t _occupied; //已經(jīng)緩存的方法數(shù)量
};
struct bucket_t {
private:
cache_key_t _key; //SEL作為Key
IMP _imp; //函數(shù)的內(nèi)存地址
};
- 緩存查找
//objc-cache.mm
bucket_t * cache_t::find(cache_key_t k, id receiver)
4镰禾、objc_msgSend執(zhí)行流程
- OC中的方法調(diào)用,其實(shí)都是轉(zhuǎn)換為
objc_msgSend
函數(shù)的調(diào)用 -
objc_msgSend
的執(zhí)行流程可以分為3大階段 - 消息發(fā)送
- 動(dòng)態(tài)方法解析
- 消息轉(zhuǎn)發(fā)
- objc_msgSend源碼執(zhí)行流程
//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
STATIC_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
//objc-runtime-new.mm
_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
//Core Foundation
__forwarding__(不開(kāi)源)
4.1 objc_msgSend 消息發(fā)送流程
objc_msgSend執(zhí)行流程01-消息發(fā)送.jpg
- 如果是從
class_rw_t
中查找方法
已經(jīng)排序的博投,二分查找
沒(méi)有排序的绸贡,遍歷查找 - receiver通過(guò)
isa
指針找到receiverClass - receiverClass通過(guò)
superclass
指針找到superClass
4.2 objc_msgSend 動(dòng)態(tài)方法解析
objc_msgSend執(zhí)行流程02-動(dòng)態(tài)方法解析.jpg
- 可以實(shí)現(xiàn)以下方法,來(lái)動(dòng)態(tài)添加方法實(shí)現(xiàn)
+resolveInstanceMethod:
+resolveInstanceMethod:
- 動(dòng)態(tài)解析過(guò)后毅哗,會(huì)重新走“消息發(fā)送”的流程
從receiverClass的cache中查找方法這一步開(kāi)始執(zhí)行
動(dòng)態(tài)添加類(lèi)方法
@interface Person : NSObject
+ (void)test;
@end
@implementation Person
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(test)) {
Class metaClass = object_getClass(self);//元類(lèi)
Method method = class_getClassMethod(metaClass, @selector(other));
// 動(dòng)態(tài)添加test方法的實(shí)現(xiàn)
class_addMethod(metaClass, sel,
method_getImplementation(method),
method_getTypeEncoding(method));
// 返回YES代表有動(dòng)態(tài)添加方法
return YES;
}
return [super resolveClassMethod:sel];
}
+ (void)other {
NSLog(@"%s", __func__);
}
@end
動(dòng)態(tài)添加實(shí)例方法
@interface Person : NSObject
- (void)test;
@end
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
Class class = self;//Class類(lèi)
Method method = class_getInstanceMethod(class, @selector(other));
// 動(dòng)態(tài)添加test方法的實(shí)現(xiàn)
class_addMethod(class, sel,
method_getImplementation(method),
method_getTypeEncoding(method));
// 返回YES代表有動(dòng)態(tài)添加方法
return YES;
}
return [super resolveInstanceMethod:sel];
}
- (void)other {
NSLog(@"%s", __func__);
}
@end
4.3 objc_msgSend 消息轉(zhuǎn)發(fā)
objc_msgSend的執(zhí)行流程03-消息轉(zhuǎn)發(fā).jpg
- 可以在
forwardInvocation:
方法中自定義任何邏輯 - 以上方法都有對(duì)象方法听怕、類(lèi)方法2個(gè)版本(前面可以是加號(hào)+,也可以是減號(hào)-)
forwardingTargetForSelector
實(shí)例方法
@interface Cat : NSObject
- (void)test;
@end
@implementation Cat
- (void)test {
NSLog(@"%s", __func__);
}
@end
@interface Person : NSObject
- (void)test;
@end
@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [[Cat alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
類(lèi)方法
@interface Cat : NSObject
+ (void)test;
@end
@implementation Cat
+ (void)test {
NSLog(@"%s", __func__);
}
@end
@interface Person : NSObject
+ (void)test;
@end
@implementation Person
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [Cat class];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
forwardInvocation
實(shí)例方法
@interface Cat : NSObject
- (void)test;
@end
@implementation Cat
- (void)test {
NSLog(@"%s", __func__);
}
@end
@interface Dog : NSObject
- (void)test;
@end
@implementation Dog
- (void)test {
NSLog(@"%s", __func__);
}
@end
@interface Person : NSObject
- (void)test;
@end
@implementation Person
// 方法簽名:返回值類(lèi)型虑绵、參數(shù)類(lèi)型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
/*
NSInvocation封裝了一個(gè)方法調(diào)用尿瞭,包括:方法調(diào)用者、方法名翅睛、方法參數(shù)
anInvocation.target 方法調(diào)用者
anInvocation.selector 方法名
[anInvocation getArgument:NULL atIndex:0]
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation invokeWithTarget:[[Cat alloc] init]];
[anInvocation invokeWithTarget:[[Dog alloc] init]];
}
@end
類(lèi)方法
@interface Cat : NSObject
+ (void)test;
@end
@implementation Cat
+ (void)test {
NSLog(@"%s", __func__);
}
@end
@interface Dog : NSObject
+ (void)test;
@end
@implementation Dog
+ (void)test {
NSLog(@"%s", __func__);
}
@end
@interface Person : NSObject
+ (void)test;
@end
@implementation Person
// 方法簽名:返回值類(lèi)型声搁、參數(shù)類(lèi)型
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation invokeWithTarget:[Cat class]];
[anInvocation invokeWithTarget:[Dog class]];
}
@end
4.4 降低doesNotRecognizeSelector崩潰
@interface Person : NSObject
- (void)run;
- (void)test;
- (void)other;
@end
@implementation Person
- (void)run {
NSLog(@"run-123");
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
// 本來(lái)能調(diào)用的方法
if ([self respondsToSelector:aSelector]) {
return [super methodSignatureForSelector:aSelector];
}
// 找不到的方法
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
// 找不到的方法黑竞,都會(huì)來(lái)到這里
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"找不到%@方法", NSStringFromSelector(anInvocation.selector));
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person run];
[person test];
[person other];
}
return 0;
}
找不到方法輸出.jpg
寫(xiě)一個(gè)NSObject的分類(lèi)解決找不到方法問(wèn)題
@implementation NSObject (ForwardInvocation)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = self;
Method method1 = class_getInstanceMethod(cls, @selector(methodSignatureForSelector:));
Method method2 = class_getInstanceMethod(cls, @selector(fi_methodSignatureForSelector:));
method_exchangeImplementations(method1, method2);
Method method3 = class_getClassMethod(cls, @selector(methodSignatureForSelector:));
Method method4 = class_getClassMethod(cls, @selector(fi_methodSignatureForSelector:));
method_exchangeImplementations(method3, method4);
});
}
- (NSMethodSignature *)fi_methodSignatureForSelector:(SEL)aSelector {
if ([self respondsToSelector:aSelector]) {
return [self fi_methodSignatureForSelector:aSelector];
}
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
+ (NSMethodSignature *)fi_methodSignatureForSelector:(SEL)aSelector {
if ([self respondsToSelector:aSelector]) {
return [self fi_methodSignatureForSelector:aSelector];
}
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"找不到%@方法", NSStringFromSelector(anInvocation.selector));
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"找不到%@方法", NSStringFromSelector(anInvocation.selector));
}
@end
5、super的本質(zhì)
- super調(diào)用疏旨,底層會(huì)轉(zhuǎn)換為objc_msgSendSuper2函數(shù)的調(diào)用很魂,接收2個(gè)參數(shù)
struct objc_super2
SEL
struct objc_super2 {
id receiver;
Class current_class;
};
-
receiver
是消息接收者 -
current_class
是receiver
的Class
對(duì)象
@interface Person : NSObject
@end
@implementation Person
@end
@interface Student : Person
@end
@implementation Student
- (instancetype)init {
if (self = [super init]) {
NSLog(@"[self class] = %@", [self class]);
NSLog(@"[self superclass] = %@", [self superclass]);
NSLog(@"[super class] = %@", [super class]);
NSLog(@"[super superclass] = %@", [super superclass]);
}
return self;
}
@end
superClass輸出.jpg
看objc源碼
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
+ (Class)superclass {
return self->superclass;
}
- (Class)superclass {
return [self class]->superclass;
}
struct objc_super {
__unsafe_unretained _Nonnull id receiver; // 消息接收者
__unsafe_unretained _Nonnull Class super_class; // 消息接收者的父類(lèi)
};
-
[super message]
的底層實(shí)現(xiàn)- 消息接收者仍然是子類(lèi)對(duì)象
- 從父類(lèi)開(kāi)始查找方法的實(shí)現(xiàn)
//[super run]轉(zhuǎn)化的源碼
struct __rw_objc_super {
struct objc_object *object;
struct objc_object *superClass;
};
static void _I_Student_run(Student * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("run"));
}
簡(jiǎn)化下相當(dāng)于
struct __rw_objc_super arg = {self, [Person class]};
objc_msgSendSuper(arg, @selector(run));
[super class]
輸出Student的原因
[super superclass]
輸出Person的原因,
-
[super class]
中的class
方法只在NSObject
才有檐涝。 - 調(diào)用的是
object_getClass(self);
, -
self
實(shí)際上是Student對(duì)象
遏匆,對(duì)應(yīng)Student類(lèi) - 同理
[super superclass]
--->[self class]->superclass
, 對(duì)應(yīng)Person類(lèi)
6、isKindOfClass谁榜、isMemberOfClass方法
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 這句代碼的方法調(diào)用者不管是哪個(gè)類(lèi)(只要是NSObject體系下的)幅聘,都返回YES
NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); // 1
NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]); // 0
NSLog(@"%d", [Person isKindOfClass:[Person class]]); // 0
NSLog(@"%d", [Person isMemberOfClass:[Person class]]); // 0
}
return 0;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
isa-superclass.png
7、Runtime應(yīng)用
- 利用關(guān)聯(lián)對(duì)象(
AssociatedObject
)給分類(lèi)添加屬性 - 遍歷類(lèi)的所有成員變量(修改
textfield
的占位文字顏色惰爬、字典轉(zhuǎn)模型喊暖、自動(dòng)歸檔解檔) - 利用消息轉(zhuǎn)發(fā)機(jī)制解決方法找不到的異常問(wèn)題
- 交換方法實(shí)現(xiàn)(交換系統(tǒng)的方法)
Method Swizzling
8、練習(xí)
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
- (void)print;
@end
@implementation Person
- (void)print {
NSLog(@"name = %@", self.name);
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [Person class];
void *obj = &cls;
[(__bridge id)obj print];
}
@end
調(diào)用輸出
練習(xí)輸出.jpg
1撕瞧、為什么print能打印成功
- obj存儲(chǔ)的是cla的地址陵叽,cls存儲(chǔ)的地址是
[Person class]
的地址。 - 與實(shí)例對(duì)象調(diào)用類(lèi)似丛版,person存儲(chǔ)的是isa的地址巩掺,通過(guò)isa找到
[Person class]
,調(diào)用print方法。所以能調(diào)用成功
Person *person = [[Person alloc] init];
[person print];
cls指向問(wèn)題.jpg
2页畦、輸出為什么變?yōu)?code>ViewController或者其他的
[super viewDidLoad];
轉(zhuǎn)化為C++函數(shù)胖替,其實(shí)有個(gè)臨時(shí)的self變量
struct abc = {
self,
[ViewController class]
};
objc_msgSendSuper2(abc, sel_registerName("viewDidLoad"));
viewDidLoad
方法各個(gè)臨時(shí)變量在函數(shù)中的內(nèi)存地址是在棧中的,棧的地址是從高到低的豫缨,地址分布如下:
棧地址.jpg
cls相當(dāng)于isa, 其下面一個(gè)地址就是_name的地址独令,所以會(huì)輸出ViewController
。