玩轉iOS開發:iOS開發中的裝逼技術 – RunTime(一)

CainLuo發表於2019-02-22

文章分享至我的個人技術部落格:https://cainluo.github.io/15033286127687.html


RunTimeObjective-C的特性, 如果用別的話來說, 就是因為Objective-C是動態語言, 然後RunTime就是它的執行時機制這些這些, 然後就沒然後了…

但是對於我這些渣渣來說, 個人認為就是一堆C語言寫的東西, 廢話少說了, 直接來擼吧.

轉載宣告:如需要轉載該文章, 請聯絡作者, 並且註明出處, 以及不能擅自修改本文.


objc_msgSend

在我們平常的使用當中, 會經常宣告一個函式, 然後去呼叫, 但裡面做了什麼操作, 我們並不知道, 現在我們來看一段程式碼:

#import "RunTimeModel.h"
#import <objc/message.h>
#import <objc/objc.h>

@implementation RunTimeModel

- (instancetype)init {
    self = [super init];
    
    if (self) {
        
        [self sendMessage];
        [self sendMessage:100];
    }
    
    return self;
}


- (void)sendMessage {
    
    NSLog(@"Message");
}

- (void)sendMessage:(NSInteger)messageCount {
    
    NSLog(@"Message: %ld", messageCount);
}

@end
複製程式碼

這段程式碼, 是我們正常寫的Objective-C程式碼, 我們可以通過終端的命令列, 進行重編:

clang -rewrite-objc RunTimeModel.m
複製程式碼
1

然後就會得到一個RunTimeModel.cpp的檔案, 裡面有90000+行程式碼, 這裡面我們要找到一段東西:

static instancetype _I_RunTimeModel_init(RunTimeModel * self, SEL _cmd) {
    self = ((RunTimeModel *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("RunTimeModel"))}, sel_registerName("init"));

    if (self) {

        ((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("sendMessage"));
        ((void (*)(id, SEL, NSInteger))(void *)objc_msgSend)((id)self, sel_registerName("sendMessage:"), (NSInteger)100);
    }

    return self;
}
複製程式碼
((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("sendMessage"));

((void (*)(id, SEL, NSInteger))(void *)objc_msgSend)((id)self, sel_registerName("sendMessage:"), (NSInteger)100);
複製程式碼
2

這就是我們在.m檔案裡呼叫方法時所進行的操作, 會轉化成訊息傳送的形式進行通訊, objc_msgSend是在#import <objc/message.h>檔案中, 宣告方式:

OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ )
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);
複製程式碼
3

這裡是有兩個基礎引數, 分別是idSEL.


id / SEL

idSEL都是定義在#include <objc/objc.h>中:

typedef struct objc_object *id;

typedef struct objc_selector *SEL;
複製程式碼
  • SEL: 本質就是一個對映到方法的C字串, 我們可以用Objective-C@selector()或者RunTime裡的sel_registerName來獲取一個SEL型別的方法選擇器.
  • id: 它是一個結構體指標型別, 可以指向Objective-C中的任何物件.

objc_object定義:

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};
複製程式碼
4

其實這才是物件本來的面貌, 不要給漂亮的外表給矇騙了咯.

這個結構體就只有一個isa成員變數, 物件是可以通過isa指標找到自己所屬的類, 看到這裡, 我們就不禁疑惑, isa是一個Class的成員變數, 那Class又是啥?


Class

我們在#include <objc/objc.h>中其實是有看到Class的宣告:

typedef struct objc_class *Class;
複製程式碼

但實際上Class是定義在#include <objc/runtime.h>中:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    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;
複製程式碼
5

這裡解釋一下里面的東東:

  • Class: 也有一個isa指標, 指向所屬的meta(元類).
  • super_class: 指向的是它的超類.
  • name: 類名.
  • version: 類的版本資訊.
  • info: 類的詳情資訊.
  • instance_size: 這個類的示例物件的大小.
  • ivars: 指向這個類的成員變數列表, 包括內部的變數.
  • methodLists: 指向這個類的示例方法列表, 它將方法選擇器和方法實現地址聯絡在一起.
  • cache: Runtime會把被呼叫的方法存到cache中, 下次查詢的時候效率更高, 其實就是這個方法第一次被呼叫了之後, 為了以後還會被呼叫的可能而做的快取.
  • protocols: 指向這個類的協議列表.

這裡的methodLists需要注意一下, 它是指向objc_method_list指標的指標, 也就是說可以動態修改methodLists的值來新增成員方法, 我們經常用的Category就是醬紫來的, 也因為這個東西, Category一般是沒辦法新增屬性, 需要我們自己寫寫寫.

看到這裡, 基本的東西我們都差不多瞭解完了, 現在加個補刀, 看看整個執行的過程:

  • Runtime會把我們的方法呼叫轉化為訊息傳送, 也就是我們剛剛說的objc_msgSend, 並且把方法的呼叫者和方法選擇器, 當做引數傳遞過去.
  • 這個時候方法的呼叫者會通過isa指標來找到方法所屬的類, 然後在cache或者methodLists查詢被呼叫的方法, 找到了就跳轉到對應的方法去執行.
    • 如果在類中沒有找到該方法, 就會通過super_class往更上一級的超類中查詢, 查詢到了就執行(如果找不到呢? 這個後面會有補充).

說完這裡, 有些人肯定會很奇怪, 這裡的methodLists裝的是例項方法, 那類方法呢?

其實, 類方法是被儲存在元類中, Class會通過isa指標找到所屬的元類, 這些類方法就是存在這裡了, 具體怎麼獲取類方法, 我們可以看看程式碼:

- (void)getClassMethods {
    
    NSObject *obj = [[NSObject alloc] init];
    
    unsigned int methodCount = 0;
    
    const char *className = class_getName([obj class]);
    
    Class metaClass = objc_getMetaClass(className);
    
    Method *methodList = class_copyMethodList(metaClass, &methodCount);
    
    for (int i = 0; i < methodCount; i++) {
        
        Method method = methodList[i];
        
        SEL selector = method_getName(method);
        
        const char *methodName = sel_getName(selector);
        
        NSLog(@"%s", methodName);
    }
}
複製程式碼

列印出來的結果:vim

2017-08-22 13:24:19.455 1.RunTime[32885:2667202] _installAppearanceSwizzlesForSetter:
2017-08-22 13:24:19.456 1.RunTime[32885:2667202] __accessibilityGuidedAccessStateEnabled
2017-08-22 13:24:19.456 1.RunTime[32885:2667202] __accessibilityGuidedAccessRestrictionStateForIdentifier:
2017-08-22 13:24:19.456 1.RunTime[32885:2667202] __accessibilityRequestGuidedAccessSession:completion:
2017-08-22 13:24:19.456 1.RunTime[32885:2667202] isSelectorExcludedFromWebScript:
2017-08-22 13:24:19.457 1.RunTime[32885:2667202] isKeyExcludedFromWebScript:
2017-08-22 13:24:19.457 1.RunTime[32885:2667202] _webkit_invokeOnMainThread
2017-08-22 13:24:19.457 1.RunTime[32885:2667202] sbs_dataFromObject:
2017-08-22 13:24:19.457 1.RunTime[32885:2667202] sbs_objectFromData:
2017-08-22 13:24:19.458 1.RunTime[32885:2667202] sbs_dataWithValue:
2017-08-22 13:24:19.458 1.RunTime[32885:2667202] sbs_valueFromData:ofType:
2017-08-22 13:24:19.458 1.RunTime[32885:2667202] CA_automaticallyNotifiesObservers:
2017-08-22 13:24:19.459 1.RunTime[32885:2667202] CA_setterForProperty:
2017-08-22 13:24:19.459 1.RunTime[32885:2667202] CA_getterForProperty:
2017-08-22 13:24:19.459 1.RunTime[32885:2667202] CA_encodesPropertyConditionally:type:
2017-08-22 13:24:19.459 1.RunTime[32885:2667202] CA_CAMLPropertyForKey:
2017-08-22 13:24:19.459 1.RunTime[32885:2667202] bs_decodedFromData:
2017-08-22 13:24:19.460 1.RunTime[32885:2667202] bs_objectFromData:
2017-08-22 13:24:19.460 1.RunTime[32885:2667202] bs_secureObjectFromData:ofClass:
2017-08-22 13:24:19.460 1.RunTime[32885:2667202] bs_secureObjectFromData:ofClasses:
2017-08-22 13:24:19.460 1.RunTime[32885:2667202] bs_synchronousWrapper:timeout:
2017-08-22 13:24:19.460 1.RunTime[32885:2667202] bs_secureDataFromObject:
2017-08-22 13:24:19.461 1.RunTime[32885:2667202] bs_dataFromObject:
2017-08-22 13:24:19.461 1.RunTime[32885:2667202] bs_secureDecodedFromData:withAdditionalClasses:
2017-08-22 13:24:19.461 1.RunTime[32885:2667202] bs_secureDecodedFromData:
2017-08-22 13:24:19.506 1.RunTime[32885:2667202] replacementObjectForPortCoder:
2017-08-22 13:24:19.506 1.RunTime[32885:2667202] instanceMethodDescriptionForSelector:
2017-08-22 13:24:19.507 1.RunTime[32885:2667202] methodDescriptionForSelector:
2017-08-22 13:24:19.507 1.RunTime[32885:2667202] _localClassNameForClass
2017-08-22 13:24:19.507 1.RunTime[32885:2667202] cancelPreviousPerformRequestsWithTarget:selector:object:
2017-08-22 13:24:19.507 1.RunTime[32885:2667202] cancelPreviousPerformRequestsWithTarget:
2017-08-22 13:24:19.507 1.RunTime[32885:2667202] setVersion:
2017-08-22 13:24:19.508 1.RunTime[32885:2667202] implementsSelector:
2017-08-22 13:24:19.508 1.RunTime[32885:2667202] instancesImplementSelector:
2017-08-22 13:24:19.508 1.RunTime[32885:2667202] load
2017-08-22 13:24:19.508 1.RunTime[32885:2667202] version
2017-08-22 13:24:19.509 1.RunTime[32885:2667202] classForKeyedUnarchiver
2017-08-22 13:24:19.509 1.RunTime[32885:2667202] classFallbacksForKeyedArchiver
2017-08-22 13:24:19.509 1.RunTime[32885:2667202] _shouldAddObservationForwardersForKey:
2017-08-22 13:24:19.509 1.RunTime[32885:2667202] setKeys:triggerChangeNotificationsForDependentKey:
2017-08-22 13:24:19.510 1.RunTime[32885:2667202] automaticallyNotifiesObserversForKey:
2017-08-22 13:24:19.510 1.RunTime[32885:2667202] _keysForValuesAffectingValueForKey:
2017-08-22 13:24:19.510 1.RunTime[32885:2667202] keyPathsForValuesAffectingValueForKey:
2017-08-22 13:24:19.510 1.RunTime[32885:2667202] _createValueGetterWithContainerClassID:key:
2017-08-22 13:24:19.511 1.RunTime[32885:2667202] _createValueSetterWithContainerClassID:key:
2017-08-22 13:24:19.511 1.RunTime[32885:2667202] _createMutableOrderedSetValueGetterWithContainerClassID:key:
2017-08-22 13:24:19.511 1.RunTime[32885:2667202] _createMutableSetValueGetterWithContainerClassID:key:
2017-08-22 13:24:19.511 1.RunTime[32885:2667202] _createValuePrimitiveGetterWithContainerClassID:key:
2017-08-22 13:24:19.512 1.RunTime[32885:2667202] _createValuePrimitiveSetterWithContainerClassID:key:
2017-08-22 13:24:19.512 1.RunTime[32885:2667202] _createOtherValueGetterWithContainerClassID:key:
2017-08-22 13:24:19.512 1.RunTime[32885:2667202] _createOtherValueSetterWithContainerClassID:key:
2017-08-22 13:24:19.512 1.RunTime[32885:2667202] _createMutableArrayValueGetterWithContainerClassID:key:
2017-08-22 13:24:19.513 1.RunTime[32885:2667202] accessInstanceVariablesDirectly
2017-08-22 13:24:19.513 1.RunTime[32885:2667202] instanceMethodSignatureForSelector:
2017-08-22 13:24:19.513 1.RunTime[32885:2667202] load
2017-08-22 13:24:19.513 1.RunTime[32885:2667202] dealloc
2017-08-22 13:24:19.514 1.RunTime[32885:2667202] doesNotRecognizeSelector:
2017-08-22 13:24:19.514 1.RunTime[32885:2667202] description
2017-08-22 13:24:19.514 1.RunTime[32885:2667202] methodSignatureForSelector:
2017-08-22 13:24:19.514 1.RunTime[32885:2667202] __allocWithZone_OA:
2017-08-22 13:24:19.515 1.RunTime[32885:2667202] _copyDescription
2017-08-22 13:24:19.515 1.RunTime[32885:2667202] init
2017-08-22 13:24:19.515 1.RunTime[32885:2667202] zone
2017-08-22 13:24:19.515 1.RunTime[32885:2667202] instancesRespondToSelector:
2017-08-22 13:24:19.516 1.RunTime[32885:2667202] instanceMethodForSelector:
2017-08-22 13:24:19.516 1.RunTime[32885:2667202] isAncestorOfObject:
2017-08-22 13:24:19.516 1.RunTime[32885:2667202] instanceMethodSignatureForSelector:
2017-08-22 13:24:19.516 1.RunTime[32885:2667202] load
2017-08-22 13:24:19.517 1.RunTime[32885:2667202] initialize
2017-08-22 13:24:19.517 1.RunTime[32885:2667202] resolveInstanceMethod:
2017-08-22 13:24:19.517 1.RunTime[32885:2667202] resolveClassMethod:
2017-08-22 13:24:19.517 1.RunTime[32885:2667202] retain
2017-08-22 13:24:19.518 1.RunTime[32885:2667202] release
2017-08-22 13:24:19.518 1.RunTime[32885:2667202] autorelease
2017-08-22 13:24:19.518 1.RunTime[32885:2667202] retainCount
2017-08-22 13:24:19.518 1.RunTime[32885:2667202] alloc
2017-08-22 13:24:19.519 1.RunTime[32885:2667202] allocWithZone:
2017-08-22 13:24:19.519 1.RunTime[32885:2667202] dealloc
2017-08-22 13:24:19.519 1.RunTime[32885:2667202] copy
2017-08-22 13:24:19.519 1.RunTime[32885:2667202] new
2017-08-22 13:24:19.520 1.RunTime[32885:2667202] forwardInvocation:
2017-08-22 13:24:19.520 1.RunTime[32885:2667202] _tryRetain
2017-08-22 13:24:19.520 1.RunTime[32885:2667202] _isDeallocating
2017-08-22 13:24:19.520 1.RunTime[32885:2667202] retainWeakReference
2017-08-22 13:24:19.521 1.RunTime[32885:2667202] allowsWeakReference
2017-08-22 13:24:19.521 1.RunTime[32885:2667202] copyWithZone:
2017-08-22 13:24:19.521 1.RunTime[32885:2667202] mutableCopyWithZone:
2017-08-22 13:24:19.522 1.RunTime[32885:2667202] doesNotRecognizeSelector:
2017-08-22 13:24:19.522 1.RunTime[32885:2667202] description
2017-08-22 13:24:19.522 1.RunTime[32885:2667202] isFault
2017-08-22 13:24:19.522 1.RunTime[32885:2667202] mutableCopy
2017-08-22 13:24:19.523 1.RunTime[32885:2667202] performSelector:withObject:
2017-08-22 13:24:19.523 1.RunTime[32885:2667202] isMemberOfClass:
2017-08-22 13:24:19.524 1.RunTime[32885:2667202] hash
2017-08-22 13:24:19.524 1.RunTime[32885:2667202] isEqual:
2017-08-22 13:24:19.524 1.RunTime[32885:2667202] self
2017-08-22 13:24:19.524 1.RunTime[32885:2667202] performSelector:
2017-08-22 13:24:19.525 1.RunTime[32885:2667202] conformsToProtocol:
2017-08-22 13:24:19.525 1.RunTime[32885:2667202] methodSignatureForSelector:
2017-08-22 13:24:19.525 1.RunTime[32885:2667202] forwardingTargetForSelector:
2017-08-22 13:24:19.525 1.RunTime[32885:2667202] methodForSelector:
2017-08-22 13:24:19.526 1.RunTime[32885:2667202] performSelector:withObject:withObject:
2017-08-22 13:24:19.526 1.RunTime[32885:2667202] superclass
2017-08-22 13:24:19.526 1.RunTime[32885:2667202] isSubclassOfClass:
2017-08-22 13:24:19.527 1.RunTime[32885:2667202] class
2017-08-22 13:24:19.527 1.RunTime[32885:2667202] init
2017-08-22 13:24:19.528 1.RunTime[32885:2667202] debugDescription
2017-08-22 13:24:19.528 1.RunTime[32885:2667202] isProxy
2017-08-22 13:24:19.529 1.RunTime[32885:2667202] respondsToSelector:
2017-08-22 13:24:19.529 1.RunTime[32885:2667202] isKindOfClass:
複製程式碼

isa的補充

這裡順帶補充一下isa指標的指向:

  • isa指標指向的是元類.
  • 元類isa指標指向的是根類.
  • 如果根類或者是元類的超類是NSObject, 那麼就是指向自己.
  • NSObject是沒有超類的.
6

工程地址

專案地址: https://github.com/CainRun/iOS-Project-Example/tree/master/RunTime/玩轉iOS開發:iOS中的RunTime(一)

注意: RunTimeModel.cpp在目錄中, 我並沒有放到工程裡.


最後

碼字很費腦, 看官賞點飯錢可好
微信
支付寶

相關文章