美文网首页OC进化iOS从入门到放弃iOS 技术文档收录
runtime变奏曲,那些藏在runtime中的接口(二)

runtime变奏曲,那些藏在runtime中的接口(二)

作者: 天口三水羊 | 来源:发表于2017-02-16 15:38 被阅读623次

    学习进度:

    一、回顾与概要

    前一章内容中,我们介绍了NSObject里面的常用runtime方法、runtime库中的主要数据结构和runtime库中的class相关方法。runtime库中除却class相关方法还有很多实用的方法,比如object相关、ivar相关、property相关、protocol相关等等等等。下面我将介绍runtime库中所有其它的public api。

    二、Class外的Runtime API

    前提

    @protocol AProtocol <NSObject>
    - (void)aProtocolMethod;
    @end
    @interface B : NSObject
    - (void)bTest;
    @end
    @implementation B
    - (void)bTest {
        NSLog(@"bTest");
    }
    @end
    @interface A : NSObject <AProtocol> {
        NSString *strA;
    }
    @property (nonatomic, assign) NSUInteger uintA;
    - (void)test;
    @end
    @implementation A
    - (void)test {
        NSLog(@"%lu", (unsigned long)_uintA);
    }
    @end
    void aNewMethod() {
        NSLog(@"aNewMethod");
    }
    

    1、object相关

    代码

    // 获取类实例的大小
    size_t size = class_getInstanceSize([A class]);
    NSLog(@"%zu", size);    // a
    // 动态创建一个实例
    A *a = class_createInstance([A class], size);
    a.uintA = 1;
    NSLog(@"%d", a.uintA);  // b
    // 销毁一个实例,但是并没有回收内存
    objc_destructInstance(a);
    // 回收内存
    object_dispose(a);
    size_t allocSize = 2 * size;
    uintptr_t ptr = (uintptr_t)calloc(allocSize, 1);
    // 构造一个实例并将其存入ptr
    a = objc_constructInstance([A class], &ptr);
    // 创建一个对象的copy,肯定是深copy,会开辟新空间
    NSString *str = @"1";
    NSString *str1 = object_copy(str, size);
    NSLog(@"%@ %p %p", str1, str1, str); // c
    // 动态为实例的成员变量赋值
    NSString *b = @"111";
    object_setInstanceVariable(a, "strA", b);
    // 获取实例的某个成员变量的值
    NSString *b1 = nil;
    object_getInstanceVariable(a, "strA", (void **)&b1);
    NSLog(@"%@", b1);   // d
    // 指向a中额外的空间,也就是多余的,没测试
    object_getIndexedIvars(a);
    //  获取实例的某个成员变量的值,比object_getInstanceVariable快
    unsigned int count;
    Ivar *intIvar = class_copyIvarList([A class], &count);
    id ivarValue =object_getIvar(a, intIvar[0]);
    NSLog(@"%@", ivarValue);    // e
    // 动态为实例的成员变量赋值,比object_setInstanceVariable快
    object_setIvar(a, intIvar[0], @"222");
    NSLog(@"%@", ivarValue);    // f
    // 获取实例的class名称
    const char *name = object_getClassName(a);
    NSLog(@"%s", name); // g
    // 获取实例的class
    object_getClass(a);
    // 将实例的isa指向一个新的class
    object_setClass(a, [B class]);
    

    输出

    2017-02-15 17:11:31.723 block[48944:8475706] 12        // a
    2017-02-15 17:11:31.728 block[48944:8475706] 1          // b
    2017-02-15 17:11:31.729 block[48944:8475706] 1 0x7c035a80 0x64194        // c
    2017-02-15 17:11:31.730 block[48944:8475706] 111        // d
    2017-02-15 17:28:13.255 block[50522:8493921] 111  // e
    2017-02-15 17:28:13.255 block[50522:8493921] 111  // f 
    2017-02-15 17:28:13.256 block[50522:8493921] A  // g
    

    2、ivar相关

    代码

    // 获取ivar名称
    unsigned int count;
    Ivar *ivars = class_copyIvarList([A class], &count);
    const char *ivarName = ivar_getName(ivars[0]);
    NSLog(@"%s", ivarName); // a
    // 获取ivar类型编码
    const char *encoding = ivar_getTypeEncoding(ivars[0]);
    NSLog(@"%s", encoding); // b
    // 获取某个成员变量的偏移量
    ptrdiff_t offset = ivar_getOffset(ivars[0]);
    NSLog(@"%td", offset);  // c
    

    输出

    2017-02-15 17:32:51.193 block[50992:8500649] strA  // a
    2017-02-15 17:32:51.197 block[50992:8500649] @"NSString"  // b
    2017-02-15 17:32:51.197 block[50992:8500649] 4  // c
    

    3、property相关

    代码

    // 获取某个property的name
    const char *prpertyName = property_getName(class_getProperty([A class], "uintA"));
    NSLog(@"%s", prpertyName);  // a
    // 获取property描述符,比如修饰词、类型、名字
    const char *propertyAttributes = property_getAttributes(class_getProperty([A class], "uintA"));
    NSLog(@"%s", propertyAttributes);   // b
    // 获取property某个描述符的value
    const char *value = property_copyAttributeValue(class_getProperty([A class], "uintA"), "T");
    NSLog(@"%s", value);    // c
    // 获取property描述符的列表
    unsigned int attriCount;
    objc_property_attribute_t *attrs = property_copyAttributeList(class_getProperty([A class], "uintA"), &attriCount);
    NSLog(@"%s", attrs[0]); // d
    

    输出

    2017-02-15 17:39:54.064 block[51662:8507683] uintA
    2017-02-15 17:39:54.069 block[51662:8507683] TI,N,V_uintA
    2017-02-15 17:39:54.069 block[51662:8507683] I
    

    4、关联property相关

    代码

    // 绑定属性,不会添加到class的propertylist
    static void *kAssociatedObjectKey = &kAssociatedObjectKey;
    NSString *assStr = @"assStr";
    unsigned int count;
    class_copyPropertyList([A class], &count);
    objc_setAssociatedObject([A class], kAssociatedObjectKey, assStr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    class_copyPropertyList([A class], &count);
    // 获取之前绑定的属性
    objc_getAssociatedObject([A class], kAssociatedObjectKey);
    NSLog(@"%@", assStr);   // a
    A *assA = [A new];
    assA.uintA = 10;
    // 移除所有绑定的属性,不会影响class自身的property
    objc_removeAssociatedObjects(assA);
    NSLog(@"%@ %d", objc_getAssociatedObject(assA, kAssociatedObjectKey), assA.uintA); // b
    

    输出

    2017-02-15 17:52:08.162 block[52913:8524607] assStr
    2017-02-15 17:52:08.167 block[52913:8524607] (null) 10
    

    疑惑点
    objc_setAssociatedObject绑定的属性并没有存在属性列表。具体可参见runtime源码

    5、sel相关

    代码

    // 获取sel的名称
    const char *selName = sel_getName(@selector(test));
    NSLog(@"%s", selName);  // a
    // 生成一个新的sel
    SEL newSel = sel_registerName("newSel");
    NSLog(@"%s", newSel);   // b
    // 同上。生成一个新的sel
    SEL newSel1 = sel_getUid("newSel1");
    NSLog(@"%s", newSel1);  // c
    // 判断两个sel是否相同
    if(sel_isEqual(newSel, newSel1)) {
      NSLog(@"相同sel");
    } else {
      NSLog(@"不相同sel");   // d
    }
    

    输出

    2017-02-15 18:06:09.912 block[54209:8542407] test  // a
    2017-02-15 18:06:09.916 block[54209:8542407] newSel  // b
    2017-02-15 18:06:09.917 block[54209:8542407] newSel1  // c
    2017-02-15 18:06:09.917 block[54209:8542407] 不相同sel  // d
    

    6、method相关

    代码

    // 调用一个object的method
    // method_invoke_stret(testMethod, method); 参照void method_invoke_stret(void *stretAddr, id theReceiver, SEL theSelector, ....) stretAddr为返回的数据结构
    // 若报错,project里面设定Enable Strict Checking of objc_msgSend Calls为NO
    A *testMethod = [A new];
    testMethod.uintA = 100;
    unsigned int outCount;
    Method *methods = class_copyMethodList([A class], &outCount);
    Method method = methods[0];
    method_invoke(testMethod, method);  // a
    // 将某个method指向一个新的IMP
    method_setImplementation(method, (IMP)aNewMethod);
    method_invoke(testMethod, method);  // b
    // 获取method的SEL
    SEL methodSel = method_getName(method);
    NSLog(@"%s", methodSel);    // c
    // 获取method的IMP
    // 随意传两个参数,无所谓的
    IMP methodImp = method_getImplementation(method);
    methodImp(0,0); // d
    // 获取method的类型编码,包含返回值和参数
    const char *methodEncoding = method_getTypeEncoding(method);
    NSLog(@"%s", methodEncoding);   // e
    // 获取method的返回值类型
    const char *returnType = method_copyReturnType(method);
    NSLog(@"%s", returnType);   // f
    // 获取method的某个参数类型
    const char *oneArgumentType = method_copyArgumentType(method, 0);
    NSLog(@"%s", oneArgumentType);  // g
    // 获取method的返回值类型
    char dst[256];
    method_getReturnType(method, dst, 256);
    NSLog(@"%s", dst);  // h
    // 获取method的参数个数
    unsigned int numOfArgu = method_getNumberOfArguments(method);
    NSLog(@"%d" ,numOfArgu);    // i
    // 获取method的某个参数的类型
    method_getArgumentType(method, 0, dst, 256);
    NSLog(@"%s", dst);  // j
    // 获取method的描述
    objc_method_description des = *method_getDescription(method);
    NSLog(@"%s", des);  // k
    // 更换method method_exchangeImplementations
    method_exchangeImplementations(methods[0], methods[1]);
    method_invoke(testMethod, methods[1]);  // l
    

    输出

    2017-02-15 18:16:16.714 block[55151:8552107] 100  // a
    2017-02-15 18:16:16.727 block[55151:8552107] aNewMethod  // b
    2017-02-15 18:16:16.727 block[55151:8552107] test  // c
    2017-02-15 18:16:16.728 block[55151:8552107] aNewMethod  // d
    2017-02-15 18:16:16.728 block[55151:8552107] v8@0:4  // e
    2017-02-15 18:16:16.728 block[55151:8552107] v  // f
    2017-02-15 18:16:16.728 block[55151:8552107] @  // g
    2017-02-15 18:16:16.729 block[55151:8552107] v  // h
    2017-02-15 18:16:16.729 block[55151:8552107] 2  // i  
    2017-02-15 18:16:16.729 block[55151:8552107] @  // j
    2017-02-15 18:16:16.729 block[55151:8552107] test  // k
    2017-02-15 18:16:16.729 block[55151:8552107] aNewMethod  // l
    

    7、protocol相关

    代码

    // 根据char *获取Protocol
    Protocol *protocol = objc_getProtocol("AProtocol");
    NSLog(@"%s", protocol_getName(protocol));   // a
    // 获取项目所有的protocol列表
    unsigned int protocolCount;
    Protocol * __unsafe_unretained *protocols = objc_copyProtocolList(&protocolCount);
    NSLog(@"%d", protocolCount);    // b
    // 动态创建一个protocol
    Protocol *bProtocol = objc_allocateProtocol("BProtocol");
    // 动态为protocol添加一个实例方法
    protocol_addMethodDescription(bProtocol, NSSelectorFromString(@"bProtocolMethod"), "v@:", NO, YES);
    // 动态为protocol添加property
    // 添加属性必须两个参数都是YES,因为属性的方法必须是实例和requried
    objc_property_attribute_t attributes[] = { { "T", "@\"NSString\"" }, { "&", "N" }, { "V", "" } };
    protocol_addProperty(bProtocol, "bProtocolProperty", attributes, 3, YES, YES);
    // 为protocol添加其需要遵循的protocol
    protocol_addProtocol(bProtocol, @protocol(NSObject));
    // 注册这个protocol
    objc_registerProtocol(bProtocol);
    // 获取protocol的所有可选的实例方法
    class_addProtocol([A class], bProtocol);
    class_addMethod([A class], NSSelectorFromString(@"bProtocolMethod"), (IMP)aNewMethod, "v@:");
    unsigned int protocolOutCount;
    protocol_copyMethodDescriptionList(bProtocol, NO, YES, &protocolOutCount);
    NSLog(@"%d", protocolOutCount); // c
    // 获取protocol中某个方法的描述
    objc_method_description desc = protocol_getMethodDescription(bProtocol, NSSelectorFromString(@"bProtocolMethod"), NO, YES);
    NSLog(@"%s", desc); // d
    // 获取protocol的属性列表
    protocol_copyPropertyList(bProtocol, &protocolOutCount);
    NSLog(@"%d", protocolOutCount); // e
    // 获取protocol中某个属性
    objc_property_t proNewPro = protocol_getProperty(bProtocol, "bProtocolProperty", YES, YES);
    NSLog(@"%s", proNewPro);    // f
    // 获取protocol遵循的protocol列表
    protocol_copyProtocolList(bProtocol, &protocolOutCount);
    NSLog(@"%d", protocolOutCount); // g
    // 判断某个protocol是否遵循另一个protocol
    if (protocol_conformsToProtocol(bProtocol, @protocol(NSObject))) {
      NSLog(@"bProtocol遵循NSObject");  // h
    }
    // 判断两个protocol是否相同
    if(!protocol_isEqual(bProtocol, @protocol(AProtocol))) {
      NSLog(@"bProtocol和Aprotocol不同");    // i
    }
    

    输出

    2017-02-15 18:35:27.722 block[56930:8579131] Protocol  // a
    2017-02-15 18:35:27.733 block[56930:8579131] 737  // b
    2017-02-15 18:35:27.735 block[56930:8579131] 1  // c
    2017-02-15 18:35:27.736 block[56930:8579131] bProtocolMethod  // d
    2017-02-15 18:35:27.737 block[56930:8579131] 1  // e
    2017-02-15 18:35:27.738 block[56930:8579131] §=  // f
    2017-02-15 18:35:27.739 block[56930:8579131] 1  // g
    2017-02-15 18:35:27.741 block[56930:8579131] bProtocol遵循NSObject  // h
    2017-02-15 18:35:27.742 block[56930:8579131] bProtocol和Aprotocol不同  // i
    

    疑惑点
    a、若某类遵循了某个协议,则其派生类均遵循这个协议。
    b、protocol的property和class的property区别:protocol的property相当于@requried的set/get,即property存在于protocol的结构体中,set/get存在于遵循protocol的class的结构体中。而class的property和set/get都存在于自身的结构体中。

    8、message相关

    代码

    // objc_msgSend返回id类型
    // objc_msgSend_fpret返回double类型
    // void objc_msgSend_stret(id self, SEL op, ...) 为返回一个结构体,参照void objc_msgSend_stret(void *stretAddr, id theReceiver, SEL theSelector, ....)
    objc_msgSend([B new], @selector(bTest)); // a
    objc_msgSend_fpret([B new], @selector(bTest));  // b
    // objc_msgSendSuper,向superClass发消息,返回id
    // OBJC_EXPORT void objc_msgSendSuper_stret(struct objc_super *super, SEL op, ...) 为返回一个结构体,参照void objc_msgSendSuper_stret(void *stretAddr, id theReceiver, SEL theSelector, ....)
    objc_super superClass = {(id)[B new], [B superclass]};
    objc_msgSendSuper(&superClass, @selector(bTest));   // c
    

    输出

    2017-02-16 10:12:34.779 block[63306:8664764] bTest  // a
    2017-02-16 10:12:34.785 block[63306:8664764] bTest  // b
    c会crash,因为B父类NSObject中没有bTest方法。
    

    9、library相关

    代码

    // 获取工程中所有的frameworks和dynamic libraries名称
    unsigned int count;
    const char **arr = objc_copyImageNames(&count);
    NSLog(@"%s", arr[0]);
    // 获取某个class所在的库名称
    const char *frameworkName = class_getImageName([UIImage class]);
    NSLog(@"%s", frameworkName);
    // 根据某个库名,获取该库所有的class
    const char **classArr = objc_copyClassNamesForImage(arr[0], &count);
    NSLog(@"%s", classArr[0]);
    

    输出

    2017-02-16 11:00:57.510 block[67800:8717176] /Users/wuyang/Downloads/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib/system/introspection/libdispatch.dylib
    2017-02-16 11:00:57.551 block[67800:8717176] /Users/wuyang/Downloads/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/UIKit.framework/UIKit
    2017-02-16 11:00:57.551 block[67800:8717176] OS_object
    

    10、OC Features相关

    代码

    // A、
    // void objc_enumerationMutation(id obj)
    // void objc_setEnumerationMutationHandler(void (*handler)(id))
    // 两个方法是用来在快速遍历的时候捕获一些突发性的问题的。(没测试过)
    // B、 
    // IMP imp_implementationWithBlock(id block)
    // id imp_getBlock(IMP anImp)
    // BOOL imp_removeBlock(IMP anImp)
    // 三个方法是用来 绑定/获取/解绑 block到某个方法的。
    // C、   
    // id objc_loadWeak(id *location)
    // id objc_storeWeak(id *location, id obj)
    // objc_storeWeak即为正常的weak类型赋值的时候需要调用的方法。objc_loadWeak为在某个方法中使用weak类型对象的话,先retain并加入autoreleasepool,pop autoreleasepool的时候release。
    

    疑惑点
    A参见objc-foreach.c
    B参见Aspects
    C参见objc_loadWeakobjc_storeWeak

    三、Runtime的简单实用样例

    1、Category关联属性

    因为category只能为class增加方法,而不能为class增加属性,但是,在某些情况下,动态增加属性是必须的。所以有了这样的黑科技的方法,即并没有为class增加了属性,但是也实现了增加属性的效果,有点绕。
    代码

    // .h
    #import <Foundation/Foundation.h>
    @interface NSObject (Test)
    @property (nonatomic, strong) NSString *test;
    @end
    // .m
    #import "NSObject+Test.h"
    #import <objc/runtime.h>
    @implementation NSObject (Test)
    - (void)setTest:(NSString *)test {
        objc_setAssociatedObject(self, @selector(test), test, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (NSString *)test {
        return objc_getAssociatedObject(self, @selector(test));
    }
    @end
    

    推荐
    关于为class动态绑定属性的深入了解,推荐文章:Objective-C Associated Objects 的实现原理

    2、Json映射Model

    这里就不举例了,很多代码。推荐参看YYModel。后续我也会写一些解读YY系列的文章,毕竟代码质量太高了。

    3、Swizzle

    也不多说了,核心函数就是method_exchangeImplementations。后续会写两篇大量使用runtime,尤其是swizzle的方案:无埋点方案和防crash方案。

    四、总结

    断断续续一个月,总结了runtime的消息转发,和runtime所有的public api。就是希望自己能对runtime有个深入的认识,如果能帮到您那是更好。runtime到这里先画一个小逗号,无埋点方案和防crash方案的blog和代码将会在不久后放出。

    五、文献

    1、http://blog.leichunfeng.com/blog/2015/06/26/objective-c-associated-objects-implementation-principle/
    2、https://github.com/ibireme/YYModel/tree/master/YYModel
    3、https://github.com/opensource-apple/objc4
    4、https://github.com/gcc-mirror/gcc/blob/master/libobjc/objc-foreach.c
    5、https://github.com/steipete/Aspects/blob/master/Aspects.m
    6、http://blog.devtang.com/2014/03/21/weak_object_lifecycle_and_tagged_pointer/
    7、http://esoftmobile.com/2012/12/16/memory-managment/

    相关文章

      网友评论

      本文标题:runtime变奏曲,那些藏在runtime中的接口(二)

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