玩转iOS开发:iOS开发中的装逼技术 - RunTime(一)

2,654 阅读14分钟

文章分享至我的个人技术博客: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在目录中, 我并没有放到工程里.


最后

码字很费脑, 看官赏点饭钱可好

微信

支付宝