iOS開發之Runtime初探
一:基礎概念
RunTime簡稱執行時,就是系統在執行的時候的一些機制,其中最主要的是訊息機制。
對於C語言,函式的呼叫在編譯的時候會決定呼叫哪個函式,編譯完成之後直接順序執行,無任何二義性。
OC的函式呼叫成為訊息傳送。屬於動態呼叫過程。在編譯的時候並不能決定真正呼叫哪個函式(事實證明,在編 譯階段,OC可以呼叫任何函式,即使這個函式並未實現,只要申明過就不會報錯。而C語言在編譯階段就會報錯)。
只有在真正執行的時候才會根據函式的名稱找 到對應的函式來呼叫。
Objective-C 從三種不同的層級上與 Runtime 系統進行互動,分別是:
1、通過 Objective-C 原始碼;
2、通過 Foundation 框架的NSObject類定義的方法;
3、通過對 runtime 函式的直接呼叫。
大部分情況下你就只管寫你的Objc程式碼就行,runtime 系統自動在幕後辛勤勞作著。
二:runtime的具體實現
我們寫的oc程式碼,它在執行的時候也是轉換成了runtime方式執行的,更好的理解runtime,也能幫我們更深的掌握oc語言。每一個oc的方法,底層必然有一個與之對應的runtime方法。
當我們用OC寫下這樣一段程式碼
[tableView cellForRowAtIndexPath:indexPath];
在編譯時RunTime會將上述程式碼轉化成[傳送訊息]
objc_msgSend(tableView, @selector(cellForRowAtIndexPath:),indexPath);
三:常見方法
1、獲取屬性列表
//People.h
@interface People : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSUInteger age;
@end
//People.m
@interface People() {
NSString *aaa;
}
@property (nonatomic, strong) NSString *fatherName;
@end
@implementation People {
NSString *bbb;
}
@end
//獲取所有屬性
-(void)getAllProperty {
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList([People class], &count);
for (unsigned int i=0; i<count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
}
}
列印結果:
結論:
1)、不管是在.h檔案中定義的屬性還是在.m檔案中定義的屬性,都可以通過獲取屬性列表方法來進行獲取;
2)、成員變數不同於屬性,不能通過該方法來獲取;
3)、先輸出的是.m檔案中的屬性,然後才是.h檔案中的屬性,並且是按照屬性定義的先後順序來儲存。
2、獲取方法列表
//People.h
@interface People : NSObject
@property (nonatomic, strong) NSString *name;
-(void)iPrintName;
+(void)cPrintName;
@end
//People.m
@interface People() {
NSString *aaa;
}
@property (nonatomic, strong) NSString *fatherName;
@end
@implementation People {
NSString *bbb;
}
//-(void)printName {
//
//}
+(void)cPrintName {
}
-(void)printAge {
}
@end
//獲取方法(不包括類方法)列表
-(void)getAllMethod {
unsigned int count = 0;
Method *methodList = class_copyMethodList([People class], &count);
for (unsigned int i=0; i<count; i++) {
Method method = methodList[i];
NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
}
}
結論:
1)、類方法不能通過這個函式去獲取到;
2)、只有在.m檔案中實現了的方法才能被獲取到,在.h檔案中定義了,但是.m中沒有實現的並不能獲取到;
3)、對於使用@property定義的屬性,會自動生成setter和getter方法,同樣能被這個方法獲取到;
4)、.m實現中還隱藏了一個.cxx_destruct也就是oc中常見delloc方法;
5)、儲存順序是優先儲存使用者在.m檔案中實現的,其次是.m屬性自動生成的getter和setter方法,然後是隱藏的delloc方法,最後是.h屬性自動生成的getter和setter方法。
3、獲取成員變數列表
//People.h
@interface People : NSObject {
NSString *cccc;
}
@property (nonatomic, strong) NSString *name;
@end
//People.m
@interface People() {
NSString *aaa;
}
@property (nonatomic, strong) NSString *fatherName;
@end
@implementation People {
NSString *bbb;
}
@end
-(void)getAllIvar{
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList([People class], &count);
for (unsigned int i=0; i<count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
}
}
列印結果:
結論:
1)、成員變數的儲存是從.h檔案開始,然後才是.m檔案中的成員變數;
2)、用@property 定義的屬性,會自動生成以_開頭的成員變數,也是先儲存.h檔案生成的,再儲存.m檔案生成的。
4、獲取協議列表
//People.h
@protocol PeopleDelegate <NSObject>
-(void)people;
@end
@interface People : NSObject
@property (nonatomic, strong) NSString *name;
//@property (nonatomic, weak) id <PeopleDelegate> delegate;
@end
//People.m
@interface People()
@property (nonatomic, strong) NSString *fatherName;
@end
@implementation People
@end
//ViewController.m
@interface ViewController ()<PeopleDelegate,UITabBarDelegate,UITableViewDataSource>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self getProtocal];
}
@end
//獲取協議列表
-(void)getProtocal {
unsigned int count = 0;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count); //這裡變成了self
for (unsigned int i = 0; i<count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
}
}
列印結果:
結論:
1)、只要宣告遵循該協議,在引用的時候,就能獲取到該類包含的協議列表,哪怕你沒有指定代理的物件,也沒有去實現協議的方法
5、獲取類方法與例項方法以及方法交換
//People.h
@interface People : NSObject
@property (nonatomic, strong) NSString *name;
-(void)printInstanceMethod;
+(void)printClassMethod;
@end
//People.m
@interface People()
@property (nonatomic, strong) NSString *fatherName;
@end
@implementation People
-(void)printInstanceMethod{
NSLog(@"我是例項方法");
}
+(void)printClassMethod {
NSLog(@"我是類方法");
}
@end
示例
-(void)getMethod {
People * p1 = [[People alloc] init];
Method m1 = class_getInstanceMethod([p1 class], @selector(printInstanceMethod));
Method m2 = class_getClassMethod([People class], @selector(printClassMethod));
NSLog(@"測試前:");
[p1 printInstanceMethod];
[People printClassMethod];
method_exchangeImplementations(m1, m2);
NSLog(@"測試後:");
[p1 printInstanceMethod];
[People printClassMethod];
}
列印結果
6、新增方法
當專案中,需要繼承某一個類(subclass),但是父類中並沒有提供我需要的呼叫方法,而我又不清楚父類中某些方法的具體實現;或者,我需要為這個類寫一個分類(category),在這個分類中,我可能需要替換/新增某個方法(注意:不推薦在分類中重寫方法,而且也無法通過 super 來獲取所謂父類的方法)。大致在這兩種情況下,我們可以通過 class_addMethod 來實現我們想要的效果。參考
//Car+MyCar.h
#import "Car+MyCar.h"
#import <objc/runtime.h>
void startEngine(id self, SEL _cmd, NSString *brand) {
NSLog(@"my %@ car starts the engine", brand);
}
@implementation Car (MyCar)
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(drive)) {
class_addMethod([self class], sel, (IMP)startEngine, "[email protected]:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
不習慣C語言程式碼可以替換成以下程式碼:
- (void)startEngine:(NSString *)brand {
NSLog(@"my %@ car starts the engine", brand);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(drive)) {
class_addMethod([self class], sel, class_getMethodImplementation(self, @selector(startEngine:)), "[email protected]:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
呼叫:
-(void)addMethod {
Car *c = [[Car alloc] init];
[c performSelector:@selector(drive) withObject:@"BMW"];
}
解釋:
在 Objective-C 中,正常的呼叫方法是通過訊息機制(message)來實現的,那麼如果類中沒有找到傳送的訊息方法,系統就會進入找不到該方法的處理流程中,如果在這個流程中,我們加入我們所需要的新方法,就能實現執行過程中的動態添加了。這個流程或者說機制,就是 Objective-C 的 Message Forwarding 。
這個機制中所涉及的方法主要有兩個:
+ (BOOL)resolveInstanceMethod:(SEL)sel;//例項方法
+ (BOOL)resolveClassMethod:(SEL)sel;//類方法
這個函式在 runtime 環境下,如果沒有找到該方法的實現的話就會執行。第一行判斷的是傳入的 SEL 名稱是否匹配,接著呼叫 class_addMethod 方法,傳入相應的引數。其中第三個引數傳入的是我們新增的 C 語言函式的實現,也就是說,第三個引數的名稱要和新增的具體函式名稱一致。第四個引數指的是函式的返回值以及引數內容。
結果:
裝逼:
一開始我以為class_addMethod和class_replaceMethod就等同於method_exchangeImplementations,但是看了下面的程式碼才發現,其實這倆的適用條件是不一樣的,當方法沒有實現的時候才能使用class_addMethod和class_replaceMethod套裝,但是當方法已存在的時候,就需要使用method_exchangeImplementations,這點從if (didAddMethod)這個判定就可見一斑。
+(void)load{
NSString *className = NSStringFromClass(self.class);
NSLog(@"classname %@", className);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//要特別注意你替換的方法到底是哪個性質的方法
// When swizzling a Instance method, use the following:
// Class class = [self class];
// When swizzling a class method, use the following:
Class class = object_getClass((id)self);
SEL originalSelector = @selector(systemMethod_PrintLog);
SEL swizzledSelector = @selector(ll_imageName);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
Runtime博大精深,看的越深入,感覺越懵 T_T,淺顯的認知,歡迎大家提意見