Objective C RunTime System (一、基本結構) === 寫OC也有一段時間了~之前一直在研究Objective C的RunTime,應該稱為Runtime系統,國內博客對Runtime的文章甚少,翻牆查閱文獻大量英文,書籍也翻閱甚多,因為近段時間家裡親人生病,一時很忙,文章斷 ...
Objective-C RunTime System (一、基本結構)
寫OC也有一段時間了~之前一直在研究Objective-C的RunTime,應該稱為Runtime系統,國內博客對Runtime的文章甚少,FQ查閱文獻大量英文,書籍也翻閱甚多,因為近段時間家裡親人生病,一時很忙,文章斷斷續續的一直在寫,博客也很久沒有更新~現在發表出來與大家分享~
一、什麼叫運行時?
電腦只能識別二進位的語言,0和1,高級語言在程式運行時需要執行的操作是編譯和鏈接,高級語言需要編譯為彙編語言,彙編語言再通過鏈接生成二進位的機器碼,也就是機器語言,電腦才能識別。程式在運行時執行的這兩步操作統稱為運行時。Runtime~
二、動態功能
程式在編譯和鏈接時確定對象類型和方法解析,這些操作都是在動態完成的,稱為動態功能。
三、對象消息
對象消息,也就是對象發送消息~在OC中對象發送消息用[ ],如下:
[person run]
中括弧之間前面為對象,後面為消息,對象也就是接收器,有的也叫作加法器,是消息傳遞的目的地。消息是由選擇器和參數構成,也可以說由加數構成。如下圖所示~
四、選擇器
在Objective-C的消息傳遞中,選擇器是一種文本字元串,用於指明調用對象或類中的哪個方法。選擇器是一種分為多段的文本字元串,每個字元串以冒號分割,冒號後接參數~
五 SEL類型
SEL是Objective-C中的一種特殊的數據類型,它叫做選擇器類型,SEL。用於編譯時替換選擇器的唯一標識~具有相同選擇器值的方法都擁有相同的SEL標識符,Objective-C在運行時會確保每一個選擇器標識的唯一性。
5.1 SEL類型的創建
使用關鍵字@selector創建SEL類型的變數,用於描述消息選擇器和參數的類型,例如~
SEL myMethod = @selector(myMethod)
5.2 SEL類型的用處
在Objective-C中,有一些方法需要用選擇器作為參數,這時候就需要用到SEL類型~如下:
[button addTarget:self action:@selector(didClickButton:) forControlEvents:UIControlEventTouchUpInside];
在這裡,SEL類型的選擇器@selector(didClickButton:)
作為參數傳遞給方法addTarget:(nullable id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents
,以上例子也可以寫為如下:
SEL selector = NSSelectorFromString(didClickButton:)
[button addTarget:self action:selector forControlEvents:UIControlEventTouchUpInside];
六、方法簽名(method signature)
6.1 什麼是方法簽名?
SEL類型描述了選擇器和參數個數,可是還需要確定參數類型和返回值類型,這時候就有了方法簽名,定義方法輸入的參數類型和方法返回值的數據類型,叫做方法簽名~
6.2 運行時如何實現對象消息傳遞?
編譯器在編譯時會將對象消息轉換為聲明中含有方法簽名的C函數調用語句,,為了生成正確的對象消息傳遞代碼,編譯器將獲得選擇器和方法簽名,編譯器可以從對象消息中拿出SEL類型的選擇器和確定參數,但是編譯器沒有辦法確定參數類型和返回值類型,也就是沒有辦法獲取方法簽名。編譯器為了確定方法簽名,編譯器會根據自己已經解析好的方法聲明進行猜測,如果編譯器找不到方法簽名,或則編譯器從方法聲明中獲取到的方法簽名與運行時實際執行的方法不匹配,會導致程式出現崩潰狀態~下麵演示一個典型錯誤~
6.3 方法簽名不匹配典型錯誤~
下麵定義了兩個類,分別繼承於NSObject,其中都有方法setPersonAge:
,當
@interface Person1 : NSObject
-(void)setPersonAge:(int)age;
@end
@interface Person2 : NSObject
-(void)setPersonAge:(NSInteger)age;
@end
七、對象發送消息
首先創建一個Calculator類繼承與NSObejct:
Calculator.h
#import <Foundation/Foundation.h>
@interface Calculator : NSObject
- (NSNumber *) sumAddend1:(NSNumber *)adder1 addend2:(NSNumber *)adder2;
- (NSNumber *) sumAddend1:(NSNumber *)adder1 :(NSNumber *)adder2;
@end
Calculator.m
@implementation Calculator
- (NSNumber *) sumAddend1:(NSNumber *)adder1 addend2:(NSNumber *)adder2 {
NSLog(@"Invoking method on ##%@## object with selector ##%@##",[self className],NSStringFromSelector(_cmd));
return [NSNumber numberWithInteger:(adder1.integerValue + adder2.integerValue)];
}
- (NSNumber *) sumAddend1:(NSNumber *)adder1 :(NSNumber *)adder2 {
NSLog(@"Invoking method on ##%@## object with selector ##%@##",[self className],NSStringFromSelector(_cmd));
return [NSNumber numberWithInteger:(adder1.integerValue + adder2.integerValue)];
}
@end
先解釋一下這句代碼~NSStringFromSelector(_cmd)
,使用Foundation框架的NSStringFromSelector函數可以獲得一個選擇器的字元串,這個函數輸入的參數是一個SEL類型的變數,這裡輸入的參數是_cmd,那麼其中的_cmd參數是從何而來的叻?_cmd是一個隱式參數(即無需聲明就存在於所有Objective-C方法中的參數),含有被髮送消息中的選擇器,所以~NSStringFromSelector(_cmd)
返回被調用方法的選擇器文本字元串~
main.m
#import <Foundation/Foundation.h>
#import "Calculator.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Calculator *cal = [Calculator new];
NSNumber *num1 = [NSNumber numberWithInteger:1];
NSNumber *num2 = [NSNumber numberWithInteger:2];
NSNumber *num3 = [NSNumber numberWithInteger:3];
NSLog(@"%@",[cal sumAddend1:num1 addend2:num2]);
NSLog(@"%@",[cal sumAddend1:num1 :num3]);
}
return 0;
}
控制台列印輸出~
2016-04-19 23:55:56.257 Test[2914:1095161] Invoking method on ##Calculator## object with selector ##sumAddend1:addend2:##
2016-04-19 23:55:56.258 Test[2914:1095161] 3
2016-04-19 23:55:56.258 Test[2914:1095161] Invoking method on ##Calculator## object with selector ##sumAddend1::##
2016-04-19 23:55:56.258 Test[2914:1095161] 4
Program ended with exit code: 0
使用performSelector:
方法
main.m
#import <Foundation/Foundation.h>
#import "Calculator.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Calculator *cal = [Calculator new];
NSNumber *num1 = [NSNumber numberWithInteger:1];
NSNumber *num2 = [NSNumber numberWithInteger:2];
NSNumber *num3 = [NSNumber numberWithInteger:3];
SEL selector1 = @selector(sumAddend1:addend2:);
id sum1 = [cal performSelector:selector1 withObject:num1 withObject:num2];
NSLog(@"%@",sum1);
SEL selector2 = @selector(sumAddend1::);
id sum2 = [cal performSelector:selector2 withObject:num1 withObject:num3];
NSLog(@"%@",sum2);
}
return 0;
}
控制台列印~
2016-04-20 00:02:02.220 Test[3011:1117690] Invoking method on ##Calculator## object with selector ##sumAddend1:addend2:##
2016-04-20 00:02:02.221 Test[3011:1117690] 3
2016-04-20 00:02:02.221 Test[3011:1117690] Invoking method on ##Calculator## object with selector ##sumAddend1::##
2016-04-20 00:02:02.221 Test[3011:1117690] 4
Program ended with exit code: 0
在程式運行時,會出現兩條警告~PerformSelector may cause a leak because its selector is unknown
,這句話的意思是如果找不到該選擇器,他就崩給你看~,如果您是處女座~您可以選擇選擇添加Pragma指令來去除這兩條警告~
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
SEL selector1 = @selector(sumAddend1:addend2:);
id sum1 = [cal performSelector:selector1 withObject:num1 withObject:num2];
NSLog(@"%@",sum1);
SEL selector2 = @selector(sumAddend1::);
id sum2 = [cal performSelector:selector2 withObject:num1 withObject:num3];
NSLog(@"%@",sum2);
#pragma clang diagnostic pop
使用pragma指令clang diagnostic ignored可以禁用編譯器報出警告~,該指令語法為:#pragma clang diagnostic ignored "診斷功能的名稱"
,那問題來了~我們怎麼才知道這個診斷功能的名稱叻?OK ~ 看了下圖~你一切都明白了~
OK,Runtime 的基本構造就寫到這裡吧~最後發表一下小小的感言~做程式員多年~每天的工作都是坐著~大家有空沒空還是多多運動~健康很重要~突然想起了一句話~人生苦短~快用Python,哈哈,不是打廣告~最後祝大家身體健康~
Author
Programming by Erma
Blog Erma-king