前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >RunTime 之常规操作

RunTime 之常规操作

作者头像
進无尽
发布2018-09-12 17:59:32
5630
发布2018-09-12 17:59:32
举报
文章被收录于专栏:進无尽的文章進无尽的文章

前言

有关Runtime的知识总结,我本来想集中写成一篇文章的,但是最后发现实在是太长,而且不利于阅读,最后分成了如下几篇:


查看objc/runtime.h 里面的API,里面是关于下面这些关键字的各种操作方法(创建、添加、修改、销毁),本文就不一一列表具体API的意思,想了解的朋友可以自行查询。本文主要讲Runtime的一些常规操作。

代码语言:javascript
复制
- Class
- Ivar
- property
- protocol
- SEL
- IMP
- Method

RunTime 的常规操作?

  • NSString、Class、SEL之间的转化(反射机制)
  • 根据字符串动态生成一个UIViewController并跳转
  • 动态创建一个类、添加属性变量并对属性变量赋值,添加方法并调用新方法
  • 获取一个类的所有方法
  • 获取一个类的所有成员变量
  • 获取一个类的所有属性变量
  • 获取协议列表
  • 动态给一个类新增一个方法
  • 动态增加实例变量
  • 动态改变对象的某个变量值.
  • 动态添加属性(属性关联)
  • 交换一个类的两个方法的实现

NSString、Class、SEL、Protocol之间的转化(反射机制)

代码语言:javascript
复制
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并跳转

代码语言:javascript
复制
    Class cls=NSClassFromString(classNameArray[buttonIndex]);
    UIViewController  *viewController=[[cls alloc] init];
    viewController.hidesBottomBarWhenPushed = YES;
    [self.navigationController pushViewController:viewController animated:YES];

动态创建一个类、添加属性变量并对属性变量赋值,添加方法并调用新方法

我们常见的创建新的类都是通过新建类文件的方式,

我们也可以通过 runtime 的方式动态创建一个类,下面是整个过程:

  • 创建一个集成NSObject的类 类名是MyClass并初始化;
  • 为这个类增加一个实例变量,通过KVC给这个实例变量赋值。
  • 为这个类增加一个方法。在这个方法中打印一些值。
  • 通过这个类的实例调用新增的方法。
代码语言:javascript
复制
注意:调用的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]获取

代码语言:javascript
复制
#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]获取

代码语言:javascript
复制
+ (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;"),

代码语言:javascript
复制
- (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);
}

获取一个类的所有属性变量

里面不光有自己定义的属性变量,还有一些系统自带的属性变量

代码语言:javascript
复制
- (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);
}

获取协议列表

下面是获取类所遵循协议列表的方法:

代码语言:javascript
复制
+ (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];
}

动态给一个类新增一个方法

代码语言:javascript
复制
/* 动态添加方法:
 第一个参数表示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;
}

虽然会报警告,但是那只是编译器的警告,不会影响实际的运行结果。

动态增加实例变量

代码语言:javascript
复制
参数一、类名
参数二、属性名称
参数三、开辟字节长度
参数四、对其方式
参数五、参数类型 “@” 官方解释 An object (whether statically typed or typed id) (
              对象 静态类型或者id类型) 具体类型可参照

官方文档

return: BOOL 是否添加成功

代码语言:javascript
复制
BOOL isSuccess = class_addIvar(MyClass, "test", sizeof(NSString *), 0, "@");
// 三目运算符
isSuccess?NSLog(@"添加变量成功"):NSLog(@"添加变量失败");

动态改变对象的某个变量值.

可以是属性变量(注意加 _ ),也可以是私有的全局变量.

代码语言:javascript
复制
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);
}

动态添加属性(属性关联)

代码语言:javascript
复制
方式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的内容。

代码语言:javascript
复制
交换俩个方法的实现:

Method method1 = class_getInstanceMethod([person class], @selector(func1));
Method method2 = class_getInstanceMethod([person class], @selector(func2));

//交换方法
method_exchangeImplementations(method1, method2);
[person func1]; 

结果在预料之中

封装后:

代码语言:javascript
复制
交换两个实例方法( - 方法)
+ (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常用示例总结

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.03.06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • RunTime 的常规操作?
  • NSString、Class、SEL、Protocol之间的转化(反射机制)
  • 根据字符串动态生成一个UIViewController并跳转
  • 动态创建一个类、添加属性变量并对属性变量赋值,添加方法并调用新方法
  • 获取一个类的所有方法
  • 获取一个类的所有成员变量
  • 获取一个类的所有属性变量
  • 获取协议列表
  • 动态给一个类新增一个方法
  • 动态增加实例变量
  • 动态改变对象的某个变量值.
  • 动态添加属性(属性关联)
  • 交换一个类的两个方法的实现
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档