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

来源:互联网 发布:照片原图ps淘宝详情页 编辑:程序博客网 时间:2024/06/18 06:36

一、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是方法选择器,可以理解为区分方法的ID3. … 是参数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.html2.http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
2 0
原创粉丝点击