之前在项目或者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添加分类

将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;
}
- Category的结构中并没有ivars成员变量列表
- 分类并不会改变原有类的内存分布的情况,分类是在运行期决定的,此时内存的分布已经确定,若此时再添加实例会改变内存的分布情况,这对编译性语言是灾难,是不允许的。反观扩展(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'
实现关联属性之后,会正确打印:
哈哈
消息处理与消息转发
当调用一个类的方法时,先在本类中的方法缓存列表进行查找,如果在缓存方法列表中找到了该方法的实现,就执行;如果找不到就在本类的方法列表中查找,在本类方法列表中查找到相应的方法实现后就进行调用,如果没找到,就去父类中查找。如果在父类中的方法列表中找到了相应的方法实现,那么就执行,否则就进入消息转发流程。

消息转发机制分三大步骤:
- Method resolution 方法解析处理阶段(动态方法解析)
- Fast forwarding 快速转发阶段(备援接收者)
- Normal forwarding 常规转发阶段(完整的消息转发)
- 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;
}
执行:
类方法: 类方法参数
- 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:@"实例方法的参数"];
打印:
实例方法: 实例方法的参数
- 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消息转发机制
网友评论