runtime看我就夠了!

1.what?

Objective-C具有相當(dāng)多的動(dòng)態(tài)特性,也就是經(jīng)常被提到和用到的有動(dòng)態(tài)類型(Dynamic typing),動(dòng)態(tài)綁定(Dynamic binding),和動(dòng)態(tài)加載(Dynamic loading)。這些特性都是基于runtime實(shí)現(xiàn)的。so,Objective-C的Runtime是一個(gè)運(yùn)行時(shí)庫(Runtime Libary),它是由C語音和匯編寫的庫。為C添加了面相對(duì)象的能力并創(chuàng)造了 Objective-C,這就是說它在類信息(Class information) 中被加載,完成所有的方法分發(fā),方法轉(zhuǎn)發(fā),等等。Objective-C runtime 創(chuàng)建了所有需要的結(jié)構(gòu)體,讓 Objective-C 的面相對(duì)象編程變?yōu)榭赡堋?/p>

Objective-C 是面相運(yùn)行時(shí)的語言(runtime oriented language),就是說它會(huì)盡可能的把編譯和鏈接時(shí)要執(zhí)行的邏輯延遲到運(yùn)行時(shí)。這就給了你很大的靈活性,你可以按需要把消息重定向給合適的對(duì)象,你甚至可以交換方法的實(shí)現(xiàn),等等。

2.引導(dǎo)--神經(jīng)病院objc runtime入院考試

看看神經(jīng)病院的objc runtime的入院考試題。這些個(gè)題雖然不會(huì)在面試中面到,但是對(duì)于runtime的理解是很有幫助的。

@implementation Son : Father

- (id)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}

BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
@interface NSObject (Sark)
+ (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo {
    NSLog(@"IMP: -[NSObject (Sark) foo]");
}
@end
// 測(cè)試代碼
[NSObject foo];
[[NSObject new] foo];
@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Sark
- (void)speak {
    NSLog(@"my name's %@", self.name);
}
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id cls = [Sark class];
    void *obj = &cls;
    [(__bridge id)obj speak];
}
@end

帶著思考以及你們的答案,讓我們開始runtime之旅。

3. objc中你不能不知道的元素

3.1 從selfsuper開始

首先得理解selfsuper這兩個(gè)概念。否則無從下手。

self是類的隱藏參數(shù),指向當(dāng)前調(diào)用方法的這個(gè)類的實(shí)例。
super是一個(gè)Magic Keyword,它本質(zhì)是一個(gè)編譯器標(biāo)示符,和self 一樣都是指向是一個(gè)消息接收者。

3.2 id(對(duì)象)

id 定義? (objc.h )
/// A pointer to an instance of a class.
typedef struct objc_object *id;

按照解釋,通俗的來說就是一個(gè)指針。objc_object又是什么?

id這個(gè)struct的定義本身就帶了個(gè), 所以我們?cè)谑褂闷渌?code>NSObject類型聲明實(shí)例時(shí)需要在前加上, 使用id時(shí)卻不用 。

什么是objc_object? (objc.h )
/// Represents an instance of a class. 
struct objc_object {
     Class isa  OBJC_ISA_AVAILABILITY;
};

這里給出了解釋是說,objc_object代表的是一個(gè)類的實(shí)例。

這個(gè)時(shí)候我們知道Objective-C中的object在最后會(huì)被轉(zhuǎn)換成C的結(jié)構(gòu)體, 在這個(gè)struct中有個(gè)isa指針,指向它的類別Class。
有一種說法解釋isa還是比較容易理解的:is a pointer,是個(gè)指針。

3.3 class(類、類對(duì)象)

上面出現(xiàn)了class,這又是什么呢?

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

是一個(gè)隱式的類型代表OC中的類,那么objc_class是什么?

struct objc_class {
     Class isa;                                 // isa 指針
     Class super_class;                         // 父類,指向父類
     const char *name;                          // 類名
     long version;                              // 版本
     long info;                                 // 類信息
     long instance_size;                        // 實(shí)例大小
     struct objc_ivar_list *ivars;              // 參數(shù)鏈表
     struct objc_method_list **methodLists;     // 方法鏈表
     struct objc_cache *cache;                  // 方法緩存,調(diào)用過的方法存入緩存列表,下次調(diào)用優(yōu)先從棧中尋找
     struct objc_protocol_list *protocols;      // 協(xié)議鏈表
}OBJC2_UNAVAILABLE;
Use `Class` instead of `struct objc_class`

objc_class的結(jié)構(gòu)體代表的是一個(gè)class。

我們都知道在oc中一切皆為對(duì)象。

下載Objc源碼,在 objc-runtime-new.h 中,objc_class還有這樣的定義

struct objc_class : objc_object {
 // Class ISA;
    Class superclass;
    ...
    ...
}

也就是說,class本身也是一個(gè)對(duì)象,也有superclass。class是一個(gè)指向類對(duì)象的指針。這個(gè)class也有一個(gè)isa指針,這個(gè)指針指向的就是元類。

比較繞,舉個(gè)例子

Son *son = [[Son alloc] init];
// 在這里,我們初始化了一個(gè)son的實(shí)例變量,就是常說的對(duì)象。
// son對(duì)象的類是Son。
// 而Son這個(gè)class也是一個(gè)對(duì)象。(類對(duì)象)
// Son這個(gè)對(duì)象的類就是上面說的元類。
Class_MetaClass.jpg

總結(jié):

<b>

  1. 每個(gè)Class都有一個(gè)isa指針指向一個(gè)唯一的Meta Class。
  2. 每一個(gè)Meta Class的isa指針都指向最上層的Meta Class(圖中的NSObject的Meta Class)
  3. 最上層的Meta Class的isa指針指向自己,形成一個(gè)回路
  4. 每一個(gè)Meta Class的super class指針指向它原本Class的 Super Class的Meta Class。但是最上層的Meta Class的 Super Class指向NSObject Class本身
  5. 最上層的NSObject Class的super class指向 nil。
    </b>

3.4 SEL

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

選擇器,一個(gè)方法selector。

并沒有找到objc_selector的結(jié)構(gòu)定義,selector用于表示一個(gè)運(yùn)行時(shí)方法的名字,在運(yùn)行時(shí),會(huì)根據(jù)方法的名字、參數(shù)序列生產(chǎn)一個(gè)唯一的標(biāo)識(shí),就是SEL,說白了就是方法名的地址,一個(gè)字符串。

在Objective-C同一個(gè)類(及類的繼承體系)中,不能存在2個(gè)同名的方法,即使參數(shù)類型不同也不行。相同的方法只能對(duì)應(yīng)一個(gè)SEL。這也就導(dǎo)致Objective-C在處理相同方法名且參數(shù)個(gè)數(shù)相同但類型不同的方法方面的能力很差。

不同的類中有相同的selector,這個(gè)無所謂,不同的類在執(zhí)行過程中,會(huì)在各自的方法列表中根據(jù)selector尋找相應(yīng)的SEL。
工程中SEL是一個(gè)set集合,每一個(gè)SEL都是唯一的。SEL只是一個(gè)指向方法的指針??梢酝ㄟ^以下三種獲取SEL:

  1. sel_registerName函數(shù)
  2. Objective-C編譯器提供的@selector()
  3. NSSelectorFromString()
SEL func1 = sel_registerName("btnClick");
SEL func2 = @selector(btnClick);
SEL func3 = NSSelectorFromString(@"btnClick");
if ([self respondsToSelector:func1]) {
    [self performSelector:func1 withObject:nil];
}

這個(gè)時(shí)候會(huì)告訴你有警告, "PerformSelector may cause a leak because its selector is unknown"。
為什么會(huì)出現(xiàn)這種警告呢???

如果正常的使用[self btnClick], btnClick這個(gè)方法沒有實(shí)現(xiàn),系統(tǒng)在編譯階段會(huì)報(bào)錯(cuò),否則會(huì)走正常的流程調(diào)用方法。
如果使用上述方式,一切方法都是在運(yùn)行時(shí)進(jìn)行處理的,不知道有沒有實(shí)現(xiàn)該方法,只有在運(yùn)行時(shí)才會(huì)知道有沒有該方法,有則執(zhí)行,沒有發(fā)生crash。

這種警告怎么消除?讓我們繼續(xù)往下看。

3.5 IMP

/// A pointer to the function of a method implementation.
void (*IMP)(id, SEL, ...)     
// id接受消息對(duì)象的id,SEL選擇器,返回一個(gè)void

是一個(gè)指針,函數(shù)的指針,指向方法實(shí)現(xiàn)的首地址。

我們可以根據(jù)選擇器SEL,獲取對(duì)應(yīng)的函數(shù)指針I(yè)MP,然后我們就可以像調(diào)用C的函數(shù)一樣使用函數(shù)指針。通過SEL獲取IMP的函數(shù)指針,這樣我們可以跳出runtime的運(yùn)行機(jī)制消息傳遞,直接執(zhí)行IMP的函數(shù)實(shí)現(xiàn)。這樣比直接向?qū)嵗龑?duì)象發(fā)送消息高效。
我們可以這樣獲取IMP:

method_getImplementation(Method m);     // 獲取任意方法法指針
[self methodForSelector:sel];           // 獲取本類中的方法指針

我們可以通過如下轉(zhuǎn)換,將上面提到的警告消除。

SEL sel = NSSelectorFromString(@"btnClick");
IMP imp = [self methodForSelector:sel];
void (*func)(id, SEL) = (void*)imp;
func(self, sel);
 
主要去看IMP的定義 void (*IMP)(id, SEL),將現(xiàn)有的函數(shù)指針轉(zhuǎn)換成C的形式實(shí)現(xiàn)。

3.6 Method

// 方法鏈表
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;
     
    int method_count                                         OBJC2_UNAVAILABLE;
    #ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
    #endif
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}
 
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;
 
struct objc_method {
   SEL method_name;         // 方法名
   char *method_types;      // 類型 char指針 存儲(chǔ)參數(shù)和返回值
   IMP method_imp;          // 函數(shù)指針
}

在上面的objc_class的定義中有objc_method_list變量用來存儲(chǔ)方法列表,而Method的結(jié)構(gòu)體中,可以看出,存儲(chǔ)的是SEL <-> IMP的映射。

3.7 Cache

在看看cache的結(jié)構(gòu)體,cache存儲(chǔ)用過的方法

struct objc_cache {
    unsigned int mask // total = mask + 1                    
        OBJC2_UNAVAILABLE;
    unsigned int occupied                                    
        OBJC2_UNAVAILABLE;
    Method buckets[1]                                        
        OBJC2_UNAVAILABLE;
};

mask: 指定分配cache buckets的總數(shù)。在方法查找中,runtime使用這個(gè)字段確定數(shù)組的索引位置。
occupied: 實(shí)際占用cache buckets的總數(shù)。
buckets: 指定Method數(shù)據(jù)結(jié)構(gòu)指針的數(shù)組。這個(gè)數(shù)組可能包含不超過mask+1個(gè)元素。需要注意的是,指針可能是NULL,表示這個(gè)緩存bucket沒有被占用,另外被占用的bucket可能是不連續(xù)的。這個(gè)數(shù)組可能會(huì)隨著時(shí)間而增長(zhǎng)。

3.8 Ivar

typedef struct objc_ivar *Ivar;

Ivar代表類中實(shí)例變量的類型。

objc_ivar的定義如下:

struct objc_ivar {
    char *ivar_name                   OBJC2_UNAVAILABLE; // 變量名
    char *ivar_type                   OBJC2_UNAVAILABLE; // 變量類型
    int ivar_offset                   OBJC2_UNAVAILABLE; // ?基地址偏移字節(jié)
#ifdef __LP64__
    int space                         OBJC2_UNAVAILABLE; // 占用空間
#endif
}

class中提到的objc_ivar_list,定義如下:

struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE; // 變量個(gè)數(shù)
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE; // 占用大小
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE; // 變量數(shù)組
}

3.9 objc_property_t

typedef struct objc_property *objc_property_t;

objc_property_t是屬性。與之相關(guān)聯(lián)的還有一個(gè)objc_property_attribute_t。

/// Defines a property attribute
typedef struct {
    const char *name;           /**< The name of the attribute */
    const char *value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

說了這么多,是不是覺得沒毛用?讓我們進(jìn)入正題。

4. 消息

4.1 objc_msgSend 動(dòng)態(tài)綁定

在OC中,消息在運(yùn)行時(shí)才會(huì)綁定到方法的實(shí)現(xiàn)上。編譯器會(huì)將消息表達(dá)式[receiver message]轉(zhuǎn)化為一個(gè)消息函數(shù)的調(diào)用,即objc_msgSend,這個(gè)函數(shù)將消息的接收者和方法名作為參數(shù)
objc_msgSend(receiver, selector)。

receiver:消息的接收者。
selector:方法的選擇器。

<b>這個(gè)函數(shù)完成了動(dòng)態(tài)綁定的所有事情:</b>

  1. 找到selector對(duì)應(yīng)的方法實(shí)現(xiàn),因?yàn)橥粋€(gè)方法可能在不同的的類中有不同的實(shí)現(xiàn),所以我么需要依賴接收者的類來找到確切的實(shí)現(xiàn)。
  2. 調(diào)用方法的實(shí)現(xiàn),并將接收者對(duì)象及方法的所有參數(shù)傳給它
  3. 將實(shí)現(xiàn)返回的值作為自己的返回值。

objc_msgSend每調(diào)用一次方法后,就會(huì)把該方法緩存到cache列表中,下次的時(shí)候,就直接優(yōu)先從cache列表中尋找,如果cache沒有,才從methodLists中查找方法。

  1. 當(dāng)消息發(fā)送給一個(gè)對(duì)象時(shí),首先從運(yùn)行時(shí)系統(tǒng)緩存使用過的方法中尋找(cache),如果找到,執(zhí)行該方法,如果未找到繼續(xù)執(zhí)行
  2. objc_msgSend通過對(duì)象的isa指針獲取到類的結(jié)構(gòu)體,然后在方法列表中查找對(duì)應(yīng)的selector,如果沒有找到則在指向父類的方法列表中尋找,依次、最后到NSObject,如果找到,加入緩存Cache,沒有找到,則會(huì)走消息轉(zhuǎn)發(fā)流程。
  3. 這里需要注意的是,實(shí)例方法(-方法)存在對(duì)應(yīng)類的方法列表中,而類方法(+方法)存在對(duì)應(yīng)元類的方法列表中。

4.2 消息轉(zhuǎn)發(fā)

也有說是動(dòng)態(tài)方法解析,動(dòng)態(tài)方法決議。都是一個(gè)意思。

消息轉(zhuǎn)發(fā)機(jī)制基本上分為三個(gè)步驟:

  1. 動(dòng)態(tài)方法解析
  2. 備用接收者
  3. 完整轉(zhuǎn)發(fā)


    消息轉(zhuǎn)發(fā).png

當(dāng)發(fā)送一個(gè)消息的時(shí)候,先從cache列表中尋找,有的話執(zhí)行,沒有從這個(gè)實(shí)例方法或者類方法的方法列表中查找,有的話執(zhí)行,沒有的話:動(dòng)態(tài)轉(zhuǎn)發(fā)第一步

  1. 方法解析。調(diào)用+(BOOL)resolveInstanceMethod:(SEL)sel。(+resolveClassMethod)這個(gè)方法,叫動(dòng)態(tài)方法解析(決議)。在這個(gè)方法中,我們有機(jī)會(huì)為該未知消息新增一個(gè)”方法”“。不過使用該方法的前提是我們已經(jīng)實(shí)現(xiàn)了該”方法”,只需要在運(yùn)行時(shí)通過class_addMethod函數(shù)動(dòng)態(tài)添加到類里面就可以了?;蛘咛鎿Q為已知的方法。
  2. 備用接受者。如果返回NO,動(dòng)態(tài)轉(zhuǎn)發(fā)第二步
    - (id)forwardingTargetForSelector:(SEL)aSelector,runtime會(huì)繼續(xù)調(diào)用這個(gè)方法,如果實(shí)現(xiàn)了這個(gè)方法并返回一個(gè)非nil的值,則這個(gè)返回值將作為新的消息接收者。如果沒有實(shí)現(xiàn)該方法, go on 第三步。
  3. 完整轉(zhuǎn)發(fā)。- (void)forwardInvocation:(NSInvocation *)anInvocation,如果上一步?jīng)]有處理消息,則runtime走這個(gè)方法。這也是最后一次操作。runtime會(huì)在這個(gè)方法中將消息轉(zhuǎn)發(fā)給其他對(duì)象。不過在執(zhí)行這個(gè)方法前會(huì)首先調(diào)用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector來請(qǐng)求一個(gè)簽名,從而生成一個(gè)NSInvocation,對(duì)消息進(jìn)行完全轉(zhuǎn)發(fā)。

具體事例看本文的demo。

總結(jié):
  1. 我們可以通過2、3來模擬“多繼承”,一個(gè)類中可能會(huì)包含其他的類,當(dāng)這個(gè)類不能實(shí)現(xiàn)該方法時(shí),將方法的接收方改為其他的類,這樣就好像是自己完成了這些操作。
  2. 多繼承: 將不同的功能集成到一個(gè)對(duì)象中,會(huì)讓這個(gè)對(duì)象變的很大、涉及到的東西很多。
  3. 消息轉(zhuǎn)發(fā):將功能分解到不同的小的對(duì)象中,通過一種特定的方式將它們連接起來,并做消息轉(zhuǎn)發(fā)。

常用方法:

// 添加
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types ); 
// cls 需要?jiǎng)討B(tài)決議的類
// name 需要?jiǎng)討B(tài)決議的方法
// imp 需要執(zhí)行的的方法的指針,(函數(shù)指針)
// types 類型 函數(shù)類型(字符串) v:返回值void @:參數(shù)id類型 ":":SEL對(duì)象 i:int d:double

// 獲取實(shí)例
Method class_getInstanceMethod ( Class cls, SEL name ); 
// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name ); 
// 獲取所有的數(shù)組
Method * class_copyMethodList ( Class cls, unsigned int *outCount ); 
//  替代方法的實(shí)現(xiàn)
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types ); 
//  返回方法的具體實(shí)現(xiàn)
IMP class_getMethodImplementation ( Class cls, SEL name ); 
//  類實(shí)例是否響應(yīng)指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel ); 

5. Category分類

/// An opaque type that represents a category.
typedef struct objc_category *Category;

在objc-runtime-new.h中有如下定義

 struct category_t {
     const char *name;                          // 指的是class_name,不是category_name
     classref_t cls;                            // 是擴(kuò)展的類對(duì)象,編譯期間不會(huì)被定義,在runtime階段通過name被指定
     struct method_list_t *instanceMethods;
     struct method_list_t *classMethods;
     struct protocol_list_t *protocols;
     struct property_list_t *instanceProperties;    // 這也是category為什么能添加屬性的原因
     
     method_list_t *methodsForMeta(bool isMeta) {
         if (isMeta) return classMethods;
         else return instanceMethods;
     }
     
     property_list_t *propertiesForMeta(bool isMeta) {
         if (isMeta) return nil; // classProperties;
         else return instanceProperties;
     }
 };

使用場(chǎng)景:

  1. 給現(xiàn)有的類添加方法;
  2. 將一個(gè)類的實(shí)現(xiàn)拆分成多個(gè)獨(dú)立的源文件;
  3. 聲明私有的方法。
// .h文件中添加一個(gè)屬性
@interface People (Add)

@property (nonatomic, copy) NSString *name;

@end

//-------------------------------
// .m中添加方法
static char *PeopleName;

@implementation People (Add)

- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, PeopleName, name, OBJC_ASSOCIATION_COPY);
}

- (NSString *)name
{
    return objc_getAssociatedObject(self, PeopleName);
}

@end

set方法中調(diào)用void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)這個(gè)函數(shù):

object 需要關(guān)聯(lián)屬性的對(duì)象

key 需要關(guān)聯(lián)的key值 一般情況下使用靜態(tài)變量static char,

value 需要與key對(duì)應(yīng)的數(shù)據(jù)

policy 關(guān)聯(lián)策略 一種枚舉值

get方法中調(diào)用id objc_getAssociatedObject(id object, void *key)函數(shù)。

精神病院的考試題應(yīng)該就迎刃而解了。

第一題:

上面的例子中[self class][super class],接受消息的對(duì)象都是son這個(gè)實(shí)例變量。不同的是,self是在本類的方法中尋找,super則告訴編譯器在父類中的方法列表中尋找。(意思就是。self直接在本類中尋找,找不到,則在父類中尋找;而super則是直接在父類中尋找)。

使用clang編譯兩個(gè)NSLog之后

NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_0,
      NSStringFromClass(((Class (*)(id, SEL))
            (void *)objc_msgSend)((id)self, sel_registerName("class"))));
 
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_1,
      NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))
            (void *)objc_msgSendSuper)((__rw_objc_super){ 
                (id)self, (id)class_getSuperclass(objc_getClass("Son")) 
            }, sel_registerName("class"))));

這兩個(gè)方法可以看到:

[self class]---> objc_msgSend

self發(fā)消息 ---> id objc_msgSend(id self, SEL op, ...)

[super class]---> objc_msgSendSuper

super發(fā)消息 ---> id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
第一個(gè)參數(shù)d結(jié)構(gòu)體定義:

struct objc_super {
    __unsafe_unretained id receiver;        // 消息的接收者,就是當(dāng)前的 self 也就是son實(shí)例變量
    __unsafe_unretained Class super_class;  // 當(dāng)前類的父類
};

// 調(diào)用class的方法 函數(shù)實(shí)現(xiàn)

- (Class)class {
    return object_getClass(self);   // 這里的self指向的是son實(shí)例變量
}

看到這里是不是明白了為什么兩個(gè)輸出都是 Son。第一個(gè),調(diào)用[self class]直接在Son中查找,發(fā)現(xiàn)Son中沒有這個(gè)函數(shù),則去 Father中查找,還是沒有,則去NSObject中查找class,有返回?cái)?shù)據(jù). [super class] 則直接從 Father中查找。

如果我們?cè)贔ather這個(gè)類中,重寫- (Class)class方法

- (Class)class {
    return nil;
}

會(huì)輸出什么呢?
nil son

這個(gè)時(shí)候,估計(jì)有人問了,最后都是在 NSObject 中調(diào)用的 class 方法,而在super的結(jié)構(gòu)體中這個(gè)receiver 上面說的是 self,實(shí)例變量son,那是不是super也是從 Son 中開始查找的呢?答案是肯定的不是,看 objc_msgSendSuper 的第二個(gè)參數(shù), class_getSuperclass(), 是從父類中尋找的方法。此時(shí)是先構(gòu)造了super的結(jié)構(gòu)體,已經(jīng)拿到了receiver 指向 self(son實(shí)例變量), 這個(gè)時(shí)候直接從 super 中找方法,不會(huì)理會(huì) Son 中的方法重寫。

第二題

首先,我們需要理解兩個(gè)概念:

isKindOfClass: 判斷當(dāng)前對(duì)象是不是該該類的實(shí)例對(duì)象,或者是繼承自該類的實(shí)例對(duì)象。

isMemberOfClass: 只能判斷當(dāng)前對(duì)象是不是該類的實(shí)例對(duì)象。

+ (Class)class {
    return self;
}
// class的類方法返回本身self。
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

根據(jù)上面的源碼我們看到

isKindOfClass:的實(shí)現(xiàn)方法中比較的是類,如果相等返回YES,否則找 superclass 繼續(xù)比較,直到最后一個(gè) superclass 比較結(jié)束

isMemberOfClass: 則是直接比較當(dāng)前class

所以上面的例子中,第一個(gè)是 YES,其它都是NO。
根據(jù)上圖。我們知道,(id)[NSObject class]這是一個(gè)類對(duì)象,是由NSObject的元類創(chuàng)建的(isa指向元類),這時(shí)比較,兩個(gè)類不一樣,NSObject的元類的superClass指向NSObject本身。所以,第一個(gè)為YES。

第三題:

1是類方法,但是并沒有實(shí)現(xiàn)該類方法。所以在編譯的時(shí)候相當(dāng)于把這段方法的聲明注銷掉了。根據(jù)之前的例子,類對(duì)象尋找方法是在它的元類中找,NSObject的元類沒有此方法,這是在類的分類中添加的一個(gè)方法,所以元類中沒有,NSObject 的元類的父類是NSObject,在NSObject的方法列表中查找,發(fā)現(xiàn)了此方法,存入到元類的方法列表中。輸出結(jié)果。

2是實(shí)例方法,所以在NSObject的方法列表中找,找到,存入緩存,輸出結(jié)果。

這是在NSObject的分類中添加的方法,如果在自己寫的一個(gè)類(People: NSObject)的分類中添加這樣的方法,輸出會(huì)怎么樣?

@interface People : NSObject

@end

@interface People (Sark)
+ (void)foo;
@end
@implementation People (Sark)
- (void)foo {
    NSLog(@"IMP: -[NSObject (Sark) foo]");
}
@end
// 測(cè)試代碼
[People foo];               // 這里會(huì)crash。
[[People new] foo];

之所以會(huì)發(fā)生crahs,是因?yàn)椋?foo方法沒有在People的元類中,People的元類的SuperClass是NSObject的元類,也沒有此法。NSObject的元類的superClass是NSObject,也沒有此方法,所以crash。

第四題

具體的內(nèi)存入棧方式是怎樣,沒有了解過,之后做處理,先留一個(gè)坑,歡迎大神們做解答,謝謝。

可以看原文是怎么解釋的,希望能看懂。
傳送門

總結(jié)

runtime的實(shí)際用例:

Method Swizzling,分類添加屬性、 字典轉(zhuǎn)模型等。 有空再整理。

引用

objc runtime source code

寫的不好,歡迎各位大神指正。喜歡的點(diǎn)贊,加個(gè)關(guān)注,謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容