Runtime

本文為大地瓜原創(chuàng),歡迎知識(shí)共享,轉(zhuǎn)載請(qǐng)注明出處。
雖然你不注明出處我也沒什么精力和你計(jì)較。
作者微信號(hào):christgreenlaw


方法(method)

Objective-C是C的擴(kuò)展,在C的語(yǔ)言層面上加了一定的關(guān)鍵字和語(yǔ)法,其核心就是運(yùn)行時(shí)、消息分發(fā)。
對(duì)于很多語(yǔ)言,編譯器會(huì)執(zhí)行優(yōu)化和錯(cuò)誤檢查,因?yàn)檎{(diào)用關(guān)系很清楚。但是對(duì)于消息分發(fā)來(lái)說(shuō),事情就沒那么簡(jiǎn)單了。發(fā)消息之前,你不用保證這個(gè)對(duì)象能夠處理這個(gè)消息,發(fā)給這個(gè)對(duì)象消息后,對(duì)象可能會(huì)處理消息,也可能轉(zhuǎn)發(fā)給其他的對(duì)象進(jìn)行處理。消息和方法并不是一一對(duì)應(yīng)關(guān)系,一個(gè)對(duì)象可以只用一個(gè)方法來(lái)處理多個(gè)消息。

在OC中,消息是通過objc_msgSend()這個(gè)runtime方法以及其他類似方法進(jìn)行實(shí)現(xiàn)的。該方法需要target, selector以及一些必須的參數(shù)。理論上來(lái)說(shuō),編譯器只是對(duì)發(fā)送消息進(jìn)行了轉(zhuǎn)化,將發(fā)送的消息變成objc_msgSend()來(lái)執(zhí)行了。
可以大致理解成如下的代碼:

[object doSomething: abc withNumber: 10];//轉(zhuǎn)化為下面的這行
objc_msgSend(object, @selector(doSomething:withNumber), abc, 10];

對(duì)象、類、元類(object, class, metaclass)

在OC中,類也是對(duì)象。

什么意思呢?
一般在面向?qū)ο笳Z(yǔ)言中,類和對(duì)象的概念是區(qū)分開來(lái)的,對(duì)象是所屬類的實(shí)例。但OC中,類本身也是對(duì)象,也可以處理消息(所謂的類方法),也就是類方法和實(shí)例方法的區(qū)別。

如果我們糾結(jié)于源代碼的話,可以看到OC中的對(duì)象其實(shí)本質(zhì)是一個(gè)C語(yǔ)言結(jié)構(gòu)體(struct),其中有一個(gè)結(jié)構(gòu)體成員是isa,類型是Class,指向自己的class。

NSObject的interface如下:

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

+ (void)load;

+ (void)initialize;
- (instancetype)init
#if NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER
    NS_DESIGNATED_INITIALIZER
#endif
    ;

+ (instancetype)new OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
+ (instancetype)allocWithZone:(struct _NSZone *)zone OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
- (void)dealloc OBJC_SWIFT_UNAVAILABLE("use 'deinit' to define a de-initializer");

- (void)finalize OBJC_DEPRECATED("Objective-C garbage collection is no longer supported");

- (id)copy;
- (id)mutableCopy;

+ (id)copyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
+ (id)mutableCopyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;

+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
+ (BOOL)conformsToProtocol:(Protocol *)protocol;
- (IMP)methodForSelector:(SEL)aSelector;
+ (IMP)instanceMethodForSelector:(SEL)aSelector;
- (void)doesNotRecognizeSelector:(SEL)aSelector;

- (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");

+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");

- (BOOL)allowsWeakReference UNAVAILABLE_ATTRIBUTE;
- (BOOL)retainWeakReference UNAVAILABLE_ATTRIBUTE;

+ (BOOL)isSubclassOfClass:(Class)aClass;

+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

+ (NSUInteger)hash;
+ (Class)superclass;
+ (Class)class OBJC_SWIFT_UNAVAILABLE("use 'aClass.self' instead");
+ (NSString *)description;
+ (NSString *)debugDescription;

@end

其中Class的定義是:

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

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif

我們看到:

  1. Class是一個(gè)結(jié)構(gòu)體struct objc_class的指針,代表一個(gè)Objective-C類
  2. 結(jié)構(gòu)體struct objc_object代表一個(gè)類的實(shí)例,而這個(gè)結(jié)構(gòu)體內(nèi)部又有Class屬性,其實(shí)這個(gè)Class屬性正是指向了該對(duì)象所屬的類
  3. id其實(shí)是struct objc_object的指針,這也解釋了id為什么是任何對(duì)象的指針了

現(xiàn)在知道了,Class是一個(gè)結(jié)構(gòu)體struct objc_class的指針,代表一個(gè)Objective-C類。那么objc_class的結(jié)構(gòu)是怎樣的呢?
如下:

struct objc_class {
    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;
/* Use `Class` instead of `struct objc_class *` */

由于有typedef,Classstruct objc_class *其實(shí)是一回事,源代碼中的注釋也提示我們,使用Class而不要使用struct objc_class * 。

Class中保存了方法列表(methodLists)以及指向父類的指針(Class _Nullable super_class)。Class也是對(duì)象,也有isa變量,它指向哪里???
答案是metaclass。


此處引用一篇文章:[objc explain]: Classes and metaclasses
其中解釋了類和元類等概念。這里是大地瓜的翻譯。


方法、選擇器、IMP(methods, selectors, IMPs)

我們已經(jīng)知道了,運(yùn)行時(shí)給對(duì)象發(fā)消息,對(duì)象的class保存了方法列表,那消息和方法的映射關(guān)系是怎樣的?方法又是怎么執(zhí)行的?

映射關(guān)系是怎樣的?
class的方法列表其實(shí)是一個(gè)字典,是selectors和IMPs的鍵值對(duì)。IMP就是一個(gè)方法在內(nèi)存中的實(shí)現(xiàn)。但這個(gè)映射關(guān)系是在運(yùn)行時(shí)決定的,而不是編譯時(shí)決定的,所以我們可以做出很多操作。
IMP一般是指向方法的指針,第一個(gè)參數(shù)是self,類型是id,第二個(gè)參數(shù)是cmd,類型為SEL,剩下的是方法的參數(shù)。

- (id)doSomethingWithInt:(int)aInt;//method
id doSomethingWithInt(id self, SEL _cmd, int aInt);//IMP

所以說(shuō)運(yùn)行時(shí)到底能干點(diǎn)什么呢???

創(chuàng)建、修改、自省class和object
class

方法如class_addIvar,class_addMethod, class_addPropertyclass_addProtocol允許重建classes。
class_copyIvarList, class_copyMethodList, class_copyProtocolListclass_copyPropertyList能拿到一個(gè)class的所有內(nèi)容。
class_getClassMethod, class_getClassVariable, class_getInstanceMethod, class_getInstanceVariable, class_getMethodImplementationclass_getProperty返回單個(gè)內(nèi)容。
也有一些通用的自省方法,如class_conformsToProtocol, class_respondsToSelector, class_getSuperclass
最后,你可以使用class_createInstance來(lái)創(chuàng)建一個(gè)object。

ivar

這些方法能讓你得到名字,內(nèi)存地址和Objective-C type encoding。

method

這些方法主要用來(lái)自省,比如method_getName, method_getImplementation, method_getReturnType等等。也有一些修改的方法,包括method_setImplementationmethod_exchangeImplementations。

objc

一旦拿到了object,你就可以對(duì)它做一些自省和修改。你可以get/set ivar, 使用object_copyobject_dispose來(lái)copy和free object的內(nèi)存。最NB的不僅是拿到一個(gè)class,而是可以使用object_setClass來(lái)改變一個(gè)object的class。

property

屬性保存了很大一部分信息。除了拿到名字,你還可以使用property_getAttributes來(lái)發(fā)現(xiàn)property的更多信息,如返回值、是否為atomic、getter/setter名字、是否為dynamic、背后使用的ivar名字、是否為弱引用。

protocol

Protocols有點(diǎn)像classes,但是精簡(jiǎn)版的,運(yùn)行時(shí)的方法是一樣的。你可以獲取method, property, protocol列表, 檢查是否實(shí)現(xiàn)了其他的protocol。

sel

最后我們有一些方法可以處理 selectors,比如獲取名字,注冊(cè)一個(gè)selector等等。


NSClassFromString/NSSelectorFromString

Class stringclass = NSClassFromString(@"NSString");
//于是我們就得到了一個(gè)string class。接下來(lái):
NSString *myString = [stringclass stringWithString:@"Hello World"];

為什么不直接使用NSString???
有些情況下,不知道是否存在某個(gè)類。需要先判斷某個(gè)類是否存在。

Class stringclass = NSClassFromString(@"NSString");
if (stringclass != nil) {
  NSString *str = [stringclass stringWithString:@"hello"];
  NSLog(@"%@", str);
}

另一個(gè)情景,就是根據(jù)不同的輸入使用不同的class或method。比如解析數(shù)據(jù),每個(gè)數(shù)據(jù)項(xiàng)都有不同的數(shù)據(jù)類型:

- (void)parseObject:(id)object {
    for (id data in object) {
        if ([data isKindOfClass:[NSString class]]) {
            NSLog(@"NSString = %@",data);
        } else if ([data isKindOfClass:[NSNumber class]]) {
            NSLog(@"NSNumber = %@",data);
        } else if ([data isKindOfClass:[NSDictionary class]]) {
            NSLog(@"NSDictionary = %@",data);
        }
    }
}
//runtime寫法
- (void)parseObjectDynamic:(id)object { 
    for (id data in object) {
        Class dataClass = [data class];
        NSLog(@"%@ = %@", dataClass,data);
    }
}
//這里打印出來(lái)的dataClass可能并不是常見的類型,而是
2018-01-18 19:44:50.320272+0800 fuck[4975:4579429] __NSCFConstantString = 123
2018-01-18 19:44:50.320302+0800 fuck[4975:4579429] __NSCFNumber = 1
2018-01-18 19:44:50.320343+0800 fuck[4975:4579429] __NSSingleEntryDictionaryI = {
    key = value;
}
//不用擔(dān)心,如果你把__NSCFConstantString和NSString進(jìn)行比較的話,其實(shí)是同一個(gè)類型。

方法欺騙(Method Swizzling)

方法由兩個(gè)部分組成:Selector和IMP。selector是方法的標(biāo)識(shí)符(id),IMP是方法的實(shí)現(xiàn),二者構(gòu)成一個(gè)映射關(guān)系。這樣的映射關(guān)系,一個(gè)好處就是可以進(jìn)行修改。也就是selector與IMP的映射關(guān)系并不是寫死的,多個(gè)selector可以指向同一個(gè)IMP。方法欺騙(method swizzling)可以進(jìn)行兩個(gè)方法的交換。

所以我們?yōu)槭裁匆粨Q方法呢??
首先,OC中的class有兩種擴(kuò)展方式,一個(gè)是繼承,此時(shí)可以重寫方法,也可以調(diào)用父類的實(shí)現(xiàn)。但是,如果你在subclass后重寫了方法,你就再也無(wú)法使用子類調(diào)用原來(lái)的父類實(shí)現(xiàn)了。另一種方式是使用分類(category),但是即使你使用擴(kuò)展,如果你擴(kuò)展中重復(fù)命名了一個(gè)方法,你也只能使用重寫后的方法了,原來(lái)的方法會(huì)在編譯期被覆蓋掉。

method swizzling就可以解決這個(gè)問題。你可以重寫某個(gè)方法,而且你不用繼承,還可以使用原來(lái)的實(shí)現(xiàn)。
這個(gè)做法就是在category中添加一個(gè)方法。通過method_exchangeImplementations這個(gè)運(yùn)行時(shí)方法來(lái)交換實(shí)現(xiàn)。

#import "objc/runtime.h"
#import "NSString+myString.h"

@implementation NSString (myString)

+ (void)load {
    Method description = class_getInstanceMethod(self, @selector(description));
    Method myDescription = class_getInstanceMethod(self, @selector(myDescription));
    method_exchangeImplementations(description, myDescription);
}

- (NSString *)myDescription {
    return @"myDescription";
}

@end
#import <Foundation/Foundation.h>
#import "NSString+myString.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString *str = @"1";
        NSLog(@"%@", [str description]);
        NSLog(@"%@", [str myDescription]);
    }
    return 0;
}
2018-01-20 12:08:10.409142+0800 fuck[6619:5768134] exchange in load
2018-01-20 12:08:10.409635+0800 fuck[6619:5768134] myDescription
2018-01-20 12:08:10.409678+0800 fuck[6619:5768134] 1

上述代碼只是單純的交換了方法的實(shí)現(xiàn)。
那么如何在不覆蓋原來(lái)的方法的情況下,重寫一個(gè)方法,還能調(diào)用原來(lái)的方法呢?

#import  <objc/runtime.h>

@interface NSMutableArray (LoggingAddObject)
- (void)logAddObject:(id)aObject;
@end

@implementation NSMutableArray (LoggingAddObject)

+ (void)load {
    Method addobject = class_getInstanceMethod(self, @selector(addObject:));
    Method logAddobject = class_getInstanceMethod(self, @selector(logAddObject:));
    method_exchangeImplementations(addObject, logAddObject);
}

- (void)logAddObject:(id)aobject {
    [self logAddObject:aObject];
    NSLog(@"Added object %@ to array %@", aObject, self);
}

@end

上述代碼好像包含一個(gè)遞歸,但是,由于我們交換了實(shí)現(xiàn),所以調(diào)用addObject時(shí)會(huì)跳轉(zhuǎn)到logAddingObject中,然后代碼中所包含的遞歸其實(shí)指向了addObject。所以,這樣寫的最終效果就是,先執(zhí)行了addObject然后執(zhí)行我們自定義的部分,在上述例子中,也就是打印出了一行l(wèi)og。
這個(gè)地方并不希望你直接調(diào)用logAddObject,而只是希望你在調(diào)用原來(lái)的addObject時(shí)有多出來(lái)的自定義實(shí)現(xiàn)。


另一篇國(guó)外文章,關(guān)于Method Swizzling。
這是大地瓜對(duì)于此文章的翻譯


動(dòng)態(tài)繼承、交換

運(yùn)行時(shí)可以動(dòng)態(tài)創(chuàng)建class,可以動(dòng)態(tài)創(chuàng)建子類,添加新的方法。
object_setClass(myObject, [MySubclass class]);
內(nèi)部是修改了isa指針,以更改其類型。


這里是KVO的更多解釋。
這里是大地瓜的翻譯。

動(dòng)態(tài)方法處理

以上我們看到了方法交換,已有方法的處理。

那么object無(wú)法處理某個(gè)消息的時(shí)候呢?當(dāng)然就崩了。大多數(shù)情況下我們的應(yīng)用在這種情況就是會(huì)崩的。但是Cocoa和runtime是提供了一些處理方式的。

動(dòng)態(tài)方法處理

一般情況下,處理一個(gè)方法的話,要根據(jù)selector找到對(duì)應(yīng)的實(shí)現(xiàn)然后運(yùn)行。但是有時(shí)候運(yùn)行時(shí)才創(chuàng)建某個(gè)方法, 或者運(yùn)行時(shí)這個(gè)方法才能獲得全部信息。那么需要重寫+resolveInstanceMethod:或者+resolveClassMethod:,最后返回YES。

+ (BOOL)resolveInstanceMethod:(SEL)aSelector {
    if (aSelector == @selector(myDynamicMethod)) {
        class_addMethod(self, aSelector, (IMP)myDynamicIMP, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:aSelector];
}

Core Data用的比較多,平時(shí)不太用得到。
若返回了NO,說(shuō)明沒有在運(yùn)行時(shí)獲得需要的方法。此時(shí)要進(jìn)行消息轉(zhuǎn)發(fā)。

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

  1. 將消息轉(zhuǎn)發(fā)到另一個(gè)可以處理該消息的object
  2. 多個(gè)消息都轉(zhuǎn)發(fā)到同一個(gè)方法上

消息轉(zhuǎn)發(fā)的步驟:
首先運(yùn)行時(shí)調(diào)用-forwadrTargetForSelector:,這個(gè)方法用于將消息傳遞給另一個(gè)對(duì)象。
如果要修改消息,使用-forwardInvocation:,運(yùn)行時(shí)將把消息包裝成NSInvocation,再返回給你(也就是開發(fā)者)處理。最后調(diào)用invokeWithTarget:。

用到消息轉(zhuǎn)發(fā)的主要有這么兩處:
代理(NSProxy)。NSProxy是一個(gè)class,可以轉(zhuǎn)發(fā)消息到另一個(gè)object。NSUndoManager是截取消息再執(zhí)行,并不進(jìn)行轉(zhuǎn)發(fā)。
響應(yīng)鏈:處理事件或行為到響應(yīng)的對(duì)象。某個(gè)消息收到后,若當(dāng)前FirstResponder不能處理該消息,則轉(zhuǎn)發(fā)到下一個(gè)responder,最后找到能處理的對(duì)象進(jìn)行處理,若找不到則報(bào)錯(cuò)。

Block作為Method IMP

一個(gè)IMP是指向方法實(shí)現(xiàn)的指針,前兩個(gè)參數(shù)為object(self)和selector(cmd)。
implementationWithBlock()可以允許我們將block作為IMP使用。

IMP myIMP = imp_implementationWithBlock(^(id _self, NSString *string) {
  NSLog(@"Hello %@", string);
});
class_addMethod([MYClass class], @selector(sayHello:), myIMP, "V@:@");

具體實(shí)現(xiàn)內(nèi)部,參看這篇文章
這里是大地瓜的翻譯

最后編輯于
?著作權(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)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,041評(píng)論 0 9
  • 我們常常會(huì)聽說(shuō) Objective-C 是一門動(dòng)態(tài)語(yǔ)言,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,326評(píng)論 0 7
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,881評(píng)論 33 466
  • 官方源碼下載地址:http://download.csdn.net/detail/liangliang103377...
    有一種再見叫青春閱讀 2,130評(píng)論 2 11
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 828評(píng)論 0 2

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