你要知道的runtime都在這里
轉載請注明出處 http://www.lxweimin.com/p/17e158a666b1
本文主要講解runtime
相關知識,從原理到實踐,由于包含內容過多分為以下五篇文章詳細講解,可自行選擇需要了解的方向:
- 從runtime開始: 理解面向對象的類到面向過程的結構體
- 從runtime開始: 深入理解OC消息轉發機制
- 從runtime開始: 理解OC的屬性property
- 從runtime開始: 實踐Category添加屬性與黑魔法method swizzling
- 從runtime開始: 深入weak實現機理
本文是系列文章的第一篇文章從runtime開始: 理解面向對象的類到面向過程的結構體,主要從runtime
出發講解面向對象的類是如何轉變為面向過程的結構體,來探究OC對類的處理本質。
什么是runtime
runtime
就是運行時,在實際開發中使用runtime
的場景并不多,但是了解runtime
有助于我們更好的理解OC的原理,從而提高開發水平。
runtime
很強大,是OC最重要的一部分也是OC最大的特色,可以不夸張的說runtime
成就了OC,盡管runtime
是OC的一個模塊而已。
我們都知道高級編程語言想要成為可執行文件需要先編譯為匯編語言再匯編為機器語言,機器語言也是計算機能夠識別的唯一語言,但是OC并不能直接編譯為匯編語言,而是要先轉寫為純C語言再進行編譯和匯編的操作,從OC到C語言的過渡就是由runtime
來實現的。然而我們使用OC進行面向對象開發,而C語言更多的是面向過程開發,這就需要將面向對象的類轉變為面向過程的結構體,本文正是通過runtime
源碼分析來講解runtime
是如何將面向對象的類轉變為面向過程的結構體。
深入代碼理解instance、class object、metaclass
面向對象編程中,最重要的概念就是類,下面我們就從代碼入手,看看OC是如何實現類的。
前文一直在說runtime
將面向對象的類轉變為面向過程的結構體,那這個結構體到底是什么樣子的?打開#import<objc/objc.h>
文件,可以發現以下幾行代碼
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
通過注釋和代碼不難發現,我們創建的一個對象或實例其實就是一個struct objc_object
結構體,而我們常用的id
也就是這個結構體的指針。有如下代碼:
//以下兩種寫法都成立
id str = [[NSString alloc] init];
NSString *str = [[NSString alloc] init];
通過上述代碼可以看出,我們創建的NSString類
的實例str
其實就是一個struct objc_object
結構體指針,所以不管是Foundation
框架中的類或是自定義的類,我們創建的類的實例最終獲取的都是一個結構體指針,這個結構體只有一個成員變量就是Class
類型的isa
指針,Class
是結構體指針,指向struct objc_class
,那這個結構體又是什么呢?這里先透露一句話str is a NSString
,再加上Class
這個指針的名字,我們不難猜測,Class
就是代表NSString
這個類。
接下來會詳細講解這個結構體,現在再看另一個例子,有時我們也會通過下述方法來創建一個實例:
NSString *str = [[NSString alloc] initWithString: @"Hello World"];
Class c = [str class];
NSString *str2 = [[c alloc] initWithString: @"Hello World"];
可能你已經發現了,通過實例對象調用的class
方法,我們能夠獲取到一個Class
類型的變量,我們可以通過這個Class
來創建相應的實例對象。
實際上,OC中的類也是一個對象,稱為類對象
,上述方法中通過[str class]
方法獲取到的就是NSString類
的類對象
,接著我們就可以通過這個類對象
來創建實例對象,那這個類對象
又是什么東西呢?打開#import<objc/runtime.h>
文件,我們可以找到結構體struct objc_class
的定義,該結構體定義如下:
文件objc/runtime.h中有如下定義:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
Class super_class
const char *name
long version
long info
long instance_size
struct objc_ivar_list *ivars
struct objc_method_list **methodLists
struct objc_cache *cache
struct objc_protocol_list *protocols
}
/* Use `Class` instead of `struct objc_class *` */
文件objc/objc.h文件中有如下定義
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
struct objc_class
結構體定義了很多變量,通過命名不難發現,結構體里保存了指向父類的指針、類的名字、版本、實例大小、實例變量列表、方法列表、緩存、遵守的協議列表等,一個類包含的信息也不就正是這些嗎?沒錯,類對象
就是一個結構體struct objc_class
,這個結構體存放的數據稱為元數據(metadata)
,該結構體的第一個成員變量也是isa
指針,這就說明了Class
本身其實也是一個對象,因此我們稱之為類對象
,類對象
在編譯期產生用于創建實例對象,是單例。
類對象
中的元數據
存儲的都是如何創建一個實例的相關信息,那么類對象
和類方法
應該從哪里創建呢?就是從isa
指針指向的結構體創建,類對象
的isa
指針指向的我們稱之為元類(metaclass)
,元類
中保存了創建類對象
以及類方法
所需的所有信息,因此整個結構應該如下圖所示:
通過上圖我們可以清晰的看出來一個實例對象也就是struct objc_object
結構體它的isa
指針指向類對象
,類對象
的isa
指針指向了元類,super_class
指針指向了父類的類對象
,而元類
的super_class
指針指向了父類的元類
,那元類
的isa
指針又指向了什么?為了更清晰的表達直接使用一個大神畫的圖。
通過上圖我們可以看出整個體系構成了一個自閉環,如果是從NSObject
中繼承而來的上圖中的Root class
就是NSObject
。至此,整個實例
、類對象
、元類
的概念也就講清了,接下來我們在代碼中看看這些概念該怎么應用。
@interface Person : NSObject
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSUInteger age;
@end
@implementation Person
@synthesize name = _name;
@synthesize age = _age;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
Class c1 = [p class];
Class c2 = [Person class];
//輸出 1
NSLog(@"%d", c1 == c2);
}
return 0;
}
c1
是通過一個實例對象獲取的Class
,實例對象可以獲取到其類對象
,類名作為消息的接受者時代表的是類對象
,因此類對象獲取Class
得到的是其本身,同時也印證了類對象
是一個單例的想法。
那么如果我們想獲取isa
指針的指向對象呢?
介紹兩個函數
OBJC_EXPORT BOOL class_isMetaClass(Class cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
OBJC_EXPORT Class object_getClass(id obj)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
class_isMetaClass
用于判斷Class
對象是否為元類
,object_getClass
用于獲取對象的isa
指針指向的對象。
再看如下代碼:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
//輸出1
NSLog(@"%d", [p class] == object_getClass(p));
//輸出0
NSLog(@"%d", class_isMetaClass(object_getClass(p)));
//輸出1
NSLog(@"%d", class_isMetaClass(object_getClass([Person class])));
//輸出0
NSLog(@"%d", object_getClass(p) == object_getClass([Person class]));
}
return 0;
}
通過代碼可以看出,一個實例對象通過class
方法獲取的Class
就是它的isa
指針指向的類對象
,而類對象
不是元類
,類對象
的isa
指針指向的對象是元類
。
總結
通過上文的代碼分析,我們已經了解了OC中的類和實例是如何映射到C語言結構體的,實例對象是一個結構體,這個結構體只有一個成員變量,指向構造它的那個類對象,這個類對象中存儲了一切實例對象需要的信息包括實例變量、實例方法等,而類對象是通過元類創建的,元類中保存了類變量和類方法,這樣就完美解釋了整個類和實例是如何映射到結構體的。
下一步
了解類到結構體映射只是揭開runtime
神秘面紗的第一步,下一篇博客將會介紹OC的消息傳遞機制以及runtime
對OC消息傳遞所做的具體操作,感興趣的讀者可以繼續學習下一篇文章從runtime開始: 深入理解OC消息轉發機制。
備注
由于作者水平有限,難免出現紕漏,如有問題還請不吝賜教。