美文网首页
runtime应用

runtime应用

作者: QYCD | 来源:发表于2021-10-27 16:39 被阅读0次

    之前在项目或者demo中,自己多多少少实践过runtime相关的使用,比较杂乱,这里主要参照了iOS开发之Runtime常用示例总结,个人对各种用法跟着实践一下

    准备

    ZZRuntimeKit对runtime常用功能的封装;ZZTestClass进行操作的主要对象
    ZZTestClass中定义了公有属性、私有属性、私有成员变量、公有实例方法、私有实例方法、类方法等,遵循NSCopying和NSCoding两个协议

    @interface ZZTestClass : NSObject<NSCopying, NSCoding>
    
    @property (nonatomic, strong) NSArray *publicProperty1;
    @property (nonatomic, copy) NSString *publicProperty2;
    
    + (void)classMethod:(NSString *)value;
    - (void)publicTestMethod1:(NSString *)value1 withSecond:(NSString *)value2;
    - (void)publicTestMethod2;
    
    - (void)method1;
    
    @end
    
    
    @interface ZZTestClass() {
        NSInteger _var1;
        int _var2;
        BOOL _var3;
        double _var4;
        float _var5;
    }
    
    @property (nonatomic, strong) NSMutableArray *privateProperty1;
    @property (nonatomic, strong) NSNumber *privateProperty2;
    @property (nonatomic, strong) NSDictionary *privateProperty3;
    
    @end
    
    + (void)classMethod:(NSString *)value {
        NSLog(@"classMethod");
    }
    
    - (void)publicTestMethod1:(NSString *)value1 withSecond:(NSString *)value2 {
        NSLog(@"publicTestMethod1:withSecond:");
    }
    
    - (void)publicTestMethod2 {
        NSLog(@"publicTestMethod2");
    }
    
    - (void)method1 {
        NSLog(@"method1");
    }
    
    - (void)privateTestMethod1 {
        NSLog(@"privateTestMethod1");
    }
    
    - (void)privateTestMethod2 {
        NSLog(@"privateTestMethod2");
    }
    
    1. class_getName(Class) 获取类名

    class_getName(Class)返回的是一个char类型的指针,即C语言的字符串类型
    在runtime.h中

    /* Working with Classes */
    
    /** 
     * Returns the name of a class.
     * 
     * @param cls A class object.
     * 
     * @return The name of the class, or the empty string if \e cls is \c Nil.
     */
    OBJC_EXPORT const char * _Nonnull
    class_getName(Class _Nullable cls) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    
    /// 获取类名
    /// @param cls 相应类
    + (NSString *)getClassName:(Class)cls {
        const char *className = class_getName(cls);
        return [NSString stringWithUTF8String:className];
    }
    

    使用:

    NSString *className = [ZZRuntimeKit getClassName:[ZZTestClass class]];
    NSLog(@"reslut = %@", className);
    打印:
    reslut = ZZTestClass
    
    2. class_copyIvarList(Class, &count)获取类的成员变量
    /** 
     * Describes the instance variables declared by a class.
     * 
     * @param cls The class to inspect.
     * @param outCount On return, contains the length of the returned array. 
     *  If outCount is NULL, the length is not returned.
     * 
     * @return An array of pointers of type Ivar describing the instance variables declared by the class. 
     *  Any instance variables declared by superclasses are not included. The array contains *outCount 
     *  pointers followed by a NULL terminator. You must free the array with free().
     * 
     *  If the class declares no instance variables, or cls is Nil, NULL is returned and *outCount is 0.
     */
    OBJC_EXPORT Ivar _Nonnull * _Nullable
    class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    

    获取成员变量的类型

    /** 
     * Returns the type string of an instance variable.
     * 
     * @param v The instance variable you want to enquire about.
     * 
     * @return A C string containing the instance variable's type encoding.
     *
     * @note For possible values, see Objective-C Runtime Programming Guide > Type Encodings.
     */
    OBJC_EXPORT const char * _Nullable
    ivar_getTypeEncoding(Ivar _Nonnull v) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    

    获取成员变量的名称

    /* Working with Instance Variables */
    
    /** 
     * Returns the name of an instance variable.
     * 
     * @param v The instance variable you want to enquire about.
     * 
     * @return A C string containing the instance variable's name.
     */
    OBJC_EXPORT const char * _Nullable
    ivar_getName(Ivar _Nonnull v) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    
    /// 获取类的成员变量
    /// @param cls 相应类
    + (NSArray *)getIvarList:(Class)cls {
        unsigned int count = 0;
        Ivar *ivarList = class_copyIvarList(cls, &count);
        
        NSMutableArray *array = [NSMutableArray array];
        for (unsigned int i = 0; i < count; i++) {
            NSMutableDictionary *dic = [NSMutableDictionary dictionary];
            const char *ivarName = ivar_getName(ivarList[I]);
            const char *ivarType = ivar_getTypeEncoding(ivarList[I]);
            
            dic[@"name"] = [NSString stringWithUTF8String:ivarName];
            dic[@"type"] = [NSString stringWithUTF8String:ivarType];
            [array addObject:dic];
        }
        free(ivarList);
        return [NSArray arrayWithArray:array];
    }
    

    使用:

    NSArray *array = [ZZRuntimeKit getIvarList:[ZZTestClass class]];
    NSLog(@"%@", array);
    
    打印:
    (
            {
            name = "_var1";
            type = q;
        },
            {
            name = "_var2";
            type = I;
        },
            {
            name = "_var3";
            type = c;
        },
            {
            name = "_var4";
            type = d;
        },
            {
            name = "_var5";
            type = f;
        },
            {
            name = "_publicProperty1";
            type = "@\"NSArray\"";
        },
            {
            name = "_publicProperty2";
            type = "@\"NSString\"";
        },
            {
            name = "_privateProperty1";
            type = "@\"NSMutableArray\"";
        },
            {
            name = "_privateProperty2";
            type = "@\"NSNumber\"";
        },
            {
            name = "_privateProperty3";
            type = "@\"NSDictionary\"";
        }
    )
    

    如上打印结果: 在运行时就没有公有、私有之分了,只要是成员变量就可以获取到。在OC中给类添加属性其实就是添加了一个成员变量加上getter和setter方法。所以获取的成员列表中肯定带有成员属性,不过成员属性的名称前方添加了下划线来与成员属性进行区分。
    也可以获取成员变量的类型,下方的_var1是NSInteger类型,动态获取到的是q字母,其实是NSInteger的符号。而i就表示int类型,c表示Bool类型,d表示double类型,f则就表示float类型。当然这些基本类型都是由一个字母代替的,如果是引用类型的话,则直接就是一个字符串了,比如NSArray类型就是"@NSArray"。

    3. class_copyPropertyList(Class, &count) 获取成员属性
    /** 
     * Describes the properties declared by a class.
     * 
     * @param cls The class you want to inspect.
     * @param outCount On return, contains the length of the returned array. 
     *  If \e outCount is \c NULL, the length is not returned.        
     * 
     * @return An array of pointers of type \c objc_property_t describing the properties 
     *  declared by the class. Any properties declared by superclasses are not included. 
     *  The array contains \c *outCount pointers followed by a \c NULL terminator. You must free the array with \c free().
     * 
     *  If \e cls declares no properties, or \e cls is \c Nil, returns \c NULL and \c *outCount is \c 0.
     */
    OBJC_EXPORT objc_property_t _Nonnull * _Nullable
    class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    

    获取属性的名字

    /* Working with Properties */
    
    /** 
     * Returns the name of a property.
     * 
     * @param property The property you want to inquire about.
     * 
     * @return A C string containing the property's name.
     */
    OBJC_EXPORT const char * _Nonnull
    property_getName(objc_property_t _Nonnull property) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    
    /// 获取类的属性列表,公有、私有属性,包括延展中定义的属性,以及通过runtime动态给类添加的属性(关联属性方式)
    /// @param cls 相应类
    + (NSArray *)getPropertyList:(Class)cls {
        unsigned int count = 0;
        objc_property_t *propertyList = class_copyPropertyList(cls, &count);
        
        NSMutableArray *array = [NSMutableArray array];
        for (unsigned int i = 0; i < count; i++) {
            const char *propertyName = property_getName(propertyList[I]);
            [array addObject:[NSString stringWithUTF8String:propertyName]];
        }
        free(propertyList);
        return [NSArray arrayWithArray:array];
    }
    

    调用:

    NSArray *array = [ZZRuntimeKit getPropertyList:[ZZTestClass class]];
    NSLog(@"%@", array);
    
    打印:
    (
        privateProperty1,
        privateProperty2,
        privateProperty3,
        publicProperty1,
        publicProperty2
    )
    

    获取到的属性的名称为了与其对应的成员变量进行区分,成员属性的名字前边是没有下划线的。

    4. class_copyMethodList(Class, &count) 获取类的实例方法
    /** 
     * Describes the instance methods implemented by a class.
     * 
     * @param cls The class you want to inspect.
     * @param outCount On return, contains the length of the returned array. 
     *  If outCount is NULL, the length is not returned.
     * 
     * @return An array of pointers of type Method describing the instance methods 
     *  implemented by the class—any instance methods implemented by superclasses are not included. 
     *  The array contains *outCount pointers followed by a NULL terminator. You must free the array with free().
     * 
     *  If cls implements no instance methods, or cls is Nil, returns NULL and *outCount is 0.
     * 
     * @note To get the class methods of a class, use \c class_copyMethodList(object_getClass(cls), &count).
     * @note To get the implementations of methods that may be implemented by superclasses, 
     *  use \c class_getInstanceMethod or \c class_getClassMethod.
     */
    OBJC_EXPORT Method _Nonnull * _Nullable
    class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    

    方法名

    /* Working with Methods */
    
    /** 
     * Returns the name of a method.
     * 
     * @param m The method to inspect.
     * 
     * @return A pointer of type SEL.
     * 
     * @note To get the method name as a C string, call \c sel_getName(method_getName(method)).
     */
    OBJC_EXPORT SEL _Nonnull
    method_getName(Method _Nonnull m) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    
    /// 获取类的实例方法列表: getter、setter,对象方法,类目中的方法等,但不能获取类方法
    /// @param cls 相应类
    + (NSArray *)getMethodList:(Class)cls {
        unsigned int count = 0;
        Method *methodList = class_copyMethodList(cls, &count);
        
        NSMutableArray *array = [NSMutableArray array];
        for (unsigned int i = 0; i < count; i++) {
            Method method = methodList[I];
            SEL methodName = method_getName(method);
            [array addObject:NSStringFromSelector(methodName)];
        }
        free(methodList);
        return [NSArray arrayWithArray:array];
    }
    

    调用:

    NSArray *array = [ZZRuntimeKit getMethodList:[ZZTestClass class]];
    NSLog(@"%@", array);
    
    打印:
    (
        "publicTestMethod1:withSecond:",
        publicTestMethod2,
        method1,
        privateTestMethod1,
        privateTestMethod2,
        publicProperty1,
        "setPublicProperty1:",
        publicProperty2,
        "setPublicProperty2:",
        privateProperty1,
        "setPrivateProperty1:",
        privateProperty2,
        "setPrivateProperty2:",
        privateProperty3,
        "setPrivateProperty3:",
        ".cxx_destruct"
    )
    
    5. class_copyProtocolList(Class, &count) 获取协议列表
    /** 
     * Describes the protocols adopted by a class.
     * 
     * @param cls The class you want to inspect.
     * @param outCount On return, contains the length of the returned array. 
     *  If outCount is NULL, the length is not returned.
     * 
     * @return An array of pointers of type Protocol* describing the protocols adopted 
     *  by the class. Any protocols adopted by superclasses or other protocols are not included. 
     *  The array contains *outCount pointers followed by a NULL terminator. You must free the array with free().
     * 
     *  If cls adopts no protocols, or cls is Nil, returns NULL and *outCount is 0.
     */
    OBJC_EXPORT Protocol * __unsafe_unretained _Nonnull * _Nullable 
    class_copyProtocolList(Class _Nullable cls, unsigned int * _Nullable outCount)
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    

    协议名

    /** 
     * Returns the name of a protocol.
     * 
     * @param proto A protocol.
     * 
     * @return The name of the protocol \e p as a C string.
     */
    OBJC_EXPORT const char * _Nonnull
    protocol_getName(Protocol * _Nonnull proto)
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    
    /// 获取协议列表
    /// @param cls 相应类
    + (NSArray *)getProtocolList:(Class)cls {
        unsigned int count = 0;
        __unsafe_unretained Protocol **protocolList = class_copyProtocolList(cls, &count);
        
        NSMutableArray *array = [NSMutableArray array];
        for (unsigned int i = 0; i < count; i++) {
            Protocol *protocol = protocolList[I];
            const char *protocolName = protocol_getName(protocol);
            [array addObject:[NSString stringWithUTF8String:protocolName]];
        }
        return [NSArray arrayWithArray:array];
    }
    

    调用:

    NSArray *array = [ZZRuntimeKit getProtocolList:[ZZTestClass class]];
    NSLog(@"%@", array);
    
    打印:
    (
        NSCopying,
        NSCoding
    )
    
    6. 动态添加方法实现
    /** 
     * Returns a specified instance method for a given class.
     * 
     * @param cls The class you want to inspect.
     * @param name The selector of the method you want to retrieve.
     * 
     * @return The method that corresponds to the implementation of the selector specified by 
     *  \e name for the class specified by \e cls, or \c NULL if the specified class or its 
     *  superclasses do not contain an instance method with the specified selector.
     *
     * @note This function searches superclasses for implementations, whereas \c class_copyMethodList does not.
     */
    OBJC_EXPORT Method _Nullable
    class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
        OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
    
    /** 
     * Returns the implementation of a method.
     * 
     * @param m The method to inspect.
     * 
     * @return A function pointer of type IMP.
     */
    OBJC_EXPORT IMP _Nonnull
    method_getImplementation(Method _Nonnull m) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    
    /** 
     * Returns a string describing a method's parameter and return types.
     * 
     * @param m The method to inspect.
     * 
     * @return A C string. The string may be \c NULL.
     */
    OBJC_EXPORT const char * _Nullable
    method_getTypeEncoding(Method _Nonnull m) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    
    /** 
     * Adds a new method to a class with a given name and implementation.
     * 
     * @param cls The class to which to add a method.
     * @param name A selector that specifies the name of the method being added.
     * @param imp A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd.
     * @param types An array of characters that describe the types of the arguments to the method. 
     * 
     * @return YES if the method was added successfully, otherwise NO 
     *  (for example, the class already contains a method implementation with that name).
     *
     * @note class_addMethod will add an override of a superclass's implementation, 
     *  but will not replace an existing implementation in this class. 
     *  To change an existing implementation, use method_setImplementation.
     */
    OBJC_EXPORT BOOL
    class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                    const char * _Nullable types) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    
    /// 往类上添加新的方法及其实现
    /// @param cls 添加方法的类
    /// @param methodSel 方法的名
    /// @param methodSelImpl 对应方法实现的方法名
    + (void)addMethod:(Class)cls withMethod:(SEL)methodSel withMethod:(SEL)methodSelImpl {
        Method method = class_getInstanceMethod(cls, methodSelImpl);
        IMP methodIMP = method_getImplementation(method);
        const char *types = method_getTypeEncoding(method);
        class_addMethod(cls, methodSel, methodIMP, types);
    }
    
    7. 方法实现交换
    /** 
     * Exchanges the implementations of two methods.
     * 
     * @param m1 Method to exchange with second method.
     * @param m2 Method to exchange with first method.
     * 
     * @note This is an atomic version of the following:
     *  \code 
     *  IMP imp1 = method_getImplementation(m1);
     *  IMP imp2 = method_getImplementation(m2);
     *  method_setImplementation(m1, imp2);
     *  method_setImplementation(m2, imp1);
     *  \endcode
     */
    OBJC_EXPORT void
    method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    
    /// 方法交换
    /// @param cls 交换方法所在的类
    /// @param method1 方法1
    /// @param method2 方法2
    + (void)methodSwap:(Class)cls firstMethod:(SEL)method1 secondMethod:(SEL)method2 {
        Method firstMethod = class_getInstanceMethod(cls, method1);
        Method secondMethod = class_getInstanceMethod(cls, method2);
        
        method_exchangeImplementations(firstMethod, secondMethod);
    }
    

    测试: 给ZZTestClass添加分类


    image.png

    将ZZTestClass类中的method1方法与其类目中的method2方法进行了交换,替换后在method2中调用的method2其实就是调用的method1。在第三方库中,经常会使用该特性,以达到AOP编程(Aspect Oriented Program,面向切面编程)的目的

    AOP: 在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
    一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为

    #import "ZZTestClass.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface ZZTestClass (Swap)
    
    - (void)testMethodSwap;
    - (void)method2;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    #import "ZZTestClass+Swap.h"
    #import "ZZRuntimeKit.h"
    
    @implementation ZZTestClass (Swap)
    
    - (void)testMethodSwap {
        [ZZRuntimeKit methodSwap:[self class]
                     firstMethod:@selector(method1)
                    secondMethod:@selector(method2)];
    }
    
    - (void)method2 {
        
        NSLog(@"下方实际调用的是ZZTestClass中的method1方法了");
        [self method2];
        NSLog(@"可以在method1的基础上添加新的东西了");
    }
    
    @end
    

    调用:

    ZZTestClass *instance = [ZZTestClass new];
    [instance testMethodSwap];
    [instance method1];
    
    打印:
    下方实际调用的是ZZTestClass中的method1方法了
    method1
    可以在method1的基础上添加新的东西了
    

    关联属性

    关联属性就是在类目中动态的为类添加属性。
    类别(类目、category)中为什么不能添加属性?

    category的定义: Category实质是一个objc_category的结构体,结构包含category_name,所属类名,实例方法列表,类方法列表和协议方法列表。与Class相比,缺少了struct objc_ivar_list * _Nullable ivars

    /// An opaque type that represents a category.
    typedef struct objc_category *Category;
    
    struct objc_category {
        char * _Nonnull category_name                            OBJC2_UNAVAILABLE;
        char * _Nonnull class_name                               OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable instance_methods     OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable class_methods        OBJC2_UNAVAILABLE;
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    }   
    
    1. Category的结构中并没有ivars成员变量列表
    2. 分类并不会改变原有类的内存分布的情况,分类是在运行期决定的,此时内存的分布已经确定,若此时再添加实例会改变内存的分布情况,这对编译性语言是灾难,是不允许的。反观扩展(extension),作用是为一个已知的类添加一些私有的信息,必须有这个类的源码,才能扩展,它是在编译期生效的,所以能直接为类添加属性或者实例变量。
    /** 
     * Returns the value associated with a given object for a given key.
     * 
     * @param object The source object for the association.
     * @param key The key for the association.
     * 
     * @return The value associated with the key \e key for \e object.
     * 
     * @see objc_setAssociatedObject
     */
    OBJC_EXPORT id _Nullable
    objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
        OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
    
    /** 
     * Sets an associated value for a given object using a given key and association policy.
     * 
     * @param object The source object for the association.
     * @param key The key for the association.
     * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
     * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
     * 
     * @see objc_setAssociatedObject
     * @see objc_removeAssociatedObjects
     */
    OBJC_EXPORT void
    objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                             id _Nullable value, objc_AssociationPolicy policy)
        OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
    
    .h:
    #import "ZZTestClass.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface ZZTestClass (AssociatedObject)
    
    @property (nonatomic, copy) NSString *addProperty;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    .m:
    #import "ZZTestClass+AssociatedObject.h"
    #import <objc/runtime.h>
    
    static char kAddProperty;
    
    @implementation ZZTestClass (AssociatedObject)
    
    /// getter方法 返回关联属性的值
    - (NSString *)addProperty {
        return objc_getAssociatedObject(self, &kAddProperty);
    }
    
    /// setter方法
    /// @param addProperty 设置关联属性的值
    - (void)setAddProperty:(NSString *)addProperty {
        objc_setAssociatedObject(self, &kAddProperty, addProperty, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    @end
    

    未实现关联属性之前:

    ZZTestClass *instance = [ZZTestClass new];
    instance.addProperty = @"哈哈";
    NSLog(@"%@", instance.addProperty);
    会崩溃:
    Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ZZTestClass setAddProperty:]: unrecognized selector sent to instance 0x104004cb0'
    
    实现关联属性之后,会正确打印:
    哈哈
    
    消息处理与消息转发

    当调用一个类的方法时,先在本类中的方法缓存列表进行查找,如果在缓存方法列表中找到了该方法的实现,就执行;如果找不到就在本类的方法列表中查找,在本类方法列表中查找到相应的方法实现后就进行调用,如果没找到,就去父类中查找。如果在父类中的方法列表中找到了相应的方法实现,那么就执行,否则就进入消息转发流程。

    image.png

    消息转发机制分三大步骤:

    1. Method resolution 方法解析处理阶段(动态方法解析)
    2. Fast forwarding 快速转发阶段(备援接收者)
    3. Normal forwarding 常规转发阶段(完整的消息转发)
    1. Method resolution
      如果调用了对象方法会首先+(BOOL)resolveInstanceMethod:(SEL)sel判断,如果调用了类方法会首先+(BOOL)resolveClassMethod:(SEL)sel判断,如果返回NO不能接收消息,如果返回YES,说明在该方法中对这个找不到实现的方法进行了处理。在该方法中,可以为找不到实现的SEL动态的添加一个方法实现,添加完毕后,就会执行我们添加的方法实现。这样,当一个类调用不存在的方法时,就不会崩溃了。

    调用不存在的实例方法:

    ZZTestClass *instance = [ZZTestClass new];
    [instance publicTestMethod2];
    [instance performSelector:@selector(noThisMthod:) withObject:@"实例方法的参数"];
    

    直接执行如上代码,因为找不到noThisMthod:方法,则会报错:

    -[ZZTestClass noThisMthod:]: unrecognized selector sent to instance 0x1005316f0
    

    动态添加方法实现:

    - (void)dynamicAddMethod:(NSString *)value {
        NSLog(@"方法参数: %@", value);
    }
    
    /// 找不到SEL的IMPL实现时会执行该方法
    /// @param sel 当前对象调用并且找不到IML的SEL
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        [ZZRuntimeKit addMethod:[self class] withMethod:sel withMethod:@selector(dynamicAddMethod:)];
        return YES;
    }
    

    再次执行,则正常运行:

    publicTestMethod2
    方法参数: 实例方法的参数
    

    需要注意的是: OC中所有的类本质上都是对象,对象的isa指向本类,类的isa指向元类,元类的isa指向根元类,根元类的isa指向自己;类方法需要添加到元类里面
    定义一个方法,获取本类的元类:

    /// 获取类的元类
    /// @param childClass 目标类
    + (Class)getMetaClassWithChildClass:(Class)childClass {
        //转换字符串类别
        const char *classChar = [NSStringFromClass(childClass) UTF8String];
        //需要char的字符串 获取元类
        return objc_getMetaClass(classChar);
    }
    

    调用不存在的类方法:

    [ZZTestClass performSelector:@selector(noThisMethod2:) withObject:@"类方法参数"];
    

    不做处理则会报错:

    +[ZZTestClass noThisMethod2:]: unrecognized selector sent to class 0x1000088d8
    

    动态添加类方法实现:

    + (void)addClassDynamicMethod:(NSString *)value {
        NSLog(@"类方法: %@", value);
    }
    
    + (BOOL)resolveClassMethod:(SEL)sel {
        [ZZRuntimeKit addMethod:[ZZRuntimeKit getMetaClassWithChildClass:[self class]] withMethod:sel withMethod:@selector(addClassDynamicMethod:)];
        return YES;
    }
    

    执行:

    类方法: 类方法参数
    
    1. Fast forwarding
      如果不对上述消息进行处理,即+ (BOOL)resolveInstanceMethod:(SEL)sel方法返回NO,让其进入第二步。这一步运行期会问它: 能不能把这条消息转发给其他接收者来处理。
      这一步会进入- (id)forwardingTargetForSelector:(SEL)aSelector方法,转发给另一个可以处理SEL的其他对象。

    新建一个ZZSecondTestClass类,内部实现- (void)noThisMthod:(NSString *)value;方法

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface ZZSecondTestClass : NSObject
    
    - (void)noThisMthod:(NSString *)value;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    - (void)noThisMthod:(NSString *)value {
        NSLog(@"实例方法: %@", value);
    }
    

    注掉如下方法或者使其返回NO

    + (BOOL)resolveInstanceMethod:(SEL)sel {
        return NO;
    }
    
    该步如果返回self或者nil,则说明没有可以响应的目标,则就进入下一步
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        return [ZZSecondTestClass new];
    }
    
    [instance performSelector:@selector(noThisMthod:) withObject:@"实例方法的参数"];
    打印:
    实例方法: 实例方法的参数
    
    1. Normal forwarding

    如果不将消息转发给其他类的对象,那么就只能自己进行处理了。如果上述方法返回self的话,会执行-methodSignatureForSelector:方法来获取方法的参数以及返回数据类型,也就是说该方法获取的是方法的签名并返回。如果上述方法返回nil的话,那么消息转发就结束,程序崩溃,报出找不到相应的方法实现的崩溃信息。

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
        if (signature == nil) {
            signature = [NSMethodSignature signatureWithObjCTypes:"@@:"];
        }
        return signature;
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        ZZSecondTestClass *forwardClass = [ZZSecondTestClass new];
        SEL sel = anInvocation.selector;
        if ([forwardClass respondsToSelector:sel]) {
            [anInvocation invokeWithTarget:forwardClass];
        } else {
            [self doesNotRecognizeSelector:sel];
        }
    }
    

    调用:

    [instance performSelector:@selector(noThisMthod:) withObject:@"实例方法的参数"];
    
    打印:
    实例方法: 实例方法的参数
    

    什么是面向切面编程AOP?
    探究iOS分类(category)为什么不能直接添加属性
    iOS Runtime 消息转发机制原理和实际用途
    OS消息转发机制

    相关文章

      网友评论

          本文标题:runtime应用

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