runtime应用

2021-10-27  本文已影响0人  QYCD

之前在项目或者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消息转发机制

上一篇下一篇

猜你喜欢

热点阅读