RunTime 之常规操作

前言

有关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常用示例总结

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏進无尽的文章

编码篇-精析OC史诗级技术之KVC

不得不承认KVC在开发过程中是神器一般的存在。如果正确灵活使用kvc,会使得整个开发过程轻松很多。简单而强大。

12920
来自专栏阿杜的世界

【译】Java 8的新特性—终极版1. 简介2. Java语言的新特性3. Java编译器的新特性4. Java官方库的新特性5. 新的Java工具6. JVM的新特性7. 结论8. 参考资料

前言: Java 8 已经发布很久了,很多报道表明Java 8 是一次重大的版本升级。在Java Code Geeks上已经有很多介绍Java 8新特性的文章,...

12740
来自专栏進无尽的文章

RunTime 之使用前须知

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

11120
来自专栏蘑菇先生的技术笔记

探索c#之不可变数据类型

21640
来自专栏大壮

iOS runtime(理论篇)

18250
来自专栏积累沉淀

【译】Java 8的新特性—终极版

声明:本文翻译自Java 8 Features Tutorial – The ULTIMATE Guide,翻译过程中发现并发编程网已经有同学翻译过了:...

267100
来自专栏从流域到海域

《笨办法学Python》 第39课手记

《笨办法学Python》 第39课手记 本节课讲列表的操作,用来做练习的代码中出现了之前用到过的几个函数,是一节复习课。你需要记住它们。 原代码如下: ten_...

21870
来自专栏华仔的技术笔记

面经之《招聘一个靠谱的iOS》import "CYLBlockExecutor.h"import "CYLBlockExecutor.h"import "CYLNSObject+RunAtDeallo

406100
来自专栏冰霜之地

神经病院Objective-C Runtime入院第一天—isa和Class

我第一次开始重视Objective-C Runtime是从2014年11月1日,@唐巧老师在微博上发的一条微博开始。

13430
来自专栏菩提树下的杨过

objective-C 的内存管理之-自动释放池(autorelease pool)

如果一个对象的生命周期显而易见,很容易就知道什么时候该new一个对象,什么时候不再需要使用,这种情况下,直接用手动的retain和release来判定其生死足矣...

220100

扫码关注云+社区

领取腾讯云代金券