Objective-C Runtime的基本使用(iOS Runtime的初体验)

Posted Break__Self

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Objective-C Runtime的基本使用(iOS Runtime的初体验)相关的知识,希望对你有一定的参考价值。

一、Runtime前言

最近研究Runtime,基础不够好,研究好久了,才了解一些些,知道个大概,这里做一个笔记。OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类的对象相应的方法。利用runtime机制让我们可以在程序运行时动态修改类,对象中的所有属性,方法,就算是私有方法以及私有属性都可以动态的修改。所以我所理解的就是 动态创建类,修改类,访问私有方法等一些基本特性,应该说理解runtime的基本用法吧!

二、Runtime简介

Runtime简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制,runtime是一套比较底层的纯C语言API,属于一个C语言库,我们平时写的OC代码中,程序在运行过程时,其实最终都是转成了runtime的C语言代码,runtime算是OC的幕后工作者。比如 OC: [[Person alloc] init]; runtime: objc_msgSend(objc_msgSden(“Peroson”, “allo”), “init”) runtime是开源库,在这里可以查看苹果官网的开源的源代码

三、Runtime用在什么场景

1.在程序运行过程中,动态创建一个类(比如KVO的底层实现)
2.在程序运行过程中,动态地为某个类添加属性\\方法,修改属性值\\方法
3.遍历一个类的所有成员变量(属性)\\所有方法
4.交换方法实现
5.动态创建类

四、Runtime 术语

我们知道 [Person message];   转换成 objc_msgSend(Persong, @selelct(message)); 它本身是这样的
id obje_msgSend(id self, SEL op, …);

1.id  
    objc_msgSend第一个参数类型id,它是一个指向类实例的指针:
typedef struct objc_object *id (objc_object是什么  查看第4点)

2.SEL objc_msgSend函数的第二个参数类型SEL, 它是selector在Objc中的表示类型。selector是方法选择器,可以理解为区分方法的ID
3. … 是参数

4.struct objc_object {Class isa; };
objc_object 结构体包含一个 isa 指针,根据isa指针就可以找到对象所属的类。 (可以查看源代码 )

4.Class  
isa是指针是因为Class其实是一个指向objc_class结构体的指针
typedef struct objc_class *Class
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
这边包含了 超类指针, 类名, 成员变量, 方法,缓存, 还有附属的协议
Method:是一种代表类中的某个方法的类型
Ivar: 是一种代表类中实例变量的类型
IMP:这个函数指针就是指向这个方法的实现
Cache:缓存

相关函数
objc_msgSend : 给对象发送消息
class_copyMethodList : 遍历某个类所有的方法
class_copyIvarList : 遍历某个类所有的成员变量

必备常识 1> Ivar : 成员变量 2> Method : 成员方法

五、Runtime 场景举例

每个按钮对应一个操作

这里写图片描述

首先创建一个Person类

Person.h文件
//
//  Person.h
//  RuntimeTestDemo
//
//  Created by GongHui_YJ on 16/6/2.
//  Copyright © 2016年 YangJian. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (strong, nonatomic) NSString *name;

@property (strong, nonatomic) NSString *address;

- (void)eat;

- (void)test1;

- (void)test2;

@end
Person.m文件
//
//  Person.m
//  RuntimeTestDemo
//
//  Created by GongHui_YJ on 16/6/2.
//  Copyright © 2016年 YangJian. All rights reserved.
//

#import "Person.h"

@implementation Person
{
    int age;
    NSString *sex;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        _name = @"张三";
        _address = @"浙江省杭州市";
        age = 18;
        sex = @"男";
    }
    return self;
}

- (void)eat
{
    NSLog(@"吃饭");
}

- (void)test1
{
    NSLog(@"我是test1方法");
}

- (void)test2
{
    NSLog(@"我是test2方法");
}

- (NSString *)description
{
    return [NSString stringWithFormat:@"name: %@ -- age: %i -- sex:%@ -- %@", _name, age, sex, _address];
}

@end

在ViewControll.m实现如下

1.获取属性\\成员变量列表 使用 class_copyIvaeList函数
/**
 *  获取属性/成员
 *
 *  @param sender sender
 */
- (IBAction)getProperty:(id)sender {

    Class classPerson = NSClassFromString(@"Person");
    NSLog(@"--------------获取所有成员变量列表打印结果如下-----------------");
    // 获取所有成员变量列表 使用 class_copyIvarList
    unsigned int count = 0; //
    Ivar *ivarList = class_copyIvarList(classPerson, &count); // 获取所有的成员变量列表 count 记录变量的数量
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivarList[i]; // 取出第i个位置的成员变量
        const char *perosonName = ivar_getName(ivar); //获取变量名
        const char *perosonType = ivar_getTypeEncoding(ivar); // 获取变量编码类型
        NSLog(@"%s ---- %s\\n", perosonName, perosonType);
    }

    NSLog(@"--------------分割线-----------------");
    NSLog(@"--------------获取所有属性列表打印结果如下-----------------");
    // 获取属性列表 使用 class_copyPropertyList
    unsigned int countProperty = 0;
    objc_property_t *propertyList = class_copyPropertyList(classPerson, &countProperty);
    for (int i = 0; i < countProperty; i++) {
        const char *cName = property_getName(propertyList[i]);
        const char *butes = property_getAttributes(propertyList[i]);
        NSLog(@"%s --- %s\\n", cName, butes);
    }


    // 获取成员变量列表打印结果 (使用class_copyIvarList函数)
    /**
     2016-06-02 19:26:16.522 RuntimeTestDemo[32706:3539390] --------------获取所有成员变量列表打印结果如下-----------------
     2016-06-02 19:26:16.523 RuntimeTestDemo[32706:3539390] age ---- i
     2016-06-02 19:26:16.523 RuntimeTestDemo[32706:3539390] sex ---- @"NSString"
     2016-06-02 19:26:16.523 RuntimeTestDemo[32706:3539390] _name ---- @"NSString"
     2016-06-02 19:26:16.523 RuntimeTestDemo[32706:3539390] _address ---- @"NSString"
     2016-06-02 19:26:16.524 RuntimeTestDemo[32706:3539390] --------------分割线-----------------
     2016-06-02 19:26:16.524 RuntimeTestDemo[32706:3539390] --------------获取所有属性列表打印结果如下-----------------
     2016-06-02 19:26:16.524 RuntimeTestDemo[32706:3539390] name --- T@"NSString",&,N,V_name
     2016-06-02 19:26:16.524 RuntimeTestDemo[32706:3539390] address --- T@"NSString",&,N,V_address
     */

}
2.修改私有变量的值
/**
 *  修改私有变量的值
 *
 *  @param sender
 */
- (IBAction)updatePrivateValue:(id)sender {
    Person *person = [[Person alloc] init];
    NSLog(@"修改前数据: %@", [person description]);

    unsigned int count = 0; //
    Ivar *ivarList = class_copyIvarList([Person class], &count);
    for (int i = 0; i < count; i++) {
        Ivar var = ivarList[i];
        if (i == 0) // 1 是表示私有变量 age  刚刚上面打印出来 第2个
        {
            object_setIvar(person, var, @28); // 把私有变量age的值改成 28
        }

        if (i == 1) // 私有变量 sex
        {
            object_setIvar(person, var, @"女");
        }
    }

    NSLog(@"修改后数据---: %@", [person description]);

    /** 打印结果 发现 私有变量 age  和 sex 的值已经改变
     2016-06-02 19:48:32.218 RuntimeTestDemo[33349:3554863] 修改前数据: name: 张三 -- age: 18 -- sex:男 -- 浙江省杭州市
     2016-06-02 19:48:32.218 RuntimeTestDemo[33349:3554863] 修改后数据---: name: 张三 -- age: 450 -- sex:女 -- 浙江省杭州市
     */
}
3.获取类的所有方法
/**
         *  获取类的所有方法(包括私有)
         *
         *  @param sender sender
         */
        - (IBAction)getAllMethod:(id)sender {
            unsigned int count = 0;
            Method *memberFuncs = class_copyMethodList([Person class], &count); // 获取所有方法名
            for (int i = 0; i < count; i++) {
                SEL name = method_getName(memberFuncs[i]);
                const char *nameMethod = sel_getName(name); // 获取方法名
                NSLog(@"%s", nameMethod);
            }


            /** 获取所有.m 文件的所有方法  其中包括属性的get set方法.cxx_destruct 系统的
             2016-06-02 19:55:26.411 RuntimeTestDemo[33544:3558588] eat
             2016-06-02 19:55:26.412 RuntimeTestDemo[33544:3558588] address
             2016-06-02 19:55:26.412 RuntimeTestDemo[33544:3558588] .cxx_destruct
             2016-06-02 19:55:26.412 RuntimeTestDemo[33544:3558588] description
             2016-06-02 19:55:26.412 RuntimeTestDemo[33544:3558588] name
             2016-06-02 19:55:26.413 RuntimeTestDemo[33544:3558588] setName:
             2016-06-02 19:55:26.413 RuntimeTestDemo[33544:3558588] init
             2016-06-02 19:55:26.413 RuntimeTestDemo[33544:3558588] setAddress:
             */
        }
4.动态添加方法
/**
 *  动态添加方法
 *
 *  @param sender sender
 */
- (IBAction)addMethod:(id)sender {

//    class_addMethod函数参数的含义:
//    第一个参数Class cls, 类型
//    第二个参数SEL name, 被解析的方法
//    第三个参数 IMP imp, 指定的实现 这里表示具体的实现方法 myTestMethod
//    第四个参数const char *types,方法的类型(方法的参数) 0代表没有参数

//    const char *cs = getprogname();
    class_addMethod([Person class], @selector(NewMethod::), (IMP)myTestMethod, "i@:i@"); // 这里会报警告  可以忽略
    //调用方法 【如果使用[per method]方法!(在ARC下会报no visible @interface 错误)】
    [person1 performSelector:@selector(NewMethod::)];

}

// 具体的实现, 即IMP所指向的方法
int myTestMethod(id self, SEL _cmd, int var1, NSString *str)
{
    NSLog(@"已经新增方法");
    return var1;
}
/**打印结果 打印如下 说明已经添加成功
 2016-06-03 11:00:58.040 RuntimeTestDemo[34732:3750980] 已经新增方法
 */

/** 此刻再打印获取类的所有方法 NewMethod:: 这个新增的方法已经添加进来
 2016-06-03 11:00:59.185 RuntimeTestDemo[34732:3750980] NewMethod::
 2016-06-03 11:00:59.185 RuntimeTestDemo[34732:3750980] eat
 2016-06-03 11:00:59.185 RuntimeTestDemo[34732:3750980] address
 2016-06-03 11:00:59.185 RuntimeTestDemo[34732:3750980] .cxx_destruct
 2016-06-03 11:00:59.185 RuntimeTestDemo[34732:3750980] description
 2016-06-03 11:00:59.185 RuntimeTestDemo[34732:3750980] name
 2016-06-03 11:00:59.186 RuntimeTestDemo[34732:3750980] setName:
 2016-06-03 11:00:59.186 RuntimeTestDemo[34732:3750980] init
 2016-06-03 11:00:59.186 RuntimeTestDemo[34732:3750980] setAddress:

 */
5.交换方法实现
/**
 *  方法交换
 *
 *  @param sender sender
 */
- (IBAction)methodExchange:(id)sender {

    [person1 test1]; // 未交换前的输出结果

    Method method1 = class_getInstanceMethod([person1 class], @selector(test1));
    Method method2 = class_getInstanceMethod([person1 class], @selector(test2));

    // 方法交换
    method_exchangeImplementations(method1, method2);
    [person1 test1]; // 输出交换后的结果

    /**  打印结果 交换完之后 test1的方法 打印test2的结果
     2016-06-03 11:27:57.921 RuntimeTestDemo[35341:3767704] 我是test1方法
     2016-06-03 11:27:57.922 RuntimeTestDemo[35341:3767704] 我是test2方法
     */
}
6.动态创建一个类
/**
 *  动态添加类
 *
 *  @param sender
 */
- (IBAction)addClass:(id)sender {
    // 添加一个Student类
    Class classStudent = objc_allocateClassPair([Person class], "Student", 0);

    // 添加一个NSStrig的变量
    if (class_addIvar(classStudent, "schoolName", sizeof(NSString *), 0, "@")) {
        NSLog(@"添加成员变量 schollName成功");
    }

    // 为Student类添加方法
    if (class_addMethod(classStudent, @selector(printSchool), (IMP)printSchool, "v@:")) {
        NSLog(@"添加方法printSchool成功");
    }

    // 注册这个类到runtime系统中 可以使用他
    objc_registerClassPair(classStudent); // 返回void


    // 创建类
    id student = [[classStudent alloc] init];

    NSString *schoolName = @"福建师范大学";

    // 给刚刚添加的变量赋值
    [student setValue:schoolName forKey:@"schoolName"];

    // 动态调用
    [student performSelector:@selector(printSchool) withObject:nil];


    /** 打印结果
     2016-06-03 11:49:26.724 RuntimeTestDemo[35846:3781380] 添加成员变量 schollName成功
     2016-06-03 11:49:26.725 RuntimeTestDemo[35846:3781380] 添加方法printSchool成功
     2016-06-03 11:49:26.725 RuntimeTestDemo[35846:3781380] 我的学校是福建师范大学
     */
}

//方法的实现
void printSchool(id self, SEL _cmd)
{
    NSLog(@"我的学校是%@", [self valueForKey:@"schoolName"]);
}

六、Runtime使用心得

Runtime很强大,这里我只是初体验,对于很多的东西还不懂,不理解,算是一个初步的了解吧,应该算了解了runtime的基本用法,用来确实很爽。要理解透彻,完全的应用到实际项目中,还需努力。对新人了解应该有帮助,首先知道runtime是什么,runtime的基本使用,再慢慢挖掘,逐步吃透。博客中的demo我已经上传,需要的可以下载运行看下打印的日志。如果不足还望指出。

七、demo地址和参考博客,感言致谢

demo地址:http://download.csdn.net/detail/yj229201093/9540125 
这里感谢两位大神的博客,受益良多。   
主要参考链接(当然网络上还有很多好的博客都学习了O(∩_∩)O哈哈~)    
1.http://www.bkjia.com/iosjc/1012702.html
2.http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/

以上是关于Objective-C Runtime的基本使用(iOS Runtime的初体验)的主要内容,如果未能解决你的问题,请参考以下文章

Objective-C runtime的常见应用

Objective-C 中的Runtime的详细使用

Objective-C Runtime 文档翻译—与Runtime的相互作用

IOS-Runtime(消息机制)

Objective-C Runtime

Objective-C Json转Model(利用Runtime特性)