前言
有关Runtime的知识总结,我本来想集中写成一篇文章的,但是最后发现实在是太长,而且不利于阅读,最后分成了如下几篇:
查看objc/runtime.h 里面的API,里面是关于下面这些关键字的各种操作方法(创建、添加、修改、销毁),本文就不一一列表具体API的意思,想了解的朋友可以自行查询。本文主要讲Runtime的一些常规操作。
- Class
- Ivar
- property
- protocol
- SEL
- IMP
- Method
RunTime 的常规操作?
- NSString、Class、SEL之间的转化(反射机制)
- 根据字符串动态生成一个UIViewController并跳转
- 动态创建一个类、添加属性变量并对属性变量赋值,添加方法并调用新方法
- 获取一个类的所有方法
- 获取一个类的所有成员变量
- 获取一个类的所有属性变量
- 获取协议列表
- 动态给一个类新增一个方法
- 动态增加实例变量
- 动态改变对象的某个变量值.
- 动态添加属性(属性关联)
- 交换一个类的两个方法的实现
NSString、Class、SEL、Protocol之间的转化(反射机制)
FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);
FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);
FOUNDATION_EXPORT NSString *NSStringFromClass(Class aClass);
FOUNDATION_EXPORT Class _Nullable NSClassFromString(NSString *aClassName);
FOUNDATION_EXPORT NSString *NSStringFromProtocol(Protocol *proto) API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
FOUNDATION_EXPORT Protocol * _Nullable NSProtocolFromString(NSString *namestr) API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
根据字符串动态生成一个UIViewController并跳转
Class cls=NSClassFromString(classNameArray[buttonIndex]);
UIViewController *viewController=[[cls alloc] init];
viewController.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:viewController animated:YES];
动态创建一个类、添加属性变量并对属性变量赋值,添加方法并调用新方法
我们常见的创建新的类都是通过新建类文件的方式,
我们也可以通过 runtime 的方式动态创建一个类,下面是整个过程:
- 创建一个集成NSObject的类 类名是MyClass并初始化;
- 为这个类增加一个实例变量,通过KVC给这个实例变量赋值。
- 为这个类增加一个方法。在这个方法中打印一些值。
- 通过这个类的实例调用新增的方法。
注意:调用的c语言的方法 所以不要使用 @"" 表示字符串 应该使用 ""
Class MyClass = objc_allocateClassPair([NSObject class], "MyClass", 0);
// 2、增加实例变量
// 参数一、类名
// 参数二、属性名称
// 参数三、开辟字节长度
// 参数四、对其方式
// 参数五、参数类型 “@” 官方解释 An object (whether statically typed or typed id) (对象 静态类型或者id类型) 具体类型可参照 https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
// return: BOOL 是否添加成功
BOOL isSuccess = class_addIvar(MyClass, "test", sizeof(NSString *), 0, "@");
// 三目运算符
isSuccess?NSLog(@"添加变量成功"):NSLog(@"添加变量失败");
// 3、增加方法
class_addMethod(MyClass, @selector(addMethodForMyClass:), (IMP)addMethodForMyClass, "V@:");
//注册这个类到runtime系统中就可以使用他了
objc_registerClassPair(MyClass);
// id myObjc = [[MyClass alloc] init];
// NSLog(@"%@",myObjc);
// 在OC中,我们对方法的调用都会被转换成内部的消息发送执行对objc_msgSend方法的调用,掌握好消息发送,可以让我们在编程中更方便灵活。
// 上面的id myObjc = [[MyClass alloc] init]; 我们可以通过runtime 消息发送objc_msgSend去实现
// 实现[MyClass alloc] 去开辟空间
id myobjc = objc_msgSend(MyClass, @selector(alloc));
myobjc = objc_msgSend(myobjc, @selector(init));
NSLog(@"%@",myobjc);
NSString *str = @"我是test";
// 通过KVC的方式给myObj对象的test属性赋值
[myobjc setValue:str forKey:@"test"];
// 如果不调用- (void)addMethodForMyClass:(NSString *)string 这个方法,就不会调用static void addMethodForMyClass(id self, SEL _cmd, NSString *test) 函数
[myobjc addMethodForMyClass:@"参数"];
--------------------------------------------------
//self和_cmd是必须的,在之后可以随意添加其他参数
static void addMethodForMyClass(id self, SEL _cmd, NSString *test) {
// 获取类中指定名称实例成员变量的信息
Ivar ivar = class_getInstanceVariable([self class], "test");
// 获取整个成员变量列表
// Ivar * class_copyIvarList ( Class cls, unsigned intint * outCount );
// 获取类中指定名称实例成员变量的信息
// Ivar class_getInstanceVariable ( Class cls, const charchar *name );
// 获取类成员变量的信息
// Ivar class_getClassVariable ( Class cls, const charchar *name );
// 返回名为test的ivar变量的值
id obj = object_getIvar(self, ivar);
NSLog(@"%@",obj);
NSLog(@"addMethodForMyClass:参数:%@",test);
NSLog(@"ClassName:%@",NSStringFromClass([self class]));
}
--------------------------------------------------
//这个方法实际上没有被调用,但是必须实现否则不会调用下面的方法
- (void)addMethodForMyClass:(NSString *)string {
}
获取一个类的所有方法
获取类的实例化方法 - 减号方法,包括getter, setter, 分类中的方法等。
使用 [Person fetchInstanceMethodList]
获取
#import <objc/runtime.h>
@implementation NSObject (Runtime)
+ (NSArray *)fetchInstanceMethodList
{
unsigned int count = 0;
Method *methodList = class_copyMethodList(self, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++)
{
Method method = methodList[i];
SEL methodName = method_getName(method);
[mutableList addObject:NSStringFromSelector(methodName)];
}
free(methodList);
return [NSArray arrayWithArray:mutableList];
}
获取类的类方法 + 加号方法
使用[Person fetchClassMethodList]
获取
+ (NSArray *)fetchClassMethodList
{
unsigned int count = 0;
Method *methodList = class_copyMethodList(object_getClass(self), &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++)
{
Method method = methodList[i];
SEL methodName = method_getName(method);
[mutableList addObject:NSStringFromSelector(methodName)];
}
free(methodList);
return [NSArray arrayWithArray:mutableList];
}
获取一个类的所有成员变量
class_copyIvarList能够获取一个含有类中所有成员变量的列表,列表中包括属性变量和实例变量。需要注意的是,如果如本例中,ivar_getName()获取的值前面有_ 是属性变量,前面没有下划线的是实例变量(包括隐藏起来的私有变量和.h 中公开的实例变量)。
(因为@property默认生成了"_age",而@synthesize默认是执行了"@synthesize age = _age;"),
- (void) getAllVariable {
unsigned int count = 0;
//获取类的一个包含所有变量的列表,IVar是runtime声明的一个宏,是实例变量的意思.
Ivar *allVariables = class_copyIvarList([self class], &count);
for(int i = 0;i<count;i++)
{
//遍历每一个变量,包括名称和类型(此处没有星号"*")
Ivar ivar = allVariables[i];
const char *Variablename = ivar_getName(ivar); //获取成员变量名称
const char *VariableType = ivar_getTypeEncoding(ivar); //获取成员变量类型
NSLog(@"(Name: %s) ----- (Type:%s)",Variablename,VariableType);
}
free(allVariables);
}
获取一个类的所有属性变量
里面不光有自己定义的属性变量,还有一些系统自带的属性变量
- (void) getAllPropertyList {
unsigned int count = 0;
objc_property_t *allVariables = class_copyPropertyList([self class], &count);
for(int i = 0;i<count;i++)
{
//遍历每一个变量,包括名称和类型(此处没有星号"*")
objc_property_t property = allVariables[i];
const char *Variablename = property_getName(property); //获取属性变量名称
const char *VariableType = property_getAttributes(property); //获取属性变量的属性
NSLog(@"(Name: %s) ----- (Type:%s)",Variablename,VariableType);
}
free(allVariables);
}
获取协议列表
下面是获取类所遵循协议列表的方法:
+ (NSArray *)fetchProtocolList
{
unsigned int count = 0;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList(self, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++ )
{
Protocol *protocol = protocolList[i];
const char *protocolName = protocol_getName(protocol);
[mutableList addObject:[NSString stringWithUTF8String:protocolName]];
}
return [NSArray arrayWithArray:mutableList];
}
动态给一个类新增一个方法
/* 动态添加方法:
第一个参数表示Class cls 类型;
第二个参数表示待调用的方法名称;
第三个参数(IMP)myAddingFunction,IMP一个函数指针,这里表示指定具体实现方法myAddingFunction;
第四个参数表方法的参数,0代表没有参数;
*/
class_addMethod([person class], @selector(myAddingFunction), (IMP)myAddingFunction, 0);
//调用方法 【如果使用[per NewMethod]调用方法,在ARC下会报“no visible @interface"错误】
[person performSelector:@selector(myAddingFunction)];
//具体的实现(方法的内部都默认包含两个参数Class类和SEL方法,被称为隐式参数。)
int myAddingFunction(id self, SEL _cmd){
NSLog(@"已新增方法:NewMethod");
return 1;
}
虽然会报警告,但是那只是编译器的警告,不会影响实际的运行结果。
动态增加实例变量
参数一、类名
参数二、属性名称
参数三、开辟字节长度
参数四、对其方式
参数五、参数类型 “@” 官方解释 An object (whether statically typed or typed id) (
对象 静态类型或者id类型) 具体类型可参照
官方文档
return: BOOL 是否添加成功
BOOL isSuccess = class_addIvar(MyClass, "test", sizeof(NSString *), 0, "@");
// 三目运算符
isSuccess?NSLog(@"添加变量成功"):NSLog(@"添加变量失败");
动态改变对象的某个变量值.
可以是属性变量(注意加 _ ),也可以是私有的全局变量.
Person *person = [[Person alloc]init];
person.name = @"asdas";
NSLog(@"%@",person.name);
[self editObjectIvar:person :@"_name" :@"新属性"];
NSLog(@"new :%@",person.name);
- (void)editObjectIvar :(id)object
:(NSString *)IvarStr
:(NSString *)IvarNewStr
{
unsigned int count = 0;
Ivar *allList = class_copyIvarList([object class], &count);
for(int i = 0;i<count;i++)
{
//遍历每一个变量,包括名称和类型(此处没有星号"*")
Ivar ivar = allList[i];
const char *Variablename = ivar_getName(ivar); //获取成员变量名称
if ([[NSString stringWithUTF8String:Variablename] isEqualToString:IvarStr]) {
object_setIvar(object, ivar, IvarNewStr); //name属性Tom被强制改为Mike。
}
}
free(allList);
}
动态添加属性(属性关联)
方式1:
#import "Person.h"
@interface Person (newProperty)
@property (nonatomic, copy) NSString *categroyProperty;
@end
#import "Person+newProperty.h"
#import <objc/runtime.h>
@implementation Person (newProperty)
- (void)setCategroyProperty:(NSString *)categroyProperty
{
objc_setAssociatedObject(self, @selector(categroyProperty), categroyProperty, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)categroyProperty
{
return objc_getAssociatedObject(self, @selector(categroyProperty));
}
@end
方式2:
static char mykey;
objc_setAssociatedObject(self, &mykey,@"test", OBJC_ASSOCIATION_COPY_NONATOMIC);
NSLog(@"LL: %@",objc_getAssociatedObject(self, &mykey));
*********************************** 使用
#import "Person+newProperty.h"
Person *person = [[Person alloc]init];
person.name = @"asdas";
person.categroyProperty = @"厉害啊";
NSLog(@"%@",person.categroyProperty);
特别说明下:
方式1中的以类别的形式为一个类增加属性,在调用环境中必须满足两个条件,否则会报错。
- 在调用的类中只能出现 #import "Person+newProperty.h",不能出现 #import "Person.h",否则不能打. 调用新属性。
- Person 文件中 不能出现 #import "Person+newProperty.h",否则也会报错。
方式2:中值得注意的是关联的新属性是任意对象类型,可以是一个 UIView,也可以是一个字符串。
交换一个类的两个方法的实现
如果将originMethod与currentMethod的方法实现进行交换的话,
调用originMethod时就会执行currentMethod的内容。
交换俩个方法的实现:
Method method1 = class_getInstanceMethod([person class], @selector(func1));
Method method2 = class_getInstanceMethod([person class], @selector(func2));
//交换方法
method_exchangeImplementations(method1, method2);
[person func1];
结果在预料之中
封装后:
交换两个实例方法( - 方法)
+ (void)swapMethod:(SEL)originMethod currentMethod:(SEL)currentMethod;
{
Method firstMethod = class_getInstanceMethod(self, originMethod);
Method secondMethod = class_getInstanceMethod(self, currentMethod);
method_exchangeImplementations(firstMethod, secondMethod);
}
交换两个类方法( + 方法)
+ (void)swapClassMethod:(SEL)originMethod currentMethod:(SEL)currentMethod;
{
Method firstMethod = class_getClassMethod(self, originMethod);
Method secondMethod = class_getClassMethod(self, currentMethod);
method_exchangeImplementations(firstMethod, secondMethod);
}
交换方法的使用场景: 项目中的某个功能,在项目中需要多次被引用,当项目的需求发生改变时,要使用另一种功能代替这个功能,且要求不改变旧的项目(也就是不改变原来方法实现的前提下)。那么 ,
- 我们可以在分类中,再写一个新的方法(符合新的需求的方法)
- 然后在分类中的+load方法里面(因为load方法会在程序运行前加载一次)交换两个方法的实现。
- 这样,在不改变项目的代码,而只是增加了新的代码的情况下,就完成了项目的改进,很好地体现了该项目的封装性与利用率。
参考文章:
iOS开发 -- Runtime 的几个小例子
Runtime的运用和减少应用崩溃
ios开发 -- runtime动态创建类、添加方法、添加实例变量
runtime——消息机制
【OC刨根问底】Runtime简单粗暴理解
OC最实用的runtime总结,面试、工作你看我就足够了!
iOS开发之Runtime常用示例总结
网友评论