iOS runtime

參考:
代碼地址:https://opensource.apple.com/tarballs/objc4/
文檔地址:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008048-CH1-SW1
https://developer.apple.com/documentation/objectivec?language=objc

Overview(概述)

The Objective-C Runtime module APIs define the base of the Objective-C language. These APIs include:(Objective-C運(yùn)行時(shí)模塊api定義了Objective-C語(yǔ)言的基礎(chǔ)两踏。這些api包括:)

  • Types such as the NSObject class and the NSObject protocol that provide the root functionality of most Objective-C classes(類(lèi)型梦染,如NSObject類(lèi)和NSObject協(xié)議,它們提供了大多數(shù)Objective-C類(lèi)的根功能)
  • Functions and data structures that comprise the Objective-C runtime, which provides support for the dynamic properties of the Objective-C language(包含Objective-C運(yùn)行時(shí)的函數(shù)和數(shù)據(jù)結(jié)構(gòu)泛粹,它支持Objective-C語(yǔ)言的動(dòng)態(tài)屬性)
    You typically don't need to use this module directly.(通常不需要直接使用此模塊晶姊。)

objc_object

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

objc_class

typedef struct objc_class *Class;

struct objc_class {
    //結(jié)構(gòu)體的第一個(gè)成員變量也是isa指針伪货,這就說(shuō)明了Class本身其實(shí)也是一個(gè)對(duì)象碱呼,因此我們稱(chēng)之為類(lèi)對(duì)象巍举,類(lèi)對(duì)象在編譯期產(chǎn)生用于創(chuàng)建實(shí)例對(duì)象,是單例蜓谋。
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

objc_method_list

struct objc_method_list {
    struct objc_method_list * _Nullable obsolete             OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}   

Method(objc_method)

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;
struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    //方法的參數(shù)類(lèi)型和返回值類(lèi)型。
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    //方法的實(shí)現(xiàn)观堂,本質(zhì)上是一個(gè)函數(shù)指針
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
}         

objc_cache

//cache為方法調(diào)用的性能進(jìn)行優(yōu)化呀忧,通俗的講而账,每當(dāng)實(shí)例對(duì)象接收到一個(gè)消息時(shí)候,它不會(huì)直接在isa指向的類(lèi)方法的方法列表中遍歷查找能夠響應(yīng)消息的方法笔横,效率太低了吹缔,而是有限在cache中查找锯茄。Runtime系統(tǒng)會(huì)把被調(diào)用的方法存在cache中,下次查找的時(shí)候效率更高俗冻。
struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method _Nullable buckets[1]                              OBJC2_UNAVAILABLE;
};

SEL(objc_selector)

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

objc_msgSend函數(shù)第二個(gè)參數(shù)類(lèi)型為SEL迄薄,它是selector在Objective-C中的表示類(lèi)型

  • 同一個(gè)類(lèi)讥蔽,selector不能重復(fù)
  • 不同的類(lèi),selector可以重復(fù)

IMP

/// A pointer to the function of a method implementation. 指向一個(gè)方法實(shí)現(xiàn)的指針
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 

Category(objc_category)

//分類(lèi)中可以添加實(shí)例方法新症,類(lèi)方法徒爹,甚至可以實(shí)現(xiàn)協(xié)議隆嗅,添加屬性胖喳,不可以添加成員變量。
struct category_t {
    //是指 class_name 而不是 category_name技健。
    const char *name;
    //要擴(kuò)展的類(lèi)對(duì)象顷级,編譯期間是不會(huì)定義的弓颈,而是在Runtime階段通過(guò)name對(duì) 應(yīng)到對(duì)應(yīng)的類(lèi)對(duì)象翔冀。
    classref_t cls;
    struct method_list_t *instanceMethods;
    //category中所有添加的類(lèi)方法的列表纤子。
    struct method_list_t *classMethods;
    //category實(shí)現(xiàn)的所有協(xié)議的列表控硼。
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

Ivar(objc_ivar)

typedef struct objc_ivar *Ivar;
//表示類(lèi)中實(shí)例變量的類(lèi)型,objc_ivar可以根據(jù)實(shí)例查找其在類(lèi)中的名字卡乾,也就是“反射”;
struct objc_ivar {
    char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
    char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}    

objc_property_t

//@property標(biāo)記了類(lèi)中的屬性幔妨,它是一個(gè)指向objc_property結(jié)構(gòu)體的指針:
typedef struct objc_property *objc_property_t;
//通過(guò)class_copyPropertyList和protocol_copyPropertyList方法來(lái)獲取類(lèi)和協(xié)議中的屬性
OBJC_EXPORT objc_property_t _Nonnull * _Nullable
class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)
OBJC_EXPORT objc_property_t _Nonnull * _Nullable
protocol_copyPropertyList(Protocol * _Nonnull proto,
                          unsigned int * _Nullable outCount)
unsigned int number = 0
objc_property_t *propertys = class_copyPropertyList([self class], &number);
 for (int i = 0; i < number; i ++) {
       objc_property_t property = propertys[i];
       const char *name = property_getName(property);
       const char *type = property_getAttributes(property);
       NSString *nameStr = [NSString stringWithUTF8String:name];
       NSString *typeStr = [NSString stringWithUTF8String:type];
       NSLog(@"name === %@   type === %@", nameStr, typeStr);
 }

objec_msgSend

objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)

消息機(jī)制

文檔地址:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtHowMessagingWorks.html#//apple_ref/doc/uid/TP40008048-CH104-SW1

The objc_msgSend Function

In Objective-C, messages aren’t bound to method implementations until runtime. The compiler converts a message expression,(在Objective-C中古话,消息直到運(yùn)行時(shí)才綁定到方法實(shí)現(xiàn)陪踩。編譯器轉(zhuǎn)換消息表達(dá)式肩狂,)

[receiver message]

into a call on a messaging function, objc_msgSend. This function takes the receiver and the name of the method mentioned in the message—that is, the method selector—as its two principal parameters:(調(diào)用消息傳遞函數(shù)objc_msgSend婚温。該函數(shù)將接收方和消息中提到的方法名稱(chēng)(即方法選擇器)作為其兩個(gè)主要參數(shù):)

Any arguments passed in the message are also handed to objc_msgSend:(在消息中傳遞的任何參數(shù)也會(huì)傳遞給objc_msgSend:)

objc_msgSend(receiver, selector, arg1, arg2, ...)

The messaging function does everything necessary for dynamic binding:(消息傳遞函數(shù)執(zhí)行動(dòng)態(tài)綁定所需的所有操作:)

  • It first finds the procedure (method implementation) that the selector refers to. Since the same method can be implemented differently by separate classes, the precise procedure that it finds depends on the class of the receiver.(它首先找到選擇器引用的過(guò)程(方法實(shí)現(xiàn))。由于相同的方法可以由不同的類(lèi)以不同的方式實(shí)現(xiàn)力图,所以它找到的精確過(guò)程取決于接收者的類(lèi)吃媒。)

  • It then calls the procedure, passing it the receiving object (a pointer to its data), along with any arguments that were specified for the method.(然后調(diào)用該過(guò)程赘那,將接收對(duì)象(指向其數(shù)據(jù)的指針)以及為該方法指定的任何參數(shù)傳遞給它。)

  • Finally, it passes on the return value of the procedure as its own return value.(最后,它將過(guò)程的返回值作為自己的返回值傳遞拱礁。)

Note: The compiler generates calls to the messaging function. You should never call it directly in the code you write.(注意:編譯器生成對(duì)消息傳遞函數(shù)的調(diào)用呢灶。在編寫(xiě)代碼時(shí)鸯乃,永遠(yuǎn)不要直接調(diào)用它)

類(lèi)和對(duì)象的結(jié)構(gòu).gif

When a message is sent to an object, the messaging function follows the object’s isa pointer to the class structure where it looks up the method selector in the dispatch table. If it can’t find the selector there, objc_msgSend follows the pointer to the superclass and tries to find the selector in its dispatch table. Successive failures cause objc_msgSend to climb the class hierarchy until it reaches the NSObject class. Once it locates the selector, the function calls the method entered in the table and passes it the receiving object’s data structure.(當(dāng)消息被發(fā)送到對(duì)象時(shí)赘娄,消息傳遞函數(shù)遵循對(duì)象的isa指針指向類(lèi)結(jié)構(gòu)遣臼,在這個(gè)類(lèi)結(jié)構(gòu)中揍堰,它在分派表中查找方法選擇器屏歹。如果在那里找不到選擇器蝙眶,objc_msgSend會(huì)跟隨指向超類(lèi)的指針褪那,并嘗試在其分派表中找到選擇器。連續(xù)的失敗導(dǎo)致objc_msgSend爬上類(lèi)層次結(jié)構(gòu)博敬,直到它到達(dá)NSObject類(lèi)偏窝。一旦找到選擇器祭往,函數(shù)就調(diào)用表中輸入的方法默赂,并將接收對(duì)象的數(shù)據(jù)結(jié)構(gòu)傳遞給它缆八。)

This is the way that method implementations are chosen at runtime—or, in the jargon of object-oriented programming, that methods are dynamically bound to messages.(這就是在運(yùn)行時(shí)選擇方法實(shí)現(xiàn)的方法—或者,用面向?qū)ο缶幊痰男g(shù)語(yǔ)來(lái)說(shuō)奖恰,方法是動(dòng)態(tài)綁定到消息的方法瑟啃。)

To speed the messaging process, the runtime system caches the selectors and addresses of methods as they are used. There’s a separate cache for each class, and it can contain selectors for inherited methods as well as for methods defined in the class. Before searching the dispatch tables, the messaging routine first checks the cache of the receiving object’s class (on the theory that a method that was used once may likely be used again). If the method selector is in the cache, messaging is only slightly slower than a function call. Once a program has been running long enough to “warm up” its caches, almost all the messages it sends find a cached method. Caches grow dynamically to accommodate new messages as the program runs.(為了加快消息傳遞過(guò)程,運(yùn)行時(shí)系統(tǒng)在使用選擇器和方法地址時(shí)緩存它們。每個(gè)類(lèi)都有一個(gè)單獨(dú)的緩存犹撒,它可以包含繼承方法和類(lèi)中定義的方法的選擇器。在搜索分派表之前祥款,消息傳遞例程首先檢查接收對(duì)象的類(lèi)的緩存(理論上镰踏,使用過(guò)一次的方法可能會(huì)再次使用)。如果方法選擇器在緩存中绊率,消息傳遞只比函數(shù)調(diào)用稍微慢一點(diǎn)。一旦一個(gè)程序運(yùn)行了足夠長(zhǎng)的時(shí)間來(lái)“預(yù)熱”它的緩存藐俺,它發(fā)送的幾乎所有消息都會(huì)找到一個(gè)緩存的方法卿啡。緩存在程序運(yùn)行時(shí)動(dòng)態(tài)增長(zhǎng)颈娜,以適應(yīng)新的消息。)

消息傳遞

  1. 系統(tǒng)首先找到消息的接收對(duì)象同仆,然后通過(guò)對(duì)象的isa找到它的類(lèi)。
  2. 在它的類(lèi)中查找method_list扶镀,是否有selector方法(先查緩存)。
  3. 沒(méi)有則查找父類(lèi)的method_list。
  4. 找到對(duì)應(yīng)的method什乙,執(zhí)行它的IMP。
  5. 轉(zhuǎn)發(fā)IMP的return值忆某。

實(shí)例、類(lèi)聋呢、父類(lèi)徒探、元央串、根元類(lèi)

isa&superclass指向.jpg

類(lèi)對(duì)象就是一個(gè)結(jié)構(gòu)體struct objc_class,這個(gè)結(jié)構(gòu)體存放的數(shù)據(jù)稱(chēng)為元數(shù)據(jù)(metadata)厦酬,
類(lèi)對(duì)象中的元數(shù)據(jù)存儲(chǔ)的都是如何創(chuàng)建一個(gè)實(shí)例的相關(guān)信息,類(lèi)對(duì)象和類(lèi)方法就是從isa指針指向的結(jié)構(gòu)體創(chuàng)建,類(lèi)對(duì)象的isa指針指向的我們稱(chēng)之為元類(lèi)(metaclass)筹裕,
struct objc_object結(jié)構(gòu)體實(shí)例它的isa指針指向類(lèi)對(duì)象乐埠,
類(lèi)對(duì)象的isa指針指向了元類(lèi)豪治,super_class指針指向了父類(lèi)的類(lèi)對(duì)象,
而元類(lèi)的super_class指針指向了父類(lèi)的元類(lèi)花吟,那元類(lèi)的isa指針又指向了自己。
元類(lèi)(Meta Class)是一個(gè)類(lèi)對(duì)象的類(lèi)今布。
在上面我們提到造虎,所有的類(lèi)自身也是一個(gè)對(duì)象份蝴,我們可以向這個(gè)對(duì)象發(fā)送消息(即調(diào)用類(lèi)方法)。
為了調(diào)用類(lèi)方法,這個(gè)類(lèi)的isa指針必須指向一個(gè)包含這些類(lèi)方法的一個(gè)objc_class結(jié)構(gòu)體。這就引出了meta-class的概念匣椰,元類(lèi)中保存了創(chuàng)建類(lèi)對(duì)象以及類(lèi)方法所需的所有信息蛤奥。
任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類(lèi),而根類(lèi)的meta-class的isa指針是指向它自己啊掏。

Using Hidden Arguments(兩個(gè)隱藏參數(shù))

  • The receiving object(接收的實(shí)例對(duì)象)
  • The selector for the method(方法選擇器SEL)

Dynamic Method Resolution(動(dòng)態(tài)方法解析)
文檔地址:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtDynamicResolution.html#//apple_ref/doc/uid/TP40008048-CH102-SW1

void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically)) {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}
@end

Message Forwarding(消息轉(zhuǎn)發(fā))
文檔地址:
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtForwarding.html#//apple_ref/doc/uid/TP40008048-CH105-SW1

消息轉(zhuǎn)發(fā).png
  1. 重定向髓霞,使用備用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(foo)) {
        return [Person new];//返回Person對(duì)象兢交,讓Person對(duì)象接收這個(gè)消息
    }
    
    return [super forwardingTargetForSelector:aSelector];
}
  1. 完整消息轉(zhuǎn)發(fā)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];//簽名晴裹,進(jìn)入forwardInvocation
    }
    
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = anInvocation.selector;

    Person *p = [Person new];
    if([p respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:p];
    }
    else {
        [self doesNotRecognizeSelector:sel];
    }

}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末经磅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)枢劝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)轴捎,“玉大人侦锯,你說(shuō)我怎么就攤上這事汇竭。” “怎么了细燎?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵两曼,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我玻驻,道長(zhǎng)悼凑,這世上最難降的妖魔是什么偿枕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮户辫,結(jié)果婚禮上渐夸,老公的妹妹穿的比我還像新娘。我一直安慰自己渔欢,他們只是感情好墓塌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著奥额,像睡著了一般苫幢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上垫挨,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天韩肝,我揣著相機(jī)與錄音,去河邊找鬼九榔。 笑死哀峻,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的哲泊。 我是一名探鬼主播谜诫,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼攻旦!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起生逸,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤牢屋,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后槽袄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體烙无,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年遍尺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了截酷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡乾戏,死狀恐怖迂苛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鼓择,我是刑警寧澤三幻,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站呐能,受9級(jí)特大地震影響念搬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一朗徊、第九天 我趴在偏房一處隱蔽的房頂上張望首妖。 院中可真熱鬧,春花似錦爷恳、人聲如沸有缆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)妒貌。三九已至,卻和暖如春铸豁,著一層夾襖步出監(jiān)牢的瞬間灌曙,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工节芥, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留在刺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓头镊,卻偏偏與公主長(zhǎng)得像蚣驼,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子相艇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容