美文网首页
OC 常用 Runtime 操作整理

OC 常用 Runtime 操作整理

作者: 扬仔360 | 来源:发表于2020-08-30 23:05 被阅读0次

    本篇英文名叫 OC Runtime In Action:CEO Cook Actually Used In Cooking,翻译过来的意思是开发人员实际开发的时候使用的 runtime 操作。进行积累整理。

    隶属于我的第二个大系列 CWCCooking With Cook,翻译过来的中文意思就是 作为一个长期热爱苹果的苹果开发者,陪着水果公司一起积累和成长。


    首先简单罗列一下大部分的 runtime 的 API 方法

    0. runtime方法概要

    NSObject部分:

    // 根据 instance 对象或者类名获得一个 class 对象
    - (Class)class
    + (Class)class
    // 判断当前 instance/class 对象的 isa 指向是不是 class/meta-class 对象或者它的子类类型
    - (BOOL)isKindOfClass:(Class)cls
    + (BOOL)isKindOfClass:(Class)cls
    // 判断当前 instance/class 对象的 isa 指向是不是 class/meta-class 对象类型
    - (BOOL)isMemberOfClass:(Class)cls
    + (BOOL)isMemberOfClass:(Class)cls
    // 判断对象是否可以接收特定消息
    - (BOOL)respondsToSelector:(SEL)sel
    + (BOOL)respondsToSelector:(SEL)sel
    // 判断对象是否实现了特定协议中定义的方法
    - (BOOL)conformsToProtocol:(Protocol *)protocol
    + (BOOL)conformsToProtocol:(Protocol *)protocol
    // 可以根据一个 SEL,得到该方法的 IMP
    - (IMP)methodForSelector:(SEL)sel
    + (IMP)methodForSelector:(SEL)sel
    

    Class 部分:

    // 动态创建一对类和元类(参数:父类,类名,额外的内存空间)
    Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
    // 注册一对类和元类(要在类注册之前添加成员变量)
    void objc_registerClassPair(Class cls) 
    // 销毁一对类和元类
    void objc_disposeClassPair(Class cls)
    // 获取 isa 指向的 Class
    Class object_getClass(id obj)
    // 设置 isa 指向的 Class
    Class object_setClass(id obj, Class cls)
    // 判断一个 OC 对象是否为 Class
    BOOL object_isClass(id obj)
    // 判断一个 Class 是否为元类
    BOOL class_isMetaClass(Class cls)
    // 获取父类
    Class class_getSuperclass(Class cls)
    

    Property iVar 相关:

    // 获取一个实例变量信息
    Ivar class_getInstanceVariable(Class cls, const char *name)
    // 拷贝实例变量列表(最后需要调用 free 释放)
    Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
    // 设置和获取成员变量的值
    void object_setIvar(id obj, Ivar ivar, id value)
    id object_getIvar(id obj, Ivar ivar)
    // 动态添加成员变量(已经注册的类是不能动态添加成员变量的)
    BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
    // 获取成员变量的相关信息
    const char *ivar_getName(Ivar v)
    const char *ivar_getTypeEncoding(Ivar v)
    

    属性相关:

    // 获取一个属性
    objc_property_t class_getProperty(Class cls, const char *name)
    // 拷贝属性列表(最后需要调用 free 释放)
    objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
    // 动态添加属性
    BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
    // 动态替换属性
    void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
    // 获取属性的一些信息
    const char *property_getName(objc_property_t property)
    const char *property_getAttributes(objc_property_t property)
    

    Method 相关:

    // 获得一个实例方法、类方法
    Method class_getInstanceMethod(Class cls, SEL name)
    Method class_getClassMethod(Class cls, SEL name)
    // 方法实现相关操作
    IMP class_getMethodImplementation(Class cls, SEL name) 
    IMP method_setImplementation(Method m, IMP imp)
    void method_exchangeImplementations(Method m1, Method m2) 
    // 拷贝方法列表(最后需要调用 free 释放)
    Method *class_copyMethodList(Class cls, unsigned int *outCount)
    // 动态添加方法
    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
    // 动态替换方法
    IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
    // 获取方法的相关信息(带有 copy 的需要调用 free 去释放)
    SEL method_getName(Method m)
    IMP method_getImplementation(Method m)
    const char *method_getTypeEncoding(Method m)
    unsigned int method_getNumberOfArguments(Method m)
    char *method_copyReturnType(Method m)
    char *method_copyArgumentType(Method m, unsigned int index)
    // 选择器相关
    const char *sel_getName(SEL sel)
    SEL sel_registerName(const char *str)
    // 用 block 作为方法实现
    IMP imp_implementationWithBlock(id block)
    id imp_getBlock(IMP anImp)
    BOOL imp_removeBlock(IMP anImp)
    

    关联对象相关:

    // 添加关联对象
    void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)
    // 获得关联对象
    id objc_getAssociatedObject(id object, const void * key)
    // 移除指定 object 的所有关联对象
    void objc_removeAssociatedObjects(id object)
    

    1. 接触 Runtime 的方式

    正常开发的时候,我们通过三个渠道来接触到runtime

    1. OC 层面, eg: @selector
    2. Runtime API, eg: objc-getclass() 这些方法,msgSend()setassosiciatedObject()
    3. NSObject 相关的 API, eg:

    2. 在lldb中打印 objc_class isa 的指向类

    这点虽然不是调用runtime,但是涉及到了isa指针的相关知识。

    直接通过位运算与上掩码来取类指针


    image

    这里与操作后面的值就是 ISAMASK,根据操作平台不同。

    3. 通过 @encode 打印所有的type encodings

    https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100

    这个还是有用的,因为其实 method_t 里面满共就三四个值,一个name,一个imp实现指针,一个就是方法的 TypeEncodings

    #pragma mark - 各种类型编码
    void logAllTypeCodings() {
        NSLog(@"char --> %s",@encode(char));
        NSLog(@"int --> %s",@encode(int));
        NSLog(@"short --> %s",@encode(short));
        NSLog(@"long --> %s",@encode(long));
        NSLog(@"long long --> %s",@encode(long long));
        NSLog(@"unsigned char --> %s",@encode(unsigned char));
        NSLog(@"unsigned int --> %s",@encode(unsigned int));
        NSLog(@"unsigned short --> %s",@encode(unsigned short));
        NSLog(@"unsigned long --> %s",@encode(unsigned long long));
        NSLog(@"float --> %s",@encode(float));
        NSLog(@"bool --> %s",@encode(bool));
        NSLog(@"void --> %s",@encode(void));
        NSLog(@"char * --> %s",@encode(char *));
        NSLog(@"id --> %s",@encode(id));
        NSLog(@"Class --> %s",@encode(Class));
        NSLog(@"SEL --> %s",@encode(SEL));
        int array[] = {1,2,3};
        NSLog(@"int[] --> %s",@encode(typeof(array)));
        typedef struct person{
            char *name;
            int age;
        }Person;
        NSLog(@"struct --> %s",@encode(Person));
        
        typedef union union_type{
            char *name;
            int a;
        }Union;
        NSLog(@"union --> %s",@encode(Union));
    
        int a = 2;
        int *b = {&a};
        NSLog(@"int[] --> %s",@encode(typeof(b)));
    }
    
    
    2020-11-01 15:21:03.434385+0800 001-类的属性与变量[34325:1798027] char --> c
    2020-11-01 15:21:03.434742+0800 001-类的属性与变量[34325:1798027] int --> i
    2020-11-01 15:21:03.434775+0800 001-类的属性与变量[34325:1798027] short --> s
    2020-11-01 15:21:03.434796+0800 001-类的属性与变量[34325:1798027] long --> q
    2020-11-01 15:21:03.434815+0800 001-类的属性与变量[34325:1798027] long long --> q
    2020-11-01 15:21:03.434834+0800 001-类的属性与变量[34325:1798027] unsigned char --> C
    2020-11-01 15:21:03.434855+0800 001-类的属性与变量[34325:1798027] unsigned int --> I
    2020-11-01 15:21:03.434873+0800 001-类的属性与变量[34325:1798027] unsigned short --> S
    2020-11-01 15:21:03.434891+0800 001-类的属性与变量[34325:1798027] unsigned long --> Q
    2020-11-01 15:21:03.434910+0800 001-类的属性与变量[34325:1798027] float --> f
    2020-11-01 15:21:03.434957+0800 001-类的属性与变量[34325:1798027] bool --> B
    2020-11-01 15:21:03.434998+0800 001-类的属性与变量[34325:1798027] void --> v
    2020-11-01 15:21:03.435051+0800 001-类的属性与变量[34325:1798027] char * --> *
    2020-11-01 15:21:03.733132+0800 001-类的属性与变量[34325:1798027] id --> @
    2020-11-01 15:21:03.733181+0800 001-类的属性与变量[34325:1798027] Class --> #
    2020-11-01 15:21:03.733214+0800 001-类的属性与变量[34325:1798027] SEL --> :
    2020-11-01 15:21:03.733242+0800 001-类的属性与变量[34325:1798027] int[] --> [3i]
    2020-11-01 15:21:03.733270+0800 001-类的属性与变量[34325:1798027] struct --> {person=*I}
    2020-11-01 15:21:03.733297+0800 001-类的属性与变量[34325:1798027] union --> (union_type=*i)
    2020-11-01 15:21:03.733324+0800 001-类的属性与变量[34325:1798027] int[] --> ^i
    

    4. 打印所有的类的属性

    #import <objc/runtime.h>
    
    #ifdef DEBUG
    #define LGLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
    #else
    #define LGLog(format, ...);
    #endif
    
    void getObjc_copyIvar_copyProperies(Class pClass){
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList(pClass, &count);
        for (unsigned int i=0; i < count; i++) {
            Ivar const ivar = ivars[I];
            //获取实例变量名
            const char*cName = ivar_getName(ivar);
            NSString *ivarName = [NSString stringWithUTF8String:cName];
            LGLog(@"class_copyIvarList:%@",ivarName);
        }
        free(ivars);
    
        unsigned int pCount = 0;
        objc_property_t *properties = class_copyPropertyList(pClass, &pCount);
        for (unsigned int i=0; i < pCount; i++) {
            objc_property_t const property = properties[I];
            //获取属性名
            NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
            //获取属性值
            LGLog(@"class_copyProperiesList:%@",propertyName);
        }
        free(properties);
    }
    

    5. 查找 方法

    不像上一点那么通用,需要手动查是否包含该方法

    #import <objc/runtime.h>
    
    #ifdef DEBUG
    #define getLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
    #else
    #define getLog(format, ...);
    #endif
    
    void getObjc_copyMethodList(Class pClass) {
        unsigned int count = 0;
        Method *methods = class_copyMethodList(pClass, &count);
        for (unsigned int i=0; i < count; i++) {
            Method const method = methods[I];
            //获取方法名
            NSString *key = NSStringFromSelector(method_getName(method));
            
            getLog(@"Method, name: %@", key);
        }
        free(methods);
    }
    
    void getInstanceMethod_classToMetaclass(Class pClass) {
        
        const char *className = class_getName(pClass);
        Class metaClass = objc_getMetaClass(className);
        
        Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
        Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
    
        Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
        Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
        
        getLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
    }
    
    void getClassMethod_classToMetaclass(Class pClass) {
        
        const char *className = class_getName(pClass);
        Class metaClass = objc_getMetaClass(className);
        
        Method method1 = class_getClassMethod(pClass, @selector(sayHello));
        Method method2 = class_getClassMethod(metaClass, @selector(sayHello));
    
        Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
        // 元类 为什么有 sayHappy 类方法 0 1
        //
        Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
        
        getLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
    }
    
    void getIMP_classToMetaclass(Class pClass) {
        
        const char *className = class_getName(pClass);
        Class metaClass = objc_getMetaClass(className);
    
        // - (void)sayHello;
        // + (void)sayHappy;
        IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
        IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));
    
        IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
        IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));
    
        NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
        NSLog(@"%s",__func__);
    }
    
    

    6. print all properties

    //- (void)printClassAllProperties:(Class)cls {
    void printClassAllProperties(Class cls) {
        unsigned int outCount = 0;
        objc_property_t *properties = class_copyPropertyList(cls, &outCount);
        NSMutableArray *propertiesAry = [NSMutableArray arrayWithCapacity:outCount];
        for (int i = 0; i<outCount; i++) {
            // objc property t 属性
            objc_property_t property = properties[I];
            // 获取属性名称 C字符串
            const char *cName = property_getName(property);
            // 转换成 OC 字符串
            NSString *name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding];
            [propertiesAry addObject:name];
            NSLog(@"属性: %@", name);
        }
    }
    

    另外这里有一个另外的函数,读取property 的 attribute,property_getAttributes(property)

    打印出来会比如这种:T@"NSString",C,N,V_name
    T:Type
    C:copy
    N:nonatomic
    V:variable

    7. 假装的本地 struct 逻辑结构

    研究 bucket 的时候用到的。

    因为除非是源码环境,否则业务环境工程我们拿不到源码的结构体,所以怎么办,只能自己写,假装我们有,在业务环境把源码环境骗过来:

    typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
    
    struct lg_bucket_t {
        SEL _sel;
        IMP _imp;
    };
    
    struct lg_cache_t {
        struct lg_bucket_t * _buckets;
        mask_t _mask;
        uint16_t _flags;
        uint16_t _occupied;
    };
    
    struct lg_class_data_bits_t {
        uintptr_t bits;
    };
    
    struct lg_objc_class {
        Class ISA;
        Class superclass;
        struct lg_cache_t cache;             // formerly cache pointer and vtable
        struct lg_class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    };
    
    // 用法
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSArray * ary = [[NSArray alloc] init];
            struct lg_objc_class *lg_pClass = (__bridge struct lg_objc_class *)(ary);
            NSLog(@"%hu - %u",lg_pClass->cache._occupied,lg_pClass->cache._mask);
            for (mask_t i = 0; i<lg_pClass->cache._mask; i++) {
                // 打印获取的 bucket
                struct lg_bucket_t bucket = lg_pClass->cache._buckets[I];
                NSLog(@"%@ - %p",NSStringFromSelector(bucket._sel),bucket._imp);
            }
        }
        return 0;
    }
    
    

    8. instrumentObjcMessageSends

    https://stackoverflow.com/questions/10749452/can-the-messages-sent-to-an-object-in-objective-c-be-monitored-or-printed-out/10750398#10750398

    iOS, this works only on Simulator.

    calling:

    (void)instrumentObjcMessageSends(YES);
    

    When I need to start logging all messages and then:

    (void)instrumentObjcMessageSends(NO);
    

    Don't forget to add #import <objc/runtime.h>.

    When I don't need it anymore. The annoying thing is that the log is created under /tmp/msgSends- and this means that you have to open the terminal and use tail to see it in a readable way.

    What is printed is something like this:

    - CustomTableViewController UIViewController _parentModalViewController
    - CustomTableViewController UIViewController isPerformingModalTransition
    - CustomTableViewController UIViewController setInAnimatedVCTransition:
    - CustomTableViewController UIViewController viewWillMoveToWindow:
    - CustomTableViewController UIViewController isPerformingModalTransition
    - CustomTableViewController UIViewController parentViewController
    - CustomTableViewController UIViewController _popoverController
    - CustomTableViewController UIViewController _didSelfOrAncestorBeginAppearanceTransition
    - CustomTableViewController UIViewController parentViewController
    - CustomTableViewController UIViewController __viewWillDisappear:
    - CustomTableViewController UIViewController _setViewAppearState:isAnimating:
    - CustomTableViewController UIViewController automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers
    

    这个方法的源码如下:

    void instrumentObjcMessageSends(BOOL flag)
    {
        bool enable = flag;
    
        // Shortcut NOP
        if (objcMsgLogEnabled == enable)
            return;
    
        // If enabling, flush all method caches so we get some traces
        if (enable)
            _objc_flush_caches(Nil);
    
        // Sync our log file
        if (objcMsgLogFD != -1)
            fsync (objcMsgLogFD);
    
        objcMsgLogEnabled = enable;
    }
    

    9. 一些常见API的区别

    iOS中内省的几个方法?

    isMemberOfClass //对象是否是某个类型的对象
    isKindOfClass //对象是否是某个类型或某个类型子类的对象
    isSubclassOfClass //某个类对象是否是另一个类型的子类
    isAncestorOfObject //某个类对象是否是另一个类型的父类
    respondsToSelector //是否能响应某个方法
    conformsToProtocol //是否遵循某个协议

    10. 打印类和子类 + 打印所有方法

    #pragma mark - 遍历方法-ivar-property
    - (void)printClassAllMethod:(Class)cls{
        unsigned int count = 0;
        Method *methodList = class_copyMethodList(cls, &count);
        for (int i = 0; i<count; i++) {
            Method method = methodList[I];
            SEL sel = method_getName(method);
            IMP imp = class_getMethodImplementation(cls, sel);
            NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
        }
        free(methodList);
    }
    
    #pragma mark - 遍历类以及子类
    - (void)printClasses:(Class)cls{
        // 注册类的总数
        int count = objc_getClassList(NULL, 0);
        // 创建一个数组, 其中包含给定对象
        NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
        // 获取所有已注册的类
        Class* classes = (Class*)malloc(sizeof(Class)*count);
        objc_getClassList(classes, count);
        for (int i = 0; i<count; i++) {
            if (cls == class_getSuperclass(classes[i])) {
                [mArray addObject:classes[I]];
            }
        }
        free(classes);
        NSLog(@"classes = %@", mArray);
    }
    

    参考链接:

    Apple objc4 源码
    Apple OC Runtime Document
    GUNStep

    相关文章

      网友评论

          本文标题:OC 常用 Runtime 操作整理

          本文链接:https://www.haomeiwen.com/subject/eksysktx.html