runtime

Runtime庫主要作下面幾件事:算法

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

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

類與對象基礎數據結構 Objective-C類是由Class類型來表示的,它其實是一個指向objc_class結構體的指針。它的定義以下:typedef struct objc_class *Class; objc_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;

isa:類自身也是一個對象,這個對象的Class裏面也有一個isa指針,它指向metaClass(元類),咱們會在後面介紹它。 super_class:指向該類的父類,若是該類已是最頂層的根類(如NSObject或NSProxy),則super_class爲NULL。 cache:用於緩存最近使用的方法。方法緩存,接收者收到消息時,先去cache中查找,在cache中找不到,再去methodLists中遍歷。 version:使用這個字段來提供類的版本信息。這對於對象的序列化很是有用,它但是讓咱們識別出不一樣類定義版本中實例變量佈局的改變。 objc_object與id objc_object是表示一個類的實例的結構體,它的定義以下:(objc/objc.h):數據結構

struct objc_object {
  Class isa  OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;

這個結構體只有一個成員變量,即指向其類的isa指針。多線程

NSObject類的alloc和allocWithZone:方法使用函數class_createInstance來建立objc_object數據結構。架構

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

objc_cache 用於緩存調用過的方法。框架

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) 類自身也是一個對象,咱們能夠向這個對象發送消息(即調用類方法)。

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指針是指向它本身。這樣就造成了一個完美的閉環。 對於NSObject繼承體系來講,其實例方法對體系中的全部實例、類和meta-class都是有效的;而類方法對於體系內的全部類和meta-class都是有效的。 這裏須要注意的是:咱們在一個類對象調用class方法是沒法獲取meta-class,它只是返回類而已。 類與對象操做函數 runtime提供了大量的函數來操做類與對象。類的操做方法大部分是以class爲前綴的,而對象的操做方法大部分是以objc或object_爲前綴。

類名(name) 類名操做的函數主要有:

// 獲取類的類名

const char * class_getName ( Class cls );

對於class_getName函數,若是傳入的cls爲Nil,則返回一個字字符串。 父類(super_class)和元類(meta-class)

// 獲取類的父類

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。

成員變量(ivars)及屬性 在objc_class中,全部的成員變量、屬性的信息是放在鏈表ivars中的。ivars是一個數組,數組中每一個元素是指向Ivar(變量信息)的指針。runtime提供了豐富的函數來操做這一字段。

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

// 獲取類中指定名稱實例成員變量的信息
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))。

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

屬性操做函數

// 獲取指定的屬性
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 );

方法(methodLists)

// 添加方法
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_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:方法來達到相同目的。

協議

// 添加協議
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)

// 獲取版本號
int class_getVersion ( Class cls );

// 設置版本號
void class_setVersion ( Class cls, int version );

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

// 建立一個新類和元類
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函數用於銷燬一個類,不過須要注意的是,若是程序運行中還存在類或其子類的實例,則不能調用針對類調用該方法。 ** 動態建立對象**

// 建立類實例
id class_createInstance ( Class cls, size_t extraBytes );

// 在指定位置建立類實例
id objc_constructInstance ( Class cls, void *bytes );

// 銷燬類實例
void * objc_destructInstance ( id obj );

調用class_createInstance的效果與+alloc方法相似。不過在使用class_createInstance時,咱們須要確切的知道咱們要用它來作什麼。

實例操做函數

// 返回指定對象的一份拷貝
id object_copy ( id obj, size_t size );

// 釋放指定對象佔用的內存
id object_dispose ( id obj );
// 修改類實例的實例變量的值
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快。

// 返回給定對象的類名
const char * object_getClassName ( id obj );

// 返回對象的類
Class object_getClass ( id obj );

// 設置對象的類
Class object_setClass ( id obj, Class cls );

獲取類定義 Objective-C動態運行庫會自動註冊咱們代碼中定義的全部的類。咱們也能夠在運行時建立類定義並使用objc_addClass函數來註冊它們。

// 獲取已註冊的類定義的列表
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體系的,因此在這些類上調用方法是,都應該先檢測一下這個方法是否在這個類中實現。

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

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

類型編碼(Type Encoding) 做爲對Runtime的補充,編譯器將每一個方法的返回值和參數類型編碼爲一個字符串,並將其與方法的selector關聯在一塊兒。這種編碼方案在其它狀況下也是很是有用的,所以咱們可使用@encode編譯器指令來獲取它。

成員變量、屬性 Runtime中關於成員變量和屬性的相關數據結構並很少,只有三個,而且都很簡單。不過還有個很是實用但可能常常被忽視的特性,即關聯對象 ** 基礎數據類型** Ivar是表示實例變量的類型,其實際是一個指向objc_ivar結構體的指針。

typedef struct objc_ivar *Ivar;

struct objc_ivar {

    char *ivar_name                 OBJC2_UNAVAILABLE;  // 變量名

    char *ivar_type                 OBJC2_UNAVAILABLE;  // 變量類型

    int ivar_offset                 OBJC2_UNAVAILABLE;  // 基地址偏移字節

#ifdef __LP64__

    int space                       OBJC2_UNAVAILABLE;

#endif

}

objc_property_t objc_property_t是表示Objective-C聲明的屬性的類型,其實際是指向objc_property結構體的指針,其定義以下:

typedef struct objc_property *objc_property_t;

objc_property_attribute_t objc_property_attribute_t定義了屬性的特性(attribute),它是一個結構體,定義以下:

typedef struct {

    const char *name;           // 特性名

    const char *value;          // 特性值

} objc_property_attribute_t;

關聯對象(Associated Object) 關聯對象是Runtime中一個很是實用的特性。關聯對象相似於成員變量,不過是在運行時添加的。咱們一般會把成員變量(Ivar)放在類聲明的頭文件中,或者放在類實現的@implementation後面。但這有一個缺點,咱們不能再分類中添加成員變量。

咱們可能但願經過使用(甚至是濫用)全局變量來解決這個問題。但這些都不是Ivar,由於他們不會鏈接到一個單獨的實例。所以,這種方法不多使用。

Objective-C針對這一問題,提供了一個解決方案:即關聯對象(Associated Object)。

咱們能夠把關聯對象想象成一個Objective-C對象(如字典),這個對象經過給定的key鏈接到類的一個實例上。不過因爲使用的是C接口,因此key是一個void指針(const void *)。咱們還須要指定一個內存管理策略,以告訴Runtime如何管理這個對象的內存。這個內存管理的策略能夠由如下值指定:

OBJC_ASSOCIATION_ASSIGN

OBJC_ASSOCIATION_RETAIN_NONATOMIC

OBJC_ASSOCIATION_COPY_NONATOMIC

OBJC_ASSOCIATION_RETAIN

OBJC_ASSOCIATION_COPY

當宿主對象被釋放時,會根據指定的內存管理策略來處理關聯對象。若是指定的策略是assign,則宿主釋放時,關聯對象不會被釋放。而若是指定的是retain或者是copy,則宿主釋放時,關聯對象會被釋放。咱們甚至能夠選擇是不是自動retain/copy。當咱們須要在多個線程中處理訪問關聯對象的多線程代碼時,這就很是有用了。

成員變量、屬性的操做方法 成員變量操做,

// 獲取成員變量名
const char * ivar_getName ( Ivar v );

// 獲取成員變量類型編碼
const char * ivar_getTypeEncoding ( Ivar v );

// 獲取成員變量的偏移量
ptrdiff_t ivar_getOffset ( Ivar v );

ivar_getOffset函數,對於類型id或其它對象類型的實例變量,能夠調用object_getIvar和object_setIvar來直接訪問成員變量,而不使用偏移量。

屬性 屬性操做

// 獲取屬性名

const char * property_getName ( objc_property_t property );



// 獲取屬性特性描述字符串

const char * property_getAttributes ( objc_property_t property );



// 獲取屬性中指定的特性

char * property_copyAttributeValue ( objc_property_t property, const char *attributeName );



// 獲取屬性的特性列表
objc_property_attribute_t * property_copyAttributeList ( objc_property_t property, unsigned int *outCount );

● property_copyAttributeValue函數,返回的char *在使用完後須要調用free()釋放。

● property_copyAttributeList函數,返回值在使用完後須要調用free()釋放。

關聯對象

// 設置關聯對象
void objc_setAssociatedObject ( id object, const void *key, id value, objc_AssociationPolicy policy );

// 獲取關聯對象
id objc_getAssociatedObject ( id object, const void *key );

// 移除關聯對象
void objc_removeAssociatedObjects ( id object );

方法與消息 消息處理機制 消息的發送及消息的轉發

基礎數據類型 SEL SEL又叫選擇器,是表示一個方法的selector的指針。 typedef struct objc_selector *SEL; objc_selector結構體的詳細定義沒有在<objc/runtime.h>頭文件中找到。方法的selector用於表示運行時方法的名字。Objective-C在編譯時,會依據每個方法的名字、參數序列,生成一個惟一的整型標識(Int類型的地址),這個標識就是SEL。

SEL sel1 = @selector(method1);
NSLog(@"sel:%p", sel1);

兩個類之間,無論它們是父類與子類的關係,仍是之間沒有這種關係,只要方法名相同,那麼方法的SEL就是同樣的。每個方法都對應着一個SEL。因此在Objective-C同一個類(及類的繼承體系)中,不能存在2個同名的方法,即便參數類型不一樣也不行。相同的方法只能對應一個SEL。這也就致使Objective-C在處理相同方法名且參數個數相同但類型不一樣的方法方面的能力不好。

固然,不一樣的類能夠擁有相同的selector,這個沒有問題。不一樣類的實例對象執行相同的selector時,會在各自的方法列表中去根據selector去尋找本身對應的IMP。

工程中的全部的SEL組成一個Set集合,Set的特色就是惟一,所以SEL是惟一的。

本質上,SEL只是一個指向方法的指針(準確的說,只是一個根據方法名hash化了的KEY值,能惟一表明一個方法),它的存在只是爲了加快方法的查詢速度。

咱們能夠在運行時添加新的selector,也能夠在運行時獲取已存在的selector,咱們能夠經過下面三種方法來獲取SEL:

sel_registerName函數 Objective-C編譯器提供的@selector() NSSelectorFromString()方法 IMP IMP其實是一個函數指針,指向方法實現的首地址。

id (*IMP)(id,SEL,...)

這個函數使用當前CPU架構實現的標準的C調用約定。第一個參數是指向self的指針(若是是實例方法,則是類實例的內存地址;若是是類方法,則是指向元類的指針),第二個參數是方法選擇器(selector),接下來是方法的實際參數列表。

前面介紹過的SEL就是爲了查找方法的最終實現IMP的。因爲每一個方法對應惟一的SEL,所以咱們能夠經過SEL方便快速準確地得到它所對應的IMP,查找過程將在下面討論。取得IMP後,咱們就得到了執行這個方法代碼的入口點,此時,咱們就能夠像調用普通的C語言函數同樣來使用這個函數指針了。

經過取得IMP,咱們能夠跳過Runtime的消息傳遞機制,直接執行IMP指向的函數實現,這樣省去了Runtime消息傳遞過程當中所作的一系列查找操做,會比直接向對象發送消息高效一些。 Method

typedef struct objc_method *Method;

struct objc_method {

    SEL method_name                 OBJC2_UNAVAILABLE;  // 方法名

    char *method_types                  OBJC2_UNAVAILABLE;

    IMP method_imp                      OBJC2_UNAVAILABLE;  // 方法實現
}

咱們能夠看到該結構體中包含一個SEL和IMP,實際上至關於在SEL和IMP之間做了一個映射。有了SEL,咱們即可以找到對應的IMP,從而調用方法的實現代碼。

objc_method_description

struct objc_method_description {SEL name; char *types;};

方法相關操做函數 方法操做相關函數

// 調用指定方法的實現
id method_invoke ( id receiver, Method m, ... );

// 調用返回一個數據結構的方法的實現
void method_invoke_stret ( id receiver, Method m, ... );

// 獲取方法名
SEL method_getName ( Method m );

// 返回方法的實現
IMP method_getImplementation ( Method m );

// 獲取描述方法參數和返回值類型的字符串
const char * method_getTypeEncoding ( Method m );

// 獲取方法的返回值類型的字符串
char * method_copyReturnType ( Method m );

// 獲取方法的指定位置參數的類型字符串
char * method_copyArgumentType ( Method m, unsigned int index );

// 經過引用返回方法的返回值類型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len );

// 返回方法的參數的個數
unsigned int method_getNumberOfArguments ( Method m );

// 經過引用返回方法指定位置參數的類型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );

// 返回指定方法的方法描述結構體
struct objc_method_description * method_getDescription ( Method m );

// 設置方法的實現
IMP method_setImplementation ( Method m, IMP imp );

// 交換兩個方法的實現
void method_exchangeImplementations ( Method m1, Method m2 );

● method_invoke函數,返回的是實際實現的返回值。參數receiver不能爲空。這個方法的效率會比method_getImplementation和method_getName更快。

● method_getName函數,返回的是一個SEL。若是想獲取方法名的C字符串,可使用sel_getName(method_getName(method))。

● method_getReturnType函數,類型字符串會被拷貝到dst中。

● method_setImplementation函數,注意該函數返回值是方法以前的實現。 方法選擇器

// 返回給定選擇器指定的方法的名稱
const char * sel_getName ( SEL sel );

// 在Objective-C Runtime系統中註冊一個方法,將方法名映射到一個選擇器,並返回這個選擇器
SEL sel_registerName ( const char *str );

// 在Objective-C Runtime系統中註冊一個方法
SEL sel_getUid ( const char *str );

// 比較兩個選擇器
BOOL sel_isEqual ( SEL lhs, SEL rhs );

● sel_registerName函數:在咱們將一個方法添加到類定義時,咱們必須在Objective-C Runtime系統中註冊一個方法名以獲取方法的選擇器。

方法調用流程 在Objective-C中,消息直到運行時才綁定到方法實現上。編譯器會將消息表達式[receiver message]轉化爲一個消息函數的調用,即objc_msgSend。這個函數將消息接收者和方法名做爲其基礎參數

objc_msgSend(receiver, selector)
objc_msgSend(receiver, selector, arg1, arg2, ...)
((void (*)(id, SEL, id))objc_msgSend)(receiver, selector, arg1,arg2,...);

這個函數完成了動態綁定的全部事情:

首先它找到selector對應的方法實現。由於同一個方法可能在不一樣的類中有不一樣的實現,因此咱們須要依賴於接收者的類來找到的確切的實現。 它調用方法實現,並將接收者對象及方法的全部參數傳給它。 最後,它將實現返回的值做爲它本身的返回值。 結構體objc_class 指向父類的指針 一個類的方法分發表,即methodLists。 當咱們建立一個新對象時,先爲其分配內存,並初始化其成員變量。其中isa指針也會被初始化,讓對象能夠訪問類及類的繼承體系。

當消息發送給一個對象時,objc_msgSend經過對象的isa指針獲取到類的結構體,而後在方法分發表裏面查找方法的selector。若是沒有找到selector,則經過objc_msgSend結構體中的指向父類的指針找到其父類,並在父類的分發表裏面查找方法的selector。依此,會一直沿着類的繼承體系到達NSObject類。一旦定位到selector,函數會就獲取到了實現的入口點,並傳入相應的參數來執行方法的具體實現。若是最後沒有定位到selector,則會走消息轉發流程。

爲了加速消息的處理,運行時系統緩存使用過的selector及對應的方法的地址。

隱藏參數 objc_msgSend有兩個隱藏參數:

消息接收對象 方法的selector 這兩個參數爲方法的實現提供了調用者的信息。之因此說是隱藏的,是由於它們在定義方法的源代碼中沒有聲明。它們是在編譯期被插入實現代碼的。

雖然這些參數沒有顯示聲明,但在代碼中仍然能夠引用它們。咱們可使用self來引用接收者對象,使用_cmd來引用選擇器。

獲取方法地址 Runtime中方法的動態綁定讓咱們寫代碼時更具靈活性,如咱們能夠把消息轉發給咱們想要的對象,或者隨意交換一個方法的實現等。不過靈活性的提高也帶來了性能上的一些損耗。畢竟咱們須要去查找方法的實現,而不像函數調用來得那麼直接。固然,方法的緩存必定程度上解決了這一問題。

咱們上面提到過,若是想要避開這種動態綁定方式,咱們能夠獲取方法實現的地址,而後像調用函數同樣來直接調用它。特別是當咱們須要在一個循環內頻繁地調用一個特定的方法時,經過這種方式能夠提升程序的性能。

NSObject類提供了methodForSelector:方法,讓咱們能夠獲取到方法的指針,而後經過這個指針來調用實現代碼。咱們須要將methodForSelector:返回的指針轉換爲合適的函數類型,函數參數和返回值都須要匹配上。

這裏須要注意的就是函數指針的前兩個參數必須是id和SEL。

消息轉發 當一個對象能接收一個消息時,就會走正常的方法調用流程。但若是一個對象沒法接收指定消息時,又會發生什麼事呢?默認狀況下,若是是以[object message]的方式調用方法,若是object沒法響應message消息時,編譯器會報錯。但若是是以perform…的形式來調用,則須要等到運行時才能肯定object是否能接收message消息。若是不能,則程序崩潰。

一般,當咱們不能肯定一個對象是否能接收某個消息時,會先調用respondsToSelector:來判斷一下。

當一個對象沒法接收某一消息時,就會啓動所謂」消息轉發(message forwarding)「機制,經過這一機制,咱們能夠告訴對象如何處理未知的消息。默認狀況下,對象接收到未知的消息,會致使程序崩潰。

消息轉發機制基本上分爲三個步驟:

動態方法解析 備用接收者 完整轉發 動態方法解析 對象在接收到未知消息時,首先會調用所屬類的類方法+resolveInstanceMethod:(實例方法)或+resolveClassMethod:(類方法)。在這個方法中,咱們有機會爲該未知消息新增一個」處理方法」「。不過使用該方法的前提是咱們已經實現了該」處理方法」,只須要在運行時經過class_addMethod函數動態添加到類裏面就能夠了。

備用接收者

- (id)forwardingTargetForSelector:(SEL)aSelector

若是一個對象實現了這個方法,並返回一個非nil的結果,則這個對象會做爲消息的新接收者,且消息會被分發到這個對象。固然這個對象不能是self自身,不然就是出現無限循環。固然,若是咱們沒有指定相應的對象來處理aSelector,則應該調用父類的實現來返回結果。

使用這個方法一般是在對象內部,可能還有一系列其它對象能處理該消息,咱們即可借這些對象來處理消息並返回,這樣在對象外部看來,仍是由該對象親自處理了這一消息。

完整消息轉發 若是在上一步還不能處理未知消息,則惟一能作的就是啓用完整的消息轉發機制了。

- (void)forwardInvocation:(NSInvocation *)anInvocation

運行時系統會在這一步給消息接收者最後一次機會將消息轉發給其它對象。對象會建立一個表示消息的NSInvocation對象,把與還沒有處理的消息有關的所有細節都封裝在anInvocation中,包括selector,目標(target)和參數。咱們能夠在forwardInvocation方法中選擇將消息轉發給其它對象。

forwardInvocation:方法的實現有兩個任務:

定位能夠響應封裝在anInvocation中的消息的對象。這個對象不須要能處理全部未知消息。 使用anInvocation做爲參數,將消息發送到選中的對象。anInvocation將會保留調用結果,運行時系統會提取這一結果並將其發送到消息的原始發送者。 不過,在這個方法中咱們能夠實現一些更復雜的功能,咱們能夠對消息的內容進行修改,好比追回一個參數等,而後再去觸發消息。另外,若發現某個消息不該由本類處理,則應調用父類的同名方法,以便繼承體系中的每一個類都有機會處理此調用請求。

還有一個很重要的問題,咱們必須重寫如下方法:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

消息轉發機制使用從這個方法中獲取的信息來建立NSInvocation對象。所以咱們必須重寫這個方法,爲給定的selector提供一個合適的方法簽名。

NSObject的forwardInvocation:方法實現只是簡單調用了doesNotRecognizeSelector:方法,它不會轉發任何消息。這樣,若是不在以上所述的三個步驟中處理未知消息,則會引起一個異常。

從某種意義上來說,forwardInvocation:就像一個未知消息的分發中心,將這些未知的消息轉發給其它對象。或者也能夠像一個運輸站同樣將全部未知消息都發送給同一個接收對象。這取決於具體的實現。

消息轉發與多重繼承 多重繼承將不一樣的功能集成到一個對象中,它會讓對象變得過大,涉及的東西過多;而消息轉發將功能分解到獨立的小的對象中,並經過某種方式將這些對象鏈接起來,並作相應的消息轉發。

不過消息轉發雖然相似於繼承,但NSObject的一些方法仍是能區分二者。如respondsToSelector:和isKindOfClass:只能用於繼承體系,而不能用於轉發鏈。便若是咱們想讓這種消息轉發看起來像是繼承,則能夠重寫這些方法。

Method Swizzling Method Swizzling是改變一個selector的實際實現的技術。經過這一技術,咱們能夠在運行時經過修改類的分發表中selector對應的函數,來修改方法的實現。

要點

●Swizzling應該老是在+load中執行 在Objective-C中,運行時會自動調用每一個類的兩個方法。+load會在類初始加載時調用,+initialize會在第一次調用類的類方法或實例方法以前被調用。這兩個方法是可選的,且只有在實現了它們時纔會被調用。因爲method swizzling會影響到類的全局狀態,所以要儘可能避免在併發處理中出現競爭的狀況。+load能保證在類的初始化過程當中被加載,並保證這種改變應用級別的行爲的一致性。相比之下,+initialize在其執行時不提供這種保證—事實上,若是在應用中沒爲給這個類發送消息,則它可能永遠不會被調用。 ●Swizzling應該老是在dispatch_once中執行 與上面相同,由於swizzling會改變全局狀態,因此咱們須要在運行時採起一些預防措施。原子性就是這樣一種措施,它確保代碼只被執行一次,無論有多少個線程。GCD的dispatch_once能夠確保這種行爲,咱們應該將其做爲method swizzling的最佳實踐。

選擇器、方法與實現 一、Selector(typedef struct objc_selector *SEL):用於在運行時中表示一個方法的名稱。一個方法選擇器是一個C字符串,它是在Objective-C運行時被註冊的。選擇器由編譯器生成,而且在類被加載時由運行時自動作映射操做。 二、Method(typedef struct objc_method *Method):在類定義中表示方法的類型 三、Implementation(typedef id (*IMP)(id, SEL, …)):這是一個指針類型,指向方法實現函數的開始位置。這個函數使用爲當前CPU架構實現的標準C調用規範。第一個參數是指向對象自身的指針(self),第二個參數是方法選擇器。而後是方法的實際參數。

理解這幾個術語之間的關係最好的方式是:一個類維護一個運行時可接收的消息分發表;分發表中的每一個入口是一個方法(Method),其中key是一個特定名稱,即選擇器(SEL),其對應一個實現(IMP),即指向底層C函數的指針。

調用_cmd

- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", NSStringFromClass([self class]));
}

咋看上去是會致使無限循環的。但使人驚奇的是,並無出現這種狀況。在swizzling的過程當中,方法中的[self xxx_viewWillAppear:animated]已經被從新指定到UIViewController類的-viewWillAppear:中。在這種狀況下,不會產生無限循環。不過若是咱們調用的是[self viewWillAppear:animated],則會產生無限循環,由於這個方法的實如今運行時已經被從新指定爲xxx_viewWillAppear:了。

注意事項 Swizzling一般被稱做是一種黑魔法,容易產生不可預知的行爲和沒法預見的後果。雖然它不是最安全的,但若是聽從如下幾點預防措施的話,仍是比較安全的:

老是調用方法的原始實現(除非有更好的理由不這麼作):API提供了一個輸入與輸出約定,但其內部實現是一個黑盒。Swizzle一個方法而不調用原始實現可能會打破私有狀態底層操做,從而影響到程序的其它部分。 避免衝突:給自定義的分類方法加前綴,從而使其與所依賴的代碼庫不會存在命名衝突。 明白是怎麼回事:簡單地拷貝粘貼swizzle代碼而不理解它是如何工做的,不只危險,並且會浪費學習Objective-C運行時的機會。閱讀Objective-C Runtime Reference和查看<objc/runtime.h>頭文件以瞭解事件是如何發生的。 當心操做:不管咱們對Foundation, UIKit或其它內建框架執行Swizzle操做抱有多大信心,須要知道在下一版本中許多事可能會不同。

協議與分類 Objective-C中的分類容許咱們經過給一個類添加方法來擴充它(可是經過category不能添加新的實例變量),而且咱們不須要訪問類中的代碼就能夠作到。

Objective-C中的協議是廣泛存在的接口定義方式,即在一個類中經過@protocol定義接口,在另外類中實現接口,這種接口定義方式也成爲「delegation」模式,@protocol聲明瞭能夠被其餘任何方法類實現的方法,協議僅僅是定義一個接口,而由其餘的類去負責實現。 ** 基礎數據類型** Category Category是表示一個指向分類的結構體的指針

typedef struct objc_category *Category;



struct objc_category {

    char *category_name                          OBJC2_UNAVAILABLE; // 分類名

    char *class_name                             OBJC2_UNAVAILABLE; // 分類所屬的類名

    struct objc_method_list *instance_methods    OBJC2_UNAVAILABLE; // 實例方法列表

    struct objc_method_list *class_methods       OBJC2_UNAVAILABLE; // 類方法列表

    struct objc_protocol_list *protocols         OBJC2_UNAVAILABLE; // 分類所實現的協議列表

}

這個結構體主要包含了分類定義的實例方法與類方法,其中instance_methods列表是objc_class中方法列表的一個子集,而class_methods列表是元類方法列表的一個子集。

Protocol Protocol其中實就是一個對象結構體。

typedef struct objc_object Protocol;

而對於Protocol,runtime提供了一系列函數來對其進行操做,這些函數包括:

// 返回指定的協議
Protocol * objc_getProtocol ( const char *name );

// 獲取運行時所知道的全部協議的數組
Protocol ** objc_copyProtocolList ( unsigned int *outCount );

// 建立新的協議實例
Protocol * objc_allocateProtocol ( const char *name );

// 在運行時中註冊新建立的協議
void objc_registerProtocol ( Protocol *proto );

// 爲協議添加方法
void protocol_addMethodDescription ( Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod );

// 添加一個已註冊的協議到協議中
void protocol_addProtocol ( Protocol *proto, Protocol *addition );

// 爲協議添加屬性
void protocol_addProperty ( Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty );

// 返回協議名
const char * protocol_getName ( Protocol *p );

// 測試兩個協議是否相等
BOOL protocol_isEqual ( Protocol *proto, Protocol *other );

// 獲取協議中指定條件的方法的方法描述數組
struct objc_method_description * protocol_copyMethodDescriptionList ( Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount );

// 獲取協議中指定方法的方法描述
struct objc_method_description protocol_getMethodDescription ( Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod );

// 獲取協議中的屬性列表
objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );

// 獲取協議的指定屬性
objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty );

// 獲取協議採用的協議
Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );

// 查看協議是否採用了另外一個協議
BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );

● objc_getProtocol函數,須要注意的是若是僅僅是聲明瞭一個協議,而未在任何類中實現這個協議,則該函數返回的是nil。

● objc_copyProtocolList函數,獲取到的數組須要使用free來釋放

● objc_allocateProtocol函數,若是同名的協議已經存在,則返回nil

● objc_registerProtocol函數,建立一個新的協議後,必須調用該函數以在運行時中註冊新的協議。協議註冊後即可以使用,但不能再作修改,即註冊完後不能再向協議添加方法或協議

須要強調的是,協議一旦註冊後就不可再修改,即沒法再經過調用protocol_addMethodDescription、protocol_addProtocol和protocol_addProperty往協議中添加方法等。

Runtime並無提供過多的函數來處理分類。對於協議,咱們能夠動態地建立協議,並向其添加方法、屬性及繼承的協議,並在運行時動態地獲取這些信息。

類與對象、成員變量與屬性、方法與消息、分類與協議的處理

self是類的一個隱藏參數,每一個方法的實現的第一個參數即爲self。而super並非隱藏參數,它實際上只是一個」編譯器標示符」,它負責告訴編譯器,當調用viewDidLoad方法時,去調用父類的方法,而不是本類中的方法。而它實際上與self指向的是相同的消息接收者。

super的定義

@interface MyViewController: UIViewController

@end
@implementation MyViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // do something
    ...
}
@end
struct objc_super { id receiver; Class superClass; };

這個結構體有兩個成員:

receiver:即消息的實際接收者

superClass:指針當前類的父類

當咱們使用super來接收消息時,編譯器會生成一個objc_super結構體。就上面的例子而言,這個結構體的receiver就是MyViewController對象,與self相同;superClass指向MyViewController的父類UIViewController。

接下來,發送消息時,不是調用objc_msgSend函數,而是調用objc_msgSendSuper函數,其聲明以下:

id objc_msgSendSuper ( struct objc_super *super, SEL op, ... ); 該函數第一個參數即爲前面生成的objc_super結構體,第二個參數是方法的selector。該函數實際的操做是:從objc_super結構體指向的superClass的方法列表開始查找viewDidLoad的selector,找到後以objc->receiver去調用這個selector,而此時的操做流程就是以下方式了

objc_msgSend(objc_super->receiver, @selector(viewDidLoad)) 因爲objc_super->receiver就是self自己,因此該方法實際與下面這個調用是相同的:

objc_msgSend(self, @selector(viewDidLoad)) 庫相關操做 庫相關的操做主要是用於獲取由系統提供的庫相關的信息,主要包含如下函數:

// 獲取全部加載的Objective-C框架和動態庫的名稱
const char ** objc_copyImageNames ( unsigned int *outCount );

// 獲取指定類所在動態庫
const char * class_getImageName ( Class cls );

// 獲取指定庫或框架中全部類的類名
const char ** objc_copyClassNamesForImage ( const char *image, unsigned int *outCount );

塊操做 咱們都知道block給咱們帶到極大的方便,蘋果也不斷提供一些使用block的新的API。同時,蘋果在runtime中也提供了一些函數來支持針對block的操做,這些函數包括:

// 建立一個指針函數的指針,該函數調用時會調用特定的block
IMP imp_implementationWithBlock ( id block );

// 返回與IMP(使用imp_implementationWithBlock建立的)相關的block
id imp_getBlock ( IMP anImp );

// 解除block與IMP(使用imp_implementationWithBlock建立的)的關聯關係,並釋放block的拷貝
BOOL imp_removeBlock ( IMP anImp );

● imp_implementationWithBlock函數:參數block的簽名必須是method_return_type ^(id self, method_args …)形式的。該方法能讓咱們使用block做爲IMP。

弱引用操做

// 加載弱引用指針引用的對象並返回
id objc_loadWeak ( id *location );

// 存儲__weak變量的新值
id objc_storeWeak ( id *location, id obj );

● objc_loadWeak函數:該函數加載一個弱指針引用的對象,並在對其作retain和autoreleasing操做後返回它。這樣,對象就能夠在調用者使用它時保持足夠長的生命週期。該函數典型的用法是在任何有使用__weak變量的表達式中使用。

● objc_storeWeak函數:該函數的典型用法是用於__weak變量作爲賦值對象時。

宏定義 布爾值

#define YES  (BOOL)1

#define NO   (BOOL)0

這兩個宏定義定義了表示布爾值的常量,須要注意的是YES的值是1,而不是非0值。

空值

#define nil  __DARWIN_NULL

#define Nil  __DARWIN_NULL

其中nil用於空的實例對象,而Nil用於空類對象。

分發函數原型

#define OBJC_OLD_DISPATCH_PROTOTYPES  1

該宏指明分發函數是否必須轉換爲合適的函數指針類型。當值爲0時,必須進行轉換

Objective-C根類

#define OBJC_ROOT_CLASS

若是咱們定義了一個Objective-C根類,則編譯器會報錯,指明咱們定義的類沒有指定一個基類。這種狀況下,咱們就可使用這個宏定義來避過這個編譯錯誤。該宏在iOS 7.0後可用。

局部變量存儲時長

#define NS_VALID_UNTIL_END_OF_SCOPE

該宏代表存儲在某些局部變量中的值在優化時不該該被編譯器強制釋放。

咱們將局部變量標記爲id類型或者是指向ObjC對象類型的指針,以便存儲在這些局部變量中的值在優化時不會被編譯器強制釋放。相反,這些值會在變量再次被賦值以前或者局部變量的做用域結束以前都會被保存。

相關文章
相關標籤/搜索