一、Runtime的介紹
- Objective-C是一門動態(tài)性比較強的編程語言,跟C、C++等語言有著很大的不同
- Objective-C的動態(tài)性是由Runtime API來支撐的
- Runtime API提供的接口基本都是C語言的,源碼由C\C++\匯編語言編寫
二、isa詳解
要想學習Runtime,首先要了解它底層的一些常用數據結構,比如isa指針
在arm64架構之前,isa就是一個普通的指針,存儲著Class、Meta-Class對象的內存地址
-
從arm64架構開始,對isa進行了優(yōu)化,變成了一個共用體(union)結構,還使用位域來存儲更多的信息
isa詳解g -
isa詳解 – 位域
- nonpointer
- 0,代表普通的指針,存儲著Class、Meta-Class對象的內存地址
- 1,代表優(yōu)化過,使用位域存儲更多的信息
- has_assoc
- 是否有設置過關聯對象,如果沒有,釋放時會更快
- has_cxx_dtor
- 是否有C++的析構函數(.cxx_destruct),如果沒有,釋放時會更快
- shiftcls
- 著Class、Meta-Class對象的內存地址信息
- magic
- 用于在調試時分辨對象是否未完成初始化
- weakly_referenced
- 是否有被弱引用指向過,如果沒有,釋放時會更快
- deallocating
- 對象是否正在釋放
- extra_rc
- 里面存儲的值是引用計數器減1
- has_sidetable_rc
- 引用計數器是否過大無法存儲在isa中
- 如果為1,那么引用計數會存儲在一個叫SideTable的類的屬性中
- nonpointer
什么是isa?
答:在 arm64之前isa就是一個普通的指針,里面就是直接存儲的類對象和元類對象的地址值,但是從arm64位之后,isa經過優(yōu)化,它采用共用體的結構,將一個64位的內存數據分開來存儲了很多的東西,而其中的33位來存儲具體的地址值的。
三、objc_msgSend執(zhí)行流程可以分為3大階段
OC的方法調用:消息機制(objc_msgSend(id receiver, SEL selector)),給方法調用者發(fā)送消息
源碼解讀
-
3.1、消息發(fā)送(其實也就是isa與superclass尋找方法(
對象方法或類方法))消息發(fā)送(MJ老師做的圖)
- 如果是從 class_rw_t 中查找方法: 已經排序的二分查找,沒有排序的遍歷查找
- receiver通過isa指針找到receiverClass
- receiverClass通過superclass指針找到superClass -
3.2、動態(tài)方法解析(在消息發(fā)送機制找不到方法之后進行動態(tài)方法解析)
動態(tài)方法解析-
可以實現以下方法,來動態(tài)添加方法實現
+ (BOOL)resolveInstanceMethod:(SEL)sel;(對象方法的動態(tài)添加)+ (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(test)) { // 獲取其他方法 Method method = class_getInstanceMethod(self, @selector(other)); // 動態(tài)添加test方法的實現 class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method)); // 返回YES代表有動態(tài)添加方法 return YES; } return [super resolveInstanceMethod:sel]; } -(void)other{ NSLog(@"%s",__func__); }+ (BOOL)resolveClassMethod:(SEL)sel;
(類方法的動態(tài)添加)+ (BOOL)resolveClassMethod:(SEL)sel { if (sel == @selector(classTest)) { // 第一個參數是object_getClass(self) //class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8"); // 獲取其他方法 Method method = class_getClassMethod(self, @selector(classOther)); // 動態(tài)添加test方法的實現 class_addMethod(object_getClass(self), sel, method_getImplementation(method), method_getTypeEncoding(method)); // 返回YES代表有動態(tài)添加方法 return YES; } return [super resolveClassMethod:sel]; } #pragma mark 類方法的轉換(類方法classTest找不到就轉換classOther方法) +(void)classOther{ NSLog(@"類方法classTest找不到就轉換classOther方法"); }Runtime動態(tài)轉換方法的demo Person 類
-
-
3.3、消息轉發(fā)
消息轉發(fā)解釋上面的圖:
-
3.3.1、當動態(tài)解析過都找不到一個方法時候就會走消息轉發(fā),首先會調用
forwardingTargetForSelector(前面可能是+(void)或者-(void)),如果不返回nil就會走 返回類的 Selector,如果返回nil就走 3.3.2-(id)forwardingTargetForSelector:(SEL)aSelector{ if (aSelector == @selector(test)) { //return [[People alloc]init]; return nil; } return [super forwardingTargetForSelector:aSelector]; } -
3.3.2、當forwardingTargetForSelector返回nil或者不寫這個方法時候,就會調用下面的方法
#pragma mark 2 /** 方法簽名:返回值類型、參數類型 */ - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { if (aSelector == @selector(test)) { // 獲取其他方法(一),會走 #pragma mark 3 /* Method method = class_getInstanceMethod([People class], @selector(test)); return [NSMethodSignature signatureWithObjCTypes:method_getTypeEncoding(method)]; */ // 下面這一句和上面2句一個意思(二) 會走 #pragma mark 3 return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"]; /** 如果返回nil 就不會再走會走 #pragma mark 3 方法了,會報錯返回(doesNotRecognizeSelector) -[NSObject(NSObject) doesNotRecognizeSelector:] + 132 */ // return nil; } return [super methodSignatureForSelector:aSelector]; } #pragma mark 3.上面的方法不返回nil才會走 /** NSInvocation封裝了一個方法調用,包括:方法調用者、方法名、方法參數 anInvocation.target 方法調用者 anInvocation.selector 方法名 [anInvocation getArgument:NULL atIndex:0] */ - (void)forwardInvocation:(NSInvocation *)anInvocation { // anInvocation.target = [[MJCat alloc] init]; // [anInvocation invoke]; [anInvocation invokeWithTarget:[[People alloc] init]]; }當上面的
#pragma mark 2返回nil的時候,就不會走#pragma mark 3,且報錯doesNotRecognizeSelector
methodSignatureForSelector 返回nil報錯 doesNotRecognizeSelector
Runtime消息轉發(fā)的demo,看Student與People類
-
四、Runtime的常見API
-
4.1、API-類
-
動態(tài)創(chuàng)建一個類 (參數:父類,類名,額外的內存空間)
/** 三個參數: Class _Nullable __unsafe_unretained superclass: 父類 const char * _Nonnull name: 新的類名字 size_t extraBytes: 需不需要為這個類增加額外的內存空間 */ objc_allocateClassPair(Class _Nullable __unsafe_unretained superclass, const char * _Nonnull name, size_t extraBytes) -
注冊一個類 (要在類注冊之前添加成員變量)
objc_registerClassPair(Class _Nonnull __unsafe_unretained cls) -
銷毀一個類
objc_disposeClassPair(Class _Nonnull __unsafe_unretained cls) -
獲取isa指向的Class
object_getClass(id _Nullable obj) 返回值: BOOL -
設置isa指向的Class
object_setClass(id _Nullable obj, Class _Nonnull __unsafe_unretained cls) 返回值: Class -
判斷一個OC對象是否為 Class
object_isClass(id _Nullable obj) 返回值: BOOL -
判斷一個Class是否為元類
class_isMetaClass(Class _Nullable __unsafe_unretained cls) 返回值: BOOL -
獲取父類
class_getSuperclass(Class _Nullable __unsafe_unretained cls) 返回值: Class
-
-
4.2、API-成員變量
-
獲取一個實例變量
class_getInstanceVariable(Class _Nullable __unsafe_unretained cls, const char * _Nonnull name) -
設置和獲取成員變量的值
object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) object_getIvar(id _Nullable obj, Ivar _Nonnull ivar) -
動態(tài)添加成員變量(已經注冊的類是不能動態(tài)添加成員變量的)
/** Class _Nullable __unsafe_unretained cls : 創(chuàng)建的新類 const char * _Nonnull name : 成員變量的名字 size_t size: 占用多少個字節(jié) uint8_t alignment: 對其,寫 1 就好 const char * _Nullable types: 類型 */ class_addIvar(Class _Nullable __unsafe_unretained cls, const char * _Nonnull name, size_t size, uint8_t alignment, const char * _Nullable types) -
拷貝實例變量列表(最后需要調用free釋放)
class_copyIvarListunsigned int count; Ivar *ivars = class_copyIvarList([JKPerson class], &count); for (int i = 0; i < count; i++) { // 取出i位置的成員變量 Ivar ivar = ivars[I]; NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar)); } free(ivars);舉例:上面的應用,我們可以把JKPerson 把它換成 UILabel 、UIButton 、UITextView等等來窺探一個類的成員變量信息,下面以UITextFiled為例,這是 demo
UITextFiled的成員變量打印
可以看到上面的打印,_placeholderLabel @"UITextFieldLabel",我們可以看到 UITextFiled的Placeholder的類是UITextFieldLabel,通過打印通過下面的打印我們可以看到UITextFieldLabel UILabelUITextField *textFiled = [[UITextField alloc]initWithFrame:CGRectMake(50, 100, 200, 50)]; textFiled.placeholder = @"請輸入手機號"; [self.view addSubview:textFiled]; id placeholderLabel = [textFiled valueForKeyPath:@"_placeholderLabel"]; NSLog(@"%@ %@",[placeholderLabel class],[placeholderLabel superclass]); 打印結果是:UITextFieldLabel UILabel所以我們可以設置UITextFiled的 placeholder 顏色新的方式
第1種修改顏色 [textFiled setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"]; 第2種修改顏色 UILabel *placeholderLabel = [textFiled valueForKeyPath:@"_placeholderLabel"]; placeholderLabel.textColor = [UIColor greenColor]; -
獲取成員變量相關信息(使用挨著的上面)
const char *ivar_getName(Ivar v) const char *ivar_getTypeEncoding(Ivar v)
-
-
4.3、API-屬性
-
獲取一個屬性
objc_property_t class_getProperty(Class cls,const char *name) -
拷貝屬性列表
objc_property_t *class_copyPropertyList(Class cls,unsigned int *outCount) -
動態(tài)添加屬性
BOOL class_addProperty(Class cls,const char *name,const objc_property_attribute_t *attributes,unsigned int attributeCount) -
動態(tài)替換屬性
void class_replaceProperty(Class cls,const char *name,const objc_property_attribute_t *attributes,unsigned int attributeCount) -
獲取一些屬性的信息
const char *property_getName(objc_property_t property) const char *property_getAttributes(objc_property_t property)
-
-
4.4、API-方法(
method_exchangeImplementations是比較常用的,這是demo)-
獲得一個實例方法、類方法
Methon class_getClassMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name) Methon class_getInstanceMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name) -
方法實現相關操作
IMP class_getMethodImplementation(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name) IMP method_setImplementation(Method _Nonnull m, IMP _Nonnull imp) void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) -
拷貝方法列表(最后需要調用free釋放)
Methon class_copyMethodList(Class _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount) -
動態(tài)添加方法
BOOL class_addMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) -
動態(tài)替換方法
IMP class_replaceMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) -
獲取方法的相關信息(帶有copy的需要調用free去釋放)
SEL method_getName(Method _Nonnull m) IMP method_getImplementation(Method _Nonnull m) const char *method_getTypeEncoding(Method _Nonnull m) unsigned int method_getNumberOfArguments(Method _Nonnull m) char *method_copyReturnType(Method _Nonnull m) char *method_copyArgumentType(Method _Nonnull m, unsigned int index) -
選擇器相關
const char *sel_getName(SEL _Nonnull sel) SEL sel_registerName(const char * _Nonnull str) -
用block作為方法實現
IMP imp_implementationWithBlock(id _Nonnull block) id imp_getBlock(IMP _Nonnull anImp) BOOL imp_removeBlock(IMP _Nonnull anImp)
-
五、常見面試題
5.1、講一下 OC 的消息機制
答:OC 中的方法調用其實都是轉成了 objc_msgSend 函數的調用,給receiver(方法調用者) 發(fā)送一條消息(selector方法名),objc_msgSend底層有3大階段(會回答這3個階段看是上面的三)5.2、消息轉發(fā)機制流程
答:看上面的 3.3-
5.3、說出下面的打印
題目內容: People 和Student兩個類,Student繼承于People,People繼承于NSObject
Student繼承于People#import "Student.h" @implementation Student -(void)printContent{ NSLog(@"[self class] = %@",[self class]); NSLog(@"[self superclass] = %@",[self superclass]); NSLog(@"[super class] = %@",[super class]); NSLog(@"[super superclass] = %@",[super superclass]); } @end 打印結果為: [self class] = Student [self superclass] = People [super class] = Student [super superclass] = People解釋:
- [self class] 是打印 類,很明顯是 Student;
- [self superclass] 是通過superclass查找父類,Student繼承于People,所以打印時People
- [super class] 其實相比[self class]在本質上它們的 receiver 都是 self,所以在打印上都是 Student
- [super superclass] 其實相比[self superclass]在本質上它們的 receiver 都是 self,所以在打印上都是 People
-
5.4、說一下下面的打印,Student是繼承于NSObject的類
Student是繼承于NSObject的類id student = [[Student alloc]init]; NSLog(@"%d",[student isMemberOfClass:[Student class]]); NSLog(@"%d",[student isMemberOfClass:[NSObject class]]); NSLog(@"%d",[student isKindOfClass:[Student class]]); NSLog(@"%d",[student isKindOfClass:[NSObject class]]); 打印結果是: 1 0 1 1解釋:先展示一下OC的源碼
+ (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; }-
5.4.1、先說
-號的或者說實例方法的- (BOOL)isMemberOfClass:(Class)cls與- (BOOL)isKindOfClass:(Class)cls-
isMemberOfClass: 是判斷一個對象是否是某一個類,看OC源碼是判斷
return [self class] == cls;,可以看出是判斷是否相同類,一對一,如下id student = [[Student alloc]init]; NSLog(@"%d",[student isMemberOfClass:[Student class]]); 打印是 1 NSLog(@"%d",[student isMemberOfClass:[NSObject class]]); 打印是 0 -
isKindOfClass: 是判斷一個對象是否是某一個類或者是某一個類的子類,看上面OC源碼是用了for循環(huán)進行查詢
id student = [[Student alloc]init]; NSLog(@"%d",[student isMemberOfClass:[Student class]]); 打印是 1 NSLog(@"%d",[student isMemberOfClass:[NSObject class]]); 打印是 0 NSLog(@"%d",[student isKindOfClass:[Student class]]); 打印是 1 NSLog(@"%d",[student isKindOfClass:[NSObject class]]); 打印是 1
-
-
5.4.2、再說
+號的或者說類方法的+(BOOL)isMemberOfClass:(Class)cls與+ (BOOL)isKindOfClass:(Class)cls-
isMemberOfClass: 是判斷一個對象是否是某一個類,看OC源碼是判斷
return [self class] == cls;,可以看出是判斷是否相同類,一對一,如下NSLog(@"%d",[Student isMemberOfClass:object_getClass([Student class])]); 打印是 1 NSLog(@"%d",[Student isMemberOfClass:object_getClass([NSObject class])]); 打印是 0 -
isKindOfClass: 是判斷一個對象是否是某一個類或者是某一個類的子類,看上面OC源碼是用了for循環(huán)進行查詢
NSLog(@"%d",[Student isKindOfClass:object_getClass([Student class])]); 打印是 1 NSLog(@"%d",[Student isKindOfClass:object_getClass([NSObject class])]); 打印是 1
-
-
5.4.3、說一個特殊的情況:一看下面的 [NSObject class] 大多數人都會認為結果是 0,正常情況下是如此的,但是呢,Student 類的 基類 NSObject 的superclass指向的是 自己的類對象 [NSObject class],故下面的結果是 1
NSLog(@"%d",[Student isKindOfClass:[NSObject class]]); 打印結果為 1說明:不管上面的 Student 是什么類型,結果都是 1,因為通過superclass都會回到 [NSObject class]
-
5.4.4、下面是一張有關 isa與superclass的圖
isa與superclass的圖
-
-
5.5、什么是 Runtime ?平時項目中用過嗎?
答:方法交換,窺探某一個類的成員變量,字典轉model等等
六:另外增加的測試demo









