runtime運行時一:類與對象

本系列出處:http://www.cocoachina.com/ios/20141031/10105.htmlhtml

Objective-C語言是一門動態語言,它將不少靜態語言在編譯和連接時期作的事放到了運行時來處理。這種動態語言的優點在於:咱們寫代碼時可以更具靈活性,如咱們能夠把消息轉發給咱們想要的對象,或者隨意交換一個方法的實現等。ios

這種特性意味着Objective-C不只須要一個編譯器,還須要一個運行時系統來執行編譯的代碼。對於Objective-C來講,這個運行時系統就像一個操做系統同樣:它讓全部的工做能夠正常的運行。這個運行時系統即Objc Runtime。Objc Runtime實際上是一個Runtime庫,它基本上是用C和彙編寫的,這個庫使得C語言有了面向對象的能力。算法

Runtime庫主要作下面幾件事:數組

封裝:在這個庫中,對象能夠用C語言中的結構體表示,而方法能夠用C函數來實現,另外再加上了一些額外的特性。這些結構體和函數被runtime函數封裝後,咱們就能夠在程序運行時建立,檢查,修改類、對象和它們的方法了。緩存

找出方法的最終執行代碼:當程序執行[object doSomething]時,會向消息接收者(object)發送一條消息(doSomething),runtime會根據消息接收者是否能響應該消息而作出不一樣的反應。這將在後面詳細介紹。session

Objective-C runtime目前有兩個版本:Modern runtime和Legacy runtime。Modern Runtime 覆蓋了64位的Mac OS X Apps,還有 iOS Apps,Legacy Runtime 是早期用來給32位 Mac OS X Apps 用的,也就是能夠不用管就是了。數據結構

在這一系列文章中,咱們將介紹runtime的基本工做原理,以及如何利用它讓咱們的程序變得更加靈活。在本文中,咱們先來介紹一下類與對象,這是面向對象的基礎,咱們看看在Runtime中,類是如何實現的。架構

類與對象基礎數據結構dom

Classtcp

Objective-C類是由Class類型來表示的,它其實是一個指向objc_class結構體的指針。它的定義以下:

1
typedef struct objc_class *Class;

查看objc/runtime.h中objc_class結構體的定義以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;

在這個定義中,下面幾個字段是咱們感興趣的:

isa:須要注意的是在Objective-C中,全部的類自身也是一個對象,這個對象的Class裏面也有一個isa指針,它指向metaClass(元類),咱們會在後面介紹它。

super_class:指向該類的父類,若是該類已是最頂層的根類(如NSObject或NSProxy),則super_class爲NULL。

cache:用於緩存最近使用的方法。一個接收者對象接收到一個消息時,它會根據isa指針去查找可以響應這個消息的對象。在實際使用中,這個對象只有一部分方法是經常使用的,不少方法其實不多用或者根本用不上。這種狀況下,若是每次消息來時,咱們都是methodLists中遍歷一遍,性能勢必不好。這時,cache就派上用場了。在咱們每次調用過一個方法後,這個方法就會被緩存到cache列表中,下次調用的時候runtime就會優先去cache中查找,若是cache沒有,纔去methodLists中查找方法。這樣,對於那些常常用到的方法的調用,但提升了調用的效率。

version:咱們可使用這個字段來提供類的版本信息。這對於對象的序列化很是有用,它但是讓咱們識別出不一樣類定義版本中實例變量佈局的改變。

針對cache,咱們用下面例子來講明其執行過程:

1
NSArray *array = [[NSArray alloc] init];

其流程是:

[NSArray alloc]先被執行。由於NSArray沒有+alloc方法,因而去父類NSObject去查找。

檢測NSObject是否響應+alloc方法,發現響應,因而檢測NSArray類,並根據其所需的內存空間大小開始分配內存空間,而後把isa指針指向NSArray類。同時,+alloc也被加進cache列表裏面。

接着,執行-init方法,若是NSArray響應該方法,則直接將其加入cache;若是不響應,則去父類查找。

在後期的操做中,若是再以[[NSArray alloc] init]這種方式來建立數組,則會直接從cache中取出相應的方法,直接調用。

objc_object與id

objc_object是表示一個類的實例的結構體,它的定義以下(objc/objc.h):

1
2
3
4
5
struct objc_object {
     Class isa  OBJC_ISA_AVAILABILITY;
};
 
typedef struct objc_object *id;

能夠看到,這個結構體只有一個字體,即指向其類的isa指針。這樣,當咱們向一個Objective-C對象發送消息時,運行時庫會根據實例對象的isa指針找到這個實例對象所屬的類。Runtime庫會在類的方法列表及父類的方法列表中去尋找與消息對應的selector指向的方法。找到後即運行這個方法。

當建立一個特定類的實例對象時,分配的內存包含一個objc_object數據結構,而後是類的實例變量的數據。NSObject類的alloc和allocWithZone:方法使用函數class_createInstance來建立objc_object數據結構。

另外還有咱們常見的id,它是一個objc_object結構類型的指針。它的存在可讓咱們實現相似於C++中泛型的一些操做。該類型的對象能夠轉換爲任何一種對象,有點相似於C語言中void *指針類型的做用。

objc_cache

上面提到了objc_class結構體中的cache字段,它用於緩存調用過的方法。這個字段是一個指向objc_cache結構體的指針,其定義以下:

1
2
3
4
5
struct objc_cache {
     unsigned int mask  /* total = mask + 1 */                  OBJC2_UNAVAILABLE;
     unsigned int occupied                                    OBJC2_UNAVAILABLE;
     Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

該結構體的字段描述以下:

mask:一個整數,指定分配的緩存bucket的總數。在方法查找過程當中,Objective-C runtime使用這個字段來肯定開始線性查找數組的索引位置。指向方法selector的指針與該字段作一個AND位操做(index = (mask & selector))。這能夠做爲一個簡單的hash散列算法。

occupied:一個整數,指定實際佔用的緩存bucket的總數。

buckets:指向Method數據結構指針的數組。這個數組可能包含不超過mask+1個元素。須要注意的是,指針多是NULL,表示這個緩存bucket沒有被佔用,另外被佔用的bucket多是不連續的。這個數組可能會隨着時間而增加。

元類(Meta Class)

在上面咱們提到,全部的類自身也是一個對象,咱們能夠向這個對象發送消息(即調用類方法)。如:

1
NSArray *array = [NSArray array];

這個例子中,+array消息發送給了NSArray類,而這個NSArray也是一個對象。既然是對象,那麼它也是一個objc_object指針,它包含一個指向其類的一個isa指針。那麼這些就有一個問題了,這個isa指針指向什麼呢?爲了調用+array方法,這個類的isa指針必須指向一個包含這些類方法的一個objc_class結構體。這就引出了meta-class的概念:

meta-class是一個類對象的類。

當咱們向一個對象發送消息時,runtime會在這個對象所屬的這個類的方法列表中查找方法;而向一個類發送消息時,會在這個類的meta-class的方法列表中查找。

meta-class之因此重要,是由於它存儲着一個類的全部類方法。每一個類都會有一個單獨的meta-class,由於每一個類的類方法基本不可能徹底相同。

再深刻一下,meta-class也是一個類,也能夠向它發送一個消息,那麼它的isa又是指向什麼呢?爲了避免讓這種結構無限延伸下去,Objective-C的設計者讓全部的meta-class的isa指向基類的meta-class,以此做爲它們的所屬類。即,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class做爲本身的所屬類,而基類的meta-class的isa指針是指向它本身。這樣就造成了一個完美的閉環。

經過上面的描述,再加上對objc_class結構體中super_class指針的分析,咱們就能夠描繪出類及相應meta-class類的一個繼承體系了,以下圖所示:

1413628797629491.png

對於NSObject繼承體系來講,其實例方法對體系中的全部實例、類和meta-class都是有效的;而類方法對於體系內的全部類和meta-class都是有效的。

講了這麼多,咱們仍是來寫個例子吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void TestMetaClass(id self, SEL _cmd) {
 
     NSLog(@ "This objcet is %p" , self);
     NSLog(@ "Class is %@, super class is %@" , [self class], [self superclass]);
 
     Class currentClass = [self class];
     for  (int i = 0; i < 4; i++) {
         NSLog(@ "Following the isa pointer %d times gives %p" , i, currentClass);
         currentClass = objc_getClass((__bridge void *)currentClass);
     }
 
     NSLog(@ "NSObject's class is %p" , [NSObject class]);
     NSLog(@ "NSObject's meta class is %p" , objc_getClass((__bridge void *)[NSObject class]));
}
 
#pragma mark -
 
@implementation Test
 
- (void)ex_registerClassPair {
 
     Class newClass = objc_allocateClassPair([NSError class],  "TestClass" , 0);
     class_addMethod(newClass, @selector(testMetaClass), (IMP)TestMetaClass,  "v@:" );
     objc_registerClassPair(newClass);
 
     id instance = [[newClass alloc] initWithDomain:@ "some domain"  code:0 userInfo:nil];
     [instance performSelector:@selector(testMetaClass)];
}
 
@end

這個例子是在運行時建立了一個NSError的子類TestClass,而後爲這個子類添加一個方法testMetaClass,這個方法的實現是TestMetaClass函數。

運行後,打印結果是

1
2
3
4
5
6
7
8
2014-10-20 22:57:07.352 mountain[1303:41490] This objcet is 0x7a6e22b0
2014-10-20 22:57:07.353 mountain[1303:41490] Class is TestStringClass,  super  class is NSError
2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 0 times gives 0x7a6e21b0
2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 1 times gives 0x0
2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 2 times gives 0x0
2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 3 times gives 0x0
2014-10-20 22:57:07.353 mountain[1303:41490] NSObject's class is 0xe10000
2014-10-20 22:57:07.354 mountain[1303:41490] NSObject's meta class is 0x0

咱們在for循環中,咱們經過objc_getClass來獲取對象的isa,並將其打印出來,依此一直回溯到NSObject的meta-class。分析打印結果,能夠看到最後指針指向的地址是0x0,即NSObject的meta-class的類地址。

這裏須要注意的是:咱們在一個類對象調用class方法是沒法獲取meta-class,它只是返回類而已。

類與對象操做函數

runtime提供了大量的函數來操做類與對象。類的操做方法大部分是以class爲前綴的,而對象的操做方法大部分是以objc或object_爲前綴。下面咱們將根據這些方法的用途來分類討論這些方法的使用。

類相關操做函數

咱們能夠回過頭去看看objc_class的定義,runtime提供的操做類的方法主要就是針對這個結構體中的各個字段的。下面咱們分別介紹這一些的函數。並在最後以實例來演示這些函數的具體用法。

類名(name)

類名操做的函數主要有:

1
2
// 獲取類的類名
const char * class_getName ( Class cls );

對於class_getName函數,若是傳入的cls爲Nil,則返回一個字字符串。

父類(super_class)和元類(meta-class)

父類和元類操做的函數主要有:

1
2
3
4
5
// 獲取類的父類
Class class_getSuperclass ( Class cls );
 
// 判斷給定的Class是不是一個元類
BOOL class_isMetaClass ( Class cls );

class_getSuperclass函數,當cls爲Nil或者cls爲根類時,返回Nil。不過一般咱們可使用NSObject類的superclass方法來達到一樣的目的。

class_isMetaClass函數,若是是cls是元類,則返回YES;若是否或者傳入的cls爲Nil,則返回NO。

實例變量大小(instance_size)

實例變量大小操做的函數有:

1
2
// 獲取實例大小
size_t class_getInstanceSize ( Class cls );

成員變量(ivars)及屬性

在objc_class中,全部的成員變量、屬性的信息是放在鏈表ivars中的。ivars是一個數組,數組中每一個元素是指向Ivar(變量信息)的指針。runtime提供了豐富的函數來操做這一字段。大致上能夠分爲如下幾類:

1.成員變量操做函數,主要包含如下函數:

1
2
3
4
5
6
7
8
9
10
11
// 獲取類中指定名稱實例成員變量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );
 
// 獲取類成員變量的信息
Ivar class_getClassVariable ( Class cls, const char *name );
 
// 添加成員變量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
 
// 獲取整個成員變量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );

class_getInstanceVariable函數,它返回一個指向包含name指定的成員變量信息的objc_ivar結構體的指針(Ivar)。

class_getClassVariable函數,目前沒有找到關於Objective-C中類變量的信息,通常認爲Objective-C不支持類變量。注意,返回的列表不包含父類的成員變量和屬性。

Objective-C不支持往已存在的類中添加實例變量,所以不論是系統庫提供的提供的類,仍是咱們自定義的類,都沒法動態添加成員變量。但若是咱們經過運行時來建立一個類的話,又應該如何給它添加成員變量呢?這時咱們就可使用class_addIvar函數了。不過須要注意的是,這個方法只能在objc_allocateClassPair函數與objc_registerClassPair之間調用。另外,這個類也不能是元類。成員變量的按字節最小對齊量是1<<alignment。這取決於ivar的類型和機器的架構。若是變量的類型是指針類型,則傳遞log2(sizeof(pointer_type))。< p="">

class_copyIvarList函數,它返回一個指向成員變量信息的數組,數組中每一個元素是指向該成員變量信息的objc_ivar結構體的指針。這個數組不包含在父類中聲明的變量。outCount指針返回數組的大小。須要注意的是,咱們必須使用free()來釋放這個數組。

2.屬性操做函數,主要包含如下函數:

1
2
3
4
5
6
7
8
9
10
11
// 獲取指定的屬性
objc_property_t class_getProperty ( Class cls, const char *name );
 
// 獲取屬性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
 
// 爲類添加屬性
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
 
// 替換類的屬性
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );

這一種方法也是針對ivars來操做,不過只操做那些是屬性的值。咱們在後面介紹屬性時會再遇到這些函數。

3.在MAC OS X系統中,咱們可使用垃圾回收器。runtime提供了幾個函數來肯定一個對象的內存區域是否能夠被垃圾回收器掃描,以處理strong/weak引用。這幾個函數定義以下:

1
2
3
4
const uint8_t * class_getIvarLayout ( Class cls );
void class_setIvarLayout ( Class cls, const uint8_t *layout );
const uint8_t * class_getWeakIvarLayout ( Class cls );
void class_setWeakIvarLayout ( Class cls, const uint8_t *layout );

但一般狀況下,咱們不須要去主動調用這些方法;在調用objc_registerClassPair時,會生成合理的佈局。在此不詳細介紹這些函數。

方法(methodLists)

方法操做主要有如下函數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
 
// 獲取實例方法
Method class_getInstanceMethod ( Class cls, SEL name );
 
// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );
 
// 獲取全部方法的數組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
 
// 替代方法的實現
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
 
// 返回方法的具體實現
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
 
// 類實例是否響應指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );

class_addMethod的實現會覆蓋父類的方法實現,但不會取代本類中已存在的實現,若是本類中包含一個同名的實現,則函數會返回NO。若是要修改已存在實現,可使用method_setImplementation。一個Objective-C方法是一個簡單的C函數,它至少包含兩個參數—self和_cmd。因此,咱們的實現函數(IMP參數指向的函數)至少須要兩個參數,以下所示:

1
2
3
4
void myMethodIMP(id self, SEL _cmd)
{
     // implementation ....
}

與成員變量不一樣的是,咱們能夠爲類動態添加方法,無論這個類是否已存在。

另外,參數types是一個描述傳遞給方法的參數類型的字符數組,這就涉及到類型編碼,咱們將在後面介紹。

class_getInstanceMethod、class_getClassMethod函數,與class_copyMethodList不一樣的是,這兩個函數都會去搜索父類的實現。

class_copyMethodList函數,返回包含全部實例方法的數組,若是須要獲取類方法,則可使用class_copyMethodList(object_getClass(cls), &count)(一個類的實例方法是定義在元類裏面)。該列表不包含父類實現的方法。outCount參數返回方法的個數。在獲取到列表後,咱們須要使用free()方法來釋放它。

class_replaceMethod函數,該函數的行爲能夠分爲兩種:若是類中不存在name指定的方法,則相似於class_addMethod函數同樣會添加方法;若是類中已存在name指定的方法,則相似於method_setImplementation同樣替代原方法的實現。

class_getMethodImplementation函數,該函數在向類實例發送消息時會被調用,並返回一個指向方法實現函數的指針。這個函數會比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函數指針多是一個指向runtime內部的函數,而不必定是方法的實際實現。例如,若是類實例沒法響應selector,則返回的函數指針將是運行時消息轉發機制的一部分。

class_respondsToSelector函數,咱們一般使用NSObject類的respondsToSelector:或instancesRespondToSelector:方法來達到相同目的。

協議(objc_protocol_list)

協議相關的操做包含如下函數:

1
2
3
4
5
6
7
8
// 添加協議
BOOL class_addProtocol ( Class cls, Protocol *protocol );
 
// 返回類是否實現指定的協議
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
 
// 返回類實現的協議列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );

class_conformsToProtocol函數可使用NSObject類的conformsToProtocol:方法來替代。

class_copyProtocolList函數返回的是一個數組,在使用後咱們須要使用free()手動釋放。

版本(version)

版本相關的操做包含如下函數:

1
2
3
4
5
// 獲取版本號
int class_getVersion ( Class cls );
 
// 設置版本號
void class_setVersion ( Class cls, int version );

其它

runtime還提供了兩個函數來供CoreFoundation的tool-free bridging使用,即:

1
2
Class objc_getFutureClass ( const char *name );
void objc_setFutureClass ( Class cls, const char *name );

一般咱們不直接使用這兩個函數。

實例(Example)

上面列舉了大量類操做的函數,下面咱們寫個實例,來看看這些函數的實例效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
//-----------------------------------------------------------
// MyClass.h
 
@interface MyClass : NSObject @property (nonatomic, strong) NSArray *array;
 
@property (nonatomic, copy) NSString *string;
 
- (void)method1;
 
- (void)method2;
 
+ (void)classMethod1;
 
@end
 
//-----------------------------------------------------------
// MyClass.m
 
#import "MyClass.h"
 
@interface MyClass () {
     NSInteger       _instance1;
 
     NSString    *   _instance2;
}
 
@property (nonatomic, assign) NSUInteger integer;
 
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2;
 
@end
 
@implementation MyClass
 
+ (void)classMethod1 {
 
}
 
- (void)method1 {
     NSLog(@ "call method method1" );
}
 
- (void)method2 {
 
}
 
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2 {
 
     NSLog(@ "arg1 : %ld, arg2 : %@" , arg1, arg2);
}
 
@end
 
//-----------------------------------------------------------
// main.h
 
#import "MyClass.h"
#import "MySubClass.h"
#import int main(int argc, const char * argv[]) {
     @autoreleasepool {
 
         MyClass *myClass = [[MyClass alloc] init];
         unsigned int outCount = 0;
 
         Class cls = myClass.class;
 
         // 類名
         NSLog(@ "class name: %s" , class_getName(cls));
 
         NSLog(@ "==========================================================" );
 
         // 父類
         NSLog(@ "super class name: %s" , class_getName(class_getSuperclass(cls)));
         NSLog(@ "==========================================================" );
 
         // 是不是元類
         NSLog(@ "MyClass is %@ a meta-class" , (class_isMetaClass(cls) ? @ ""  : @ "not" ));
         NSLog(@ "==========================================================" );
 
         Class meta_class = objc_getMetaClass(class_getName(cls));
         NSLog(@ "%s's meta-class is %s" , class_getName(cls), class_getName(meta_class));
         NSLog(@ "==========================================================" );
 
         // 變量實例大小
         NSLog(@ "instance size: %zu" , class_getInstanceSize(cls));
         NSLog(@ "==========================================================" );
 
         // 成員變量
         Ivar *ivars = class_copyIvarList(cls, &outCount);
         for  (int i = 0; i < outCount; i++) {
             Ivar ivar = ivars[i];
             NSLog(@ "instance variable's name: %s at index: %d" , ivar_getName(ivar), i);
         }
 
         free(ivars);
 
         Ivar string = class_getInstanceVariable(cls,  "_string" );
         if  (string != NULL) {
             NSLog(@ "instace variable %s" , ivar_getName(string));
         }
 
         NSLog(@ "==========================================================" );
 
         // 屬性操做
         objc_property_t * properties = class_copyPropertyList(cls, &outCount);
         for  (int i = 0; i < outCount; i++) {
             objc_property_t property = properties[i];
             NSLog(@ "property's name: %s" , property_getName(property));
         }
 
         free(properties);
 
         objc_property_t array = class_getProperty(cls,  "array" );
         if  (array != NULL) {
             NSLog(@ "property %s" , property_getName(array));
         }
 
         NSLog(@ "==========================================================" );
 
         // 方法操做
         Method *methods = class_copyMethodList(cls, &outCount);
         for  (int i = 0; i < outCount; i++) {
             Method method = methods[i];
             NSLog(@ "method's signature: %s" , method_getName(method));
         }
 
         free(methods);
 
         Method method1 = class_getInstanceMethod(cls, @selector(method1));
         if  (method1 != NULL) {
             NSLog(@ "method %s" , method_getName(method1));
         }
 
         Method classMethod = class_getClassMethod(cls, @selector(classMethod1));
         if  (classMethod != NULL) {
             NSLog(@ "class method : %s" , method_getName(classMethod));
         }
 
         NSLog(@ "MyClass is%@ responsd to selector: method3WithArg1:arg2:" , class_respondsToSelector(cls, @selector(method3WithArg1:arg2:)) ? @ ""  : @ " not" );
 
         IMP imp = class_getMethodImplementation(cls, @selector(method1));
         imp();
 
         NSLog(@ "==========================================================" );
 
         // 協議
         Protocol * __unsafe_unretained * protocols = class_copyProtocolList(cls, &outCount);
         Protocol * protocol;
         for  (int i = 0; i < outCount; i++) {
             protocol = protocols[i];
             NSLog(@ "protocol name: %s" , protocol_getName(protocol));
         }
 
         NSLog(@ "MyClass is%@ responsed to protocol %s" , class_conformsToProtocol(cls, protocol) ? @ ""  : @ " not" , protocol_getName(protocol));
 
         NSLog(@ "==========================================================" );
     }
     return  0;
}

這段程序的輸出以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2014-10-22 19:41:37.452 RuntimeTest[3189:156810] class name: MyClass
2014-10-22 19:41:37.453 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.454 RuntimeTest[3189:156810]  super  class name: NSObject
2014-10-22 19:41:37.454 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.454 RuntimeTest[3189:156810] MyClass is not a meta-class
2014-10-22 19:41:37.454 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.454 RuntimeTest[3189:156810] MyClass's meta-class is MyClass
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance size: 48
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance variable's name: _instance1 at index: 0
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance variable's name: _instance2 at index: 1
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance variable's name: _array at index: 2
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance variable's name: _string at index: 3
2014-10-22 19:41:37.463 RuntimeTest[3189:156810] instance variable's name: _integer at index: 4
2014-10-22 19:41:37.463 RuntimeTest[3189:156810] instace variable _string
2014-10-22 19:41:37.463 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.463 RuntimeTest[3189:156810] property's name: array
2014-10-22 19:41:37.463 RuntimeTest[3189:156810] property's name: string
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] property's name: integer
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] property array
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] method's signature: method1
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] method's signature: method2
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] method's signature: method3WithArg1:arg2:
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: integer
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: setInteger:
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: array
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: string
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: setString:
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: setArray:
2014-10-22 19:41:37.466 RuntimeTest[3189:156810] method's signature: .cxx_destruct
2014-10-22 19:41:37.466 RuntimeTest[3189:156810] method method1
2014-10-22 19:41:37.466 RuntimeTest[3189:156810] class method : classMethod1
2014-10-22 19:41:37.466 RuntimeTest[3189:156810] MyClass is responsd to selector: method3WithArg1:arg2:
2014-10-22 19:41:37.467 RuntimeTest[3189:156810] call method method1
2014-10-22 19:41:37.467 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.467 RuntimeTest[3189:156810] protocol name: NSCopying
2014-10-22 19:41:37.467 RuntimeTest[3189:156810] protocol name: NSCoding
2014-10-22 19:41:37.467 RuntimeTest[3189:156810] MyClass is responsed to protocol NSCoding
2014-10-22 19:41:37.468 RuntimeTest[3189:156810] ==========================================================

動態建立類和對象

runtime的強大之處在於它能在運行時建立類和對象。

動態建立類

動態建立類涉及到如下幾個函數:

1
2
3
4
5
6
7
8
// 建立一個新類和元類
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );
 
// 銷燬一個類及其相關聯的類
void objc_disposeClassPair ( Class cls );
 
// 在應用中註冊由objc_allocateClassPair建立的類
void objc_registerClassPair ( Class cls );

objc_allocateClassPair函數:若是咱們要建立一個根類,則superclass指定爲Nil。extraBytes一般指定爲0,該參數是分配給類和元類對象尾部的索引ivars的字節數。

爲了建立一個新類,咱們須要調用objc_allocateClassPair。而後使用諸如class_addMethod,class_addIvar等函數來爲新建立的類添加方法、實例變量和屬性等。完成這些後,咱們須要調用objc_registerClassPair函數來註冊類,以後這個新類就能夠在程序中使用了。

實例方法和實例變量應該添加到類自身上,而類方法應該添加到類的元類上。

objc_disposeClassPair函數用於銷燬一個類,不過須要注意的是,若是程序運行中還存在類或其子類的實例,則不能調用針對類調用該方法。

在前面介紹元類時,咱們已經有接觸到這幾個函數了,在此咱們再舉個實例來看看這幾個函數的使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Class cls = objc_allocateClassPair(MyClass.class,  "MySubClass" , 0);
class_addMethod(cls, @selector(submethod1), (IMP)imp_submethod1,  "v@:" );
class_replaceMethod(cls, @selector(method1), (IMP)imp_submethod1,  "v@:" );
class_addIvar(cls,  "_ivar1" , sizeof(NSString *), log(sizeof(NSString *)),  "i" );
 
objc_property_attribute_t type = { "T" "@\"NSString\"" };
objc_property_attribute_t ownership = {  "C" ""  };
objc_property_attribute_t backingivar = {  "V" "_ivar1" };
objc_property_attribute_t attrs[] = {type, ownership, backingivar};
 
class_addProperty(cls,  "property2" , attrs, 3);
objc_registerClassPair(cls);
 
id instance = [[cls alloc] init];
[instance performSelector:@selector(submethod1)];
[instance performSelector:@selector(method1)];

程序的輸出以下:

1
2
2014-10-23 11:35:31.006 RuntimeTest[3800:66152] run sub method 1
2014-10-23 11:35:31.006 RuntimeTest[3800:66152] run sub method 1

動態建立對象

動態建立對象的函數以下:

1
2
3
4
5
6
7
8
// 建立類實例
id class_createInstance ( Class cls, size_t extraBytes );
 
// 在指定位置建立類實例
id objc_constructInstance ( Class cls, void *bytes );
 
// 銷燬類實例
void * objc_destructInstance ( id obj );

class_createInstance函數:建立實例時,會在默認的內存區域爲類分配內存。extraBytes參數表示分配的額外字節數。這些額外的字節可用於存儲在類定義中所定義的實例變量以外的實例變量。該函數在ARC環境下沒法使用。

調用class_createInstance的效果與+alloc方法相似。不過在使用class_createInstance時,咱們須要確切的知道咱們要用它來作什麼。在下面的例子中,咱們用NSString來測試一下該函數的實際效果:

1
2
3
4
5
6
7
id theObject = class_createInstance(NSString.class, sizeof(unsigned));
id str1 = [theObject init];
 
NSLog(@ "%@" , [str1 class]);
 
id str2 = [[NSString alloc] initWithString:@ "test" ];
NSLog(@ "%@" , [str2 class]);

輸出結果是:

1
2
2014-10-23 12:46:50.781 RuntimeTest[4039:89088] NSString
2014-10-23 12:46:50.781 RuntimeTest[4039:89088] __NSCFConstantString

能夠看到,使用class_createInstance函數獲取的是NSString實例,而不是類簇中的默認佔位符類__NSCFConstantString。

objc_constructInstance函數:在指定的位置(bytes)建立類實例。

objc_destructInstance函數:銷燬一個類的實例,但不會釋放並移除任何與其相關的引用。

實例操做函數

實例操做函數主要是針對咱們建立的實例對象的一系列操做函數,咱們可使用這組函數來從實例對象中獲取咱們想要的一些信息,如實例對象中變量的值。這組函數能夠分爲三小類:

針對整個對象進行操做的函數,這類函數包含

1
2
3
4
5
// 返回指定對象的一份拷貝
id object_copy ( id obj, size_t size );
 
// 釋放指定對象佔用的內存
id object_dispose ( id obj );

有這樣一種場景,假設咱們有類A和類B,且類B是類A的子類。類B經過添加一些額外的屬性來擴展類A。如今咱們建立了一個A類的實例對象,並但願在運行時將這個對象轉換爲B類的實例對象,這樣能夠添加數據到B類的屬性中。這種狀況下,咱們沒有辦法直接轉換,由於B類的實例會比A類的實例更大,沒有足夠的空間來放置對象。此時,咱們就要以使用以上幾個函數來處理這種狀況,以下代碼所示:

1
2
3
4
NSObject *a = [[NSObject alloc] init];
id newB = object_copy(a, class_getInstanceSize(MyClass.class));
object_setClass(newB, MyClass.class);
object_dispose(a);

2.針對對象實例變量進行操做的函數,這類函數包含:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 修改類實例的實例變量的值
Ivar object_setInstanceVariable ( id obj, const char *name, void *value );
 
// 獲取對象實例變量的值
Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
 
// 返回指向給定對象分配的任何額外字節的指針
void * object_getIndexedIvars ( id obj );
 
// 返回對象中實例變量的值
id object_getIvar ( id obj, Ivar ivar );
 
// 設置對象中實例變量的值
void object_setIvar ( id obj, Ivar ivar, id value );

若是實例變量的Ivar已經知道,那麼調用object_getIvar會比object_getInstanceVariable函數快,相同狀況下,object_setIvar也比object_setInstanceVariable快。

3.針對對象的類進行操做的函數,這類函數包含:

1
2
3
4
5
6
7
8
// 返回給定對象的類名
const char * object_getClassName ( id obj );
 
// 返回對象的類
Class object_getClass ( id obj );
 
// 設置對象的類
Class object_setClass ( id obj, Class cls );

獲取類定義

Objective-C動態運行庫會自動註冊咱們代碼中定義的全部的類。咱們也能夠在運行時建立類定義並使用objc_addClass函數來註冊它們。runtime提供了一系列函數來獲取類定義相關的信息,這些函數主要包括:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 獲取已註冊的類定義的列表
int objc_getClassList ( Class *buffer, int bufferCount );
 
// 建立並返回一個指向全部已註冊類的指針列表
Class * objc_copyClassList ( unsigned int *outCount );
 
// 返回指定類的類定義
Class objc_lookUpClass ( const char *name );
Class objc_getClass ( const char *name );
Class objc_getRequiredClass ( const char *name );
 
// 返回指定類的元類
Class objc_getMetaClass ( const char *name );

objc_getClassList函數:獲取已註冊的類定義的列表。咱們不能假設從該函數中獲取的類對象是繼承自NSObject體系的,因此在這些類上調用方法是,都應該先檢測一下這個方法是否在這個類中實現。

下面代碼演示了該函數的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int numClasses;
Class * classes = NULL;
 
numClasses = objc_getClassList(NULL, 0);
if  (numClasses > 0) {
     classes = malloc(sizeof(Class) * numClasses);
     numClasses = objc_getClassList(classes, numClasses);
 
     NSLog(@ "number of classes: %d" , numClasses);
 
     for  (int i = 0; i < numClasses; i++) {
 
         Class cls = classes[i];
         NSLog(@ "class name: %s" , class_getName(cls));
     }
 
     free(classes);
}

輸出結果以下:

1
2
3
4
5
6
7
8
9
10
11
12
2014-10-23 16:20:52.589 RuntimeTest[8437:188589] number of classes: 1282
2014-10-23 16:20:52.589 RuntimeTest[8437:188589] class name: DDTokenRegexp
2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: _NSMostCommonKoreanCharsKeySet
2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: OS_xpc_dictionary
2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: NSFileCoordinator
2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: NSAssertionHandler
2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: PFUbiquityTransactionLogMigrator
2014-10-23 16:20:52.591 RuntimeTest[8437:188589] class name: NSNotification
2014-10-23 16:20:52.591 RuntimeTest[8437:188589] class name: NSKeyValueNilSetEnumerator
2014-10-23 16:20:52.591 RuntimeTest[8437:188589] class name: OS_tcp_connection_tls_session
2014-10-23 16:20:52.591 RuntimeTest[8437:188589] class name: _PFRoutines
......還有大量輸出

獲取類定義的方法有三個:objc_lookUpClass, objc_getClass和objc_getRequiredClass。若是類在運行時未註冊,則objc_lookUpClass會返回nil,而objc_getClass會調用類處理回調,並再次確認類是否註冊,若是確認未註冊,再返回nil。而objc_getRequiredClass函數的操做與objc_getClass相同,只不過若是沒有找到類,則會殺死進程。

objc_getMetaClass函數:若是指定的類沒有註冊,則該函數會調用類處理回調,並再次確認類是否註冊,若是確認未註冊,再返回nil。不過,每一個類定義都必須有一個有效的元類定義,因此這個函數老是會返回一個元類定義,無論它是否有效。

小結

在這一章中咱們介紹了Runtime運行時中與類和對象相關的數據結構,經過這些數據函數,咱們能夠管窺Objective-C底層面向對象實現的一些信息。另外,經過豐富的操做函數,能夠靈活地對這些數據進行操做。

相關文章
相關標籤/搜索