本博客主要分以下幾個方面來介紹iOS中的runtime
- Runtime的概念介紹
- iOS中的消息機制
- Runtime的作用
Objective-C語言中的Runtime概念
- 動態編程語言和靜態編程語言的區別
- 動態編程語言:在程序運行過程中可以改變數據類型的結構,對象的函數,變量可以被修改刪除。例如OC
- 靜態編程語言:在程序編譯階段檢查數據的類型,數據類型的結構不可以在運行時被改變。例如C
- Runtime---運行時系統是一個有公開接口的動態庫,由一些數據結構和函數的集合組成,這些數據結構和函數的聲明頭文件,在/usr/include/objc,這些函數支持用純C的函數來實現和Objective-C同樣的功能
- Runtime---是開源的,目前蘋果公司和GNU各自維護一個開源的runtime版本,apple open runtime;
- Runtime---在OC中的使用方式
- 通過Objective-C源代碼
- 通過類NSObject的方法
class 返回對象的類
isKindeOfClass:和isMemberOfClass: 檢查對象是否在指定的類繼承體系中
respondsToSelector: 檢查對象能否響應指定的消息
conformsToProtocol: 檢查對象是否實現了指定協議類的方法
methodForSelector: 返回指定方法實現的地址
- 通過運行時系統的函數
通過導入頭文件"#import<objc/message.h>"
調用相關函數
iOS中的消息機制
- Objective-C中消息機制的相關概念
- message(消息) --包括了函數名+參數列表的一種抽象
- method(方法)-- 是真正的存在的代碼。如:- (int)meaning { return 42; }
- selector(方法選擇器)--通過SEL類型存在,描述一個特定的method 或者說 message。在實際編程中,可以通過selector進行檢索方法等操作
- SEL(方法選擇器) -- 是一個char*指針,僅僅表示它所代表的方法名字,SEL只是一個指向方法的指針(準確的說,只是一個根據方法名hash化了的KEY值,能唯一代表一個方法),它的存在只是為了加快方法的查詢速度
//SEL
SEL selector = @selector(message); //@selector不是函數調用,只是給這個坑爹的編譯器的一個提示
NSLog (@"%s", (char *)selector); //print message
//以下函數命名方式這被認為是一種編譯錯誤
-(void)setWidth:(int)width;
-(void)setWidth:(double)width;
- IMP --函數指針
- 我們可以通過SEL方便、快速、準確的獲得它所對應的IMP(也就是函數指針),而在取得了函數指針之后,也就意味著我們取得了執行的時候的這段方法的代碼的入口,這樣我們就可以像普通的C語言函數調用一樣使用這個函數指針。當然我們可以把函數指針作為參數傳遞到其他的方法,或者實例變量里面,從而獲得極大的動態性
Runtime的作用
-
關聯對象:主要為分類增加屬性和實例變量。
能用擴展一般不用繼承,因為隨著繼承深度的增加,代碼可維護性變差
static char myKey; //定義一個靜態字符 /*! * 給關聯對象賦值 * * @param self 需要添加關聯的分類 * @param key const void *,key僅僅是一個地址,不是字符串內容。具體指向內容不用關心 * @param value 關聯對象的值 * @param policy 關聯策略,類似于對象的屬性修飾符,OBJC_ASSOCIATION_RETAIN_NONATOMIC等 * */ objc_setAssociatedObject(self, &myKey, model, OBJC_ASSOCIATION_RETAIN_NONATOMIC); //給關聯對象賦值 /*! * 根據關聯的key,獲取關聯的值 * * @param self 需要添加關聯的分類 * @param myKey onst void *,key僅僅是一個地址,不是字符串內容。具體指向內容不用關心 * * @return 關聯的對象的值 */ objc_getAssociatedObject(self, &myKey);
獲取對象的私有變量,私有方法,動態添加屬性、方法等。
- 獲取類名
```objectivec
Class class = [objc class];
class_getName(class);
class_getSuperclass(class);
```
- 獲取成員變量(可以獲取私有成員變量)
CaculatorMaker *make = [[CaculatorMaker alloc] init];
Class objcClass = [make class];
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(objcClass, &outCount);
for (int i = 0; i<outCount; i++) {
Ivar ivar = ivars[i];
const char *ivarString = ivar_getName(ivar);
NSLog(@"the variable of make is %s",ivarString);
}
free(ivars);
- 獲取屬性
/*!
* 獲取對象的所有屬性和屬性值
*
* @param object 對象
*
* @return 屬性和屬性值數組
*/
- (NSMutableArray *)getObjectPropertyAndValues:(id)object
{
NSMutableArray *array = [NSMutableArray new];
Class objcClass = [object class];
unsigned int outCount = 0 ;
objc_property_t *properties = class_copyPropertyList(objcClass, &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
const char *propertyName = property_getName(property);
NSString *propertyKey = [NSString stringWithUTF8String:propertyName];
NSString *propertyValue = [object valueForKey:propertyKey];
RACTuple *tuple = RACTuplePack(propertyKey,propertyValue);
[array addObject:tuple];
}
return array;
}
- 動態添加方法
CaculatorMaker *make = [[CaculatorMaker alloc] init];
/*!
* runtime添加方法
*
* @param class 被添加方法的類
* @param addMethod: 方法的名稱
* @param imp: 實現這個方法的函數
* @param type: 定義函數返回值類型和參數類型的字符串[Type Encodings](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)
* @return 添加成功或者失敗
*/
class_addMethod([make class], @selector(addMethod:), (IMP)addMethod, "i@:@");
[make performSelector:@selector(addMethod:) withObject:@"new Method"];
//添加的方法的具體實現
int addMethod(id self, SEL _cmd, NSString *str)
{
NSLog(@"new method is %@",str);
return 100;
}
- 動態添加屬性和成員變量均未成功,屬性添加完成之后的setter方法和getter方法不會實現。
- 消息轉發(message forwarding)-當一個對象無法接收某一消息時,就會啟動消息轉發機制。
通過這一機制,我們可以告訴對象如何處理未知的消息。默認情況下,對象接收到未知的消息,會導致程序崩潰。
- 消息轉發的步驟:1.動態方法解析 2.備用接收者 3. 完整轉發
- 動態方法解析:當對象接收到未實現的方法時,為自動調用resolveInstanceMethod:和resolveClassMethod:
//當對象接收到未知方法時,動態添加以下方法。作為默認執行,防止程序崩潰
void functionForMethod1(id self, SEL _cmd)
{
NSLog(@"當未實現某個方法時默認執行此函數");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
return [super resolveClassMethod:sel];
}
```
+ 備用接收者:如果沒有用動態方法解析處理消息,則Runtime會繼續調以下方法:
```objectivec
//如果對象實現了這個方法,并返回一個非nil的值,則這個對象會作為消息的接收者。且消息會被分發到這個對象
- (id)forwardingTargetForSelector:(SEL)aSelector {
id standByObj;
//....standByObj 備用接收對象的初始化等操作
return standByObj;
return [super forwardingTargetForSelector:aSelector];
}
- 完整轉發:如果在上一步還不能處理未知消息,則唯一能做的就是啟用完整的消息轉發機制了
/*!
* 消息轉發機制使用從這個方法中獲取的信息來創建NSInvocation對象。因此我們必須重寫這個方法,為給定的selector提供一個合適的方法簽名
*
* @param aSelector 需要轉發的方法
*
* @return 新的方法簽名
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) {
signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
/*!
* 將消息轉發給其它對象
*
* @param anInvocation 需要轉發的消息的selector,目標(target)和參數
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([SUTRuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:_helper];
}
}
- 方法交換(Method Swizzling)
- 給view controller的viewWillAppear:中添加跟蹤代碼,將viewWillAppear:與自定義的dg_viewWillAppear:交換,代碼如下:
#import "UIViewController+Tracking.h"
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
/*!
* 在load中實現方法交換,
*/
+ (void)load
{
//
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(dg_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
//本類是否已添加swizzledMethod,未添加則進行添加
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);
}
});
}
- (void)dg_viewWillAppear:(BOOL)animated
{
[self dg_viewWillAppear:animated];
NSLog(@"viewWilleAppear");
}
@end