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 --- [email protected]"NSString",&,N,V_name
     2016-06-02 19:26:16.524 RuntimeTestDemo[32706:3539390] address --- [email protected]"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, "[email protected]:[email protected]"); // 这里会报警告  可以忽略
    //调用方法 【如果使用[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, "[email protected]:")) {
        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 (积分2分,分多任性 哈哈)
github地址:https://github.com/yj229201093/RuntimeTestDemo
这里感谢两位大神的博客,受益良多。
主要参考链接(当然网络上还有很多好的博客都学习了O(∩_∩)O哈哈~)
1.http://www.bkjia.com/IOSjc/1012702.html
2.http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
时间: 2024-10-20 19:34:10

Objective-C Runtime的基本使用(iOS Runtime的初体验)的相关文章

iOS AR技术初体验,使用EasyAR示例程序的小白指南

QQ前两天的传递火炬,是我第一次直接接触到AR.(虽然之前听同事说过,因为他喜欢玩游戏,PS.3DS等等都玩过,这个技术最开始就是从这里出现的).所以感觉很有趣,就想自己也试着搞一下玩玩...下面是我的初体验. 首先搜索了一下网上有哪些支持的SDK,比较好的几个:Metaio被苹果收购.Vuforia被高通(Qualcomm)卖给PTC后,相对较好的有ARToolKit.Wikitude等.不过国内也有不错的.本着能不看英文就不看英文的想法,我选择了一个国内的引擎:EasyAR. 既然选择了,就

iOS facebook PoP 初体验

说到pop,我学到了很多.第一次使用了,cocopods工具,因为不熟悉操作,昨天弄了好久,终于给掌握了.从安装到运行.为了给更多的人能够学到pop,我今天写一个完全版的从cocopods运用到pop动画简单介绍. 1.cocopods的安装.打开终端输入这个命令:  gem sources --remove https://rubygems.org/  等有反应后再输入:  gem sources -a http://ruby.taobao.org/  最好输入  gem sources -l

iOS学习1_初体验

UIView:所有控件的父类,每个UIView也是一个容器,可以容纳其他的UIView UIController:用来控制UIView,负责创建/销毁自己的UIView,显示/隐藏UIView,处理UIView和用户交互(事件处理) 界面的创建过程,先创建一个ViewController,再由其创建自己的UIView,最后把UIView显示到用户眼前,并且由UIViewController处理UIView的事件. 程序的启动过程: 加载配置为main的storyboard,创建白色箭头只想的控制

ios runtime swizzle

ios runtime swizzle @implementation NSObject(Extension) + (void)swizzleClassMethod:(Class)class originSelector:(SEL)originSelector otherSelector:(SEL)otherSelector { Method otherMehtod = class_getClassMethod(class, otherSelector); Method originMehtod

ios runtime 动态向类添加方法

1.定义C函数: void dynamicMethodIMP(id self, SEL _cmd) { NSLog(@"蜗牛也疯狂"); } 2.重写函数+(BOOL)resolveInstanceMethod:(SEL)sel +(BOOL)resolveInstanceMethod:(SEL)sel { class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "[email protected]:");

iOS runtime原理

对于runtime机制,在网上找到的资料大概就是怎么去用这些东西,以及查看runtime.h头文件中的实现,当然这确实是一种很好的学习方法,但是,其实我们还是不会知道runtime底层编译成C++语言之后做了什么? 查到一个大牛给资料,顿时对runtime有了一定认识! 我们随便写一个小程序,代码如下: person类头文件如下, #import <Foundation/Foundation.h> @interface Person : NSObject @property (nonatomi

ios runtime的相关知识

一.iOS runtime原理 对于runtime机制,在网上找到的资料大概就是怎么去用这些东西,以及查看runtime.h头文件中的实现,当然这确实是一种很好的学习方法,但是,其实我们还是不会知道runtime底层编译成C++语言之后做了什么? 查到一个大牛给资料,顿时对runtime有了一定认识! 我们随便写一个小程序,代码如下: person类头文件如下, <!-- lang: cpp --> #import <Foundation/Foundation.h> @interf

iOS Runtime原理及使用

runtime简介 因为Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时.也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码.这就是 Objective-C Runtime 系统存在的意义,它是整个Objc运行框架的一块基石. RunTime简称运行时.OC就是运行时机制,其中最主要的是消息机制.对于C语言,函数的调用在编译的时候会决定调用哪个函数.对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个

iOS runtime实战应用:关联对象

在开始之前建议先阅读iOS runtime的基础理解篇:iOS内功篇:runtime 有筒子在面试的时候,遇到这样一个问题:"如何給NSArray添加一个属性(不能使用继承)",筒子立马蒙逼了,不能用继承,难道用分类?但是分类貌似只能添加方法不能添加属性啊,筒子百思不得其解,直到后来接触到了runtime才恍然大悟. 什么是关联对象 关联对象是指某个OC对象通过一个唯一的key连接到一个类的实例上.举个例子:xiaoming是Person类的一个实例,他的dog(一个OC对象)通过一根