Runtime學習--轉載自http://www.saitjr.com/ios/objc-runtime.html


在iOS開發的過程中經常會提及一個東西叫做RunTime,並且在面試中RunTime也是經常被考到的問題。那麼本文就來探討下RunTime到底是什麼,他如何來使用。

 

環境信息

Mac OS X 10.10.2
xcode 6
iOS 8.1

一、RunTime是什麼

首先Objective-C是C語言的擴展,並加入了面向對象特性和Smalltalk式的消息傳遞機制。而這個擴展的核心就是一個用C和彙編語言寫的RunTime庫,這個庫所做的事情就是加載類信息,進行方法的分發和轉發,正是這個庫賦予了Objective-C的動態特性。

運行時很小卻很強大,並且Objc Runtime是開源的,蘋果也允許你使用RunTime裏面的東西(不會導致上不了架)。

雖然這些特性在開發中使用的較少,但是理解Objective-C的Runtime機制可以幫我們更好的瞭解這個語言,合理的運用還能在系統層面上解決一些技術問題。

 

二、消息機制

比如我們初始化一個NSObject對象:

NSObject *object = [[NSObject alloc] init];

事實上,調用方法其實也是在給這個對象發送一個消息,在編譯時這句話會翻譯成一個C的函數調用,即:

objc_msgSend(objc_msgSend([NSObject class],@selector(alloc)),@selector(init));

那麼,這不是把OC代碼轉換成C代碼了麼,他的動態特性體現在哪裏?對於C語言,函數的調用在編譯的時候就會去決定調用哪個函數。而OC是一種動態語言,它會儘可能的把代碼執行的決策從編譯和鏈接的時候,推遲到運行時。

給一個對象發送的一個消息並不會立即執行,而是在運行的時候再去尋找他對應的實現。那麼你就可以把消息轉發給你想要的對象,或者隨意交換一個方法的實現之類的。

那麼這個消息機制到底是怎麼運作的呢,我們就需要來了解下Objective-C的對象模型。

 

三、Objective-C對象模型

我們打開<objc/objc.h>文件可以看到如下對NSObject的定義:

@interface NSObject <NSObject> {
 Class isa  OBJC_ISA_AVAILABILITY;}

Objective-C是一門面向對象的語言,每一個對象都是一個類的實例,在Objective-C的內部,每個對象都有一個isa的指針,指向該對象的類。每個類描述了一系列它的實例的特點,包括成員變量的列表,成員函數的列表等,每個對象都可以接收消息,而對象可以接收的消息列表保存在它對應的類中。

 

我們再來看看class的定義

typedef struct objc_class *Class;
struct objc_class { 
 Class isa  OBJC_ISA_AVAILABILITY#if !__OBJC2__
 Class super_class     OBJC2_UNAVAILABLE; // 父類
 const char *name     OBJC2_UNAVAILABLE; // 類名
 long version          OBJC2_UNAVAILABLE; // 類的版本信息,默認爲0
 long info             OBJC2_UNAVAILABLE; // 類信息,供運行時期使用的一些位標識
 long instance_size                 OBJC2_UNAVAILABLE; // 該類的實例變量大小
 struct objc_ivar_list *ivars       OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
 struct objc_method_list **methodLists     OBJC2_UNAVAILABLE; // 方法定義鏈表
 struct objc_cache *cache      OBJC2_UNAVAILABLE; // 方法緩存
 struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE; // 協議鏈表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

發現他裏面也有一個isa指針,因爲在Objective-C語言中,每個類實際上也是一個對象,每個類也可以接收消息,例如[NSObject alloc]。那麼既然一個類也是對象,所以它也必須是另一個類的實例,這個類就是元類(metaclass)。元類保存了類方法的列表,當一個類被調用時,元類會首先查找本身是否有該類方法的實現,如果沒有,則該元類會向它的父類查找該方法。

元類也是一個對象,爲了設計上的完整,所以元類的isa指針都會指向一個根元類(root metaclass)。根元類本身的isa指針指向自己,這樣就形成了一個閉環。

 

所以使用objc_msgSend函數,會執行以下步驟

  1. 通過對象(類)的isa指針去找到他的class
  2. 在class的method list 找到該消息的實現
  3. 如果class中沒有改消息的實現,就繼續到它的super_class中去找
  4. 一旦找到這個這個消息的實現,那麼就去執行他的IMP

這樣的話每發送一個消息就要方法列表objc_method_list進行一次遍歷,爲了提高效率,使用了objc_cache對經常調用的函數進行緩存,再次調用時就先到objc_cache中去查找函數實現。

 

四、RunTime的具體應用

1.動態創建一個類

#import <objc/runtime.h>
// 自定義一個方法
void reportFunction (id self, SEL _cmd) {
 NSLog(@"This object is %p", self);
}
int main(int argc, const char * argv[]) {
 @autoreleasepool {        
 // 1.動態創建對象 創建一個Person 繼承自 NSObject
 Class newClass = objc_allocateClassPair([NSObject class], “Person”, 0); 
 // 爲該類增加名爲Report的方法
 class_addMethod(newClass, @selector(report), (IMP)reportFunction, @"v@:");        
 // 註冊該類
 objc_registerClassPair(newClass);
 // 創建一個 Student 類的實例
 instantOfNewClass = [[newClass alloc] init];
 // 調用方法
 [instantOfNewClass report]; 
 }
 return 0;
}

這裏用到的Selector事實上是一個C的結構體,表示一個消息,類似於C的方法調用:

typedef struct objc_selector  *SEL;

IMP (Method Implementations),就是一個函數指針,當你發起一個ObjC消息之後,最終它會執行的那個代碼,就是由這個函數指針指定的。

typedef id (*IMP)(id self,SEL _cmd,...); 

 

2.關聯對象

對象在內存中的排布可以看成是一個結構體,該結構體的大小並不能動態的變化,所以無法在運行時動態的給對象增加成員變量,但是我們可以通過關聯對象的方法變相的給對象增加一個成員變量。

比如,我們想給NSObject新增一個關聯對象:
創建一個NSObject的類目AssociatedObject,在.h文件裏面聲明一個屬性

@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject; 
@end

在NSObject+AssociatedObject.m文件裏面進行關聯

#import "NSObject+AssociatedObject.h"
#import <objc/runtime.h>
@implementation NSObject (AssociatedObject)
@dynamic associatedObject;
- (void)setAssociatedObject:(id)object {
 // 設置關聯對象
 objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
 // 得到關聯對象
 return objc_getAssociatedObject(self, @selector(associatedObject));
}
@end

這樣就給NSObject新增了一個屬性,可是,這有什麼用呢?通常,我們會利用關聯對象給UIAlertView新增一個block回調,方便使用。

 

3.利用RunTime進行模型歸檔

對於一個有很多屬性的Person類,遵守了NSCoding協議之後,我們可以利用RunTime遍歷模型對象的所有屬性進行歸檔,關鍵代碼如下:

// 利用runtime機制進行屬性的歸檔接檔
- (void)encodeWithCoder:(NSCoder *)aCoder {
 unsigned int count = 0;
 Ivar *ivars = class_copyIvarList([Person class], &count);
 for (int i = 0; i<count; i++) {
 // 取出i位置對應的成員變量
 Ivar ivar = ivars[i];
 // 查看成員變量
 const char *name = ivar_getName(ivar);
 // 歸檔
 NSString *key = [NSString stringWithUTF8String:name]; id value = [self valueForKey:key];
 [aCoder encodeObject:value forKey:key];
 }
 free(ivars);
}

 

- (id)initWithCoder:(NSCoder *)aDecoder {
 self = [super init];
 if (self) {
 unsigned int count = 0;
 Ivar *ivars = class_copyIvarList([Person class], &count);
 for (int i = 0; i<count; i++) {
 // 取出i位置對應的成員變量
 Ivar ivar = ivars[i];
 // 查看成員變量
 const char *name = ivar_getName(ivar);
 // 歸檔
 NSString *key = [NSString stringWithUTF8String:name];
 id value = [aDecoder decodeObjectForKey:key];
 // 設置到成員變量身上
 [self setValue:value forKey:key];
 }
 free(ivars);
 }
 return self;
}

 

五、小結

利用RunTime我們還可以完成字典和對象模型之間的轉換,例如MJExtension,還可以自己實現KVO,封裝框架(修改系統實現),實現客戶端根據後臺動態更改邏輯等等,光說無用,還是需要自己多練才行。

 

六、參考資料

http://tech.glowing.com/cn/objective-c-runtime/

http://www.justinyan.me/post/1624

http://limboy.me/ios/2013/08/03/dynamic-tips-and-tricks-with-objective-c.html

http://nshipster.cn/associated-objects/

http://blog.devtang.com/blog/2013/10/15/objective-c-object-model/

http://honglu.me/2014/12/29/%E6%B5%85%E8%B0%88OC%E8%BF%90%E8%A1%8C%E6%97%B6-RunTime/


本文轉載自:http://www.saitjr.com/ios/objc-runtime.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章