文档章节

iOS Runtime实例

_子墨
 _子墨
发布于 2017/02/09 18:02
字数 1526
阅读 297
收藏 17
点赞 1
评论 3

/* runtime 常见的使用有:(本文将详细用代码展示)

1.动态创建一个类(比如KVO的底层实现)

2.动态地为某个类添加属性\方法, 修改属性值\方法

3.遍历一个类的所有成员变量(属性)\所有方法

4.动态交换两个方法的实现

5.实现分类也可以添加属性

6.实现NSCoding的自动归档和解档

7.实现字典转模型的自动转换

8.Hook拦截

*/

#import "ViewController.h"
#import <objc/runtime.h>
#import "Teacher.h"
#import "WZLSerializeKit.h"

[@interface](https://my.oschina.net/u/996807) ViewController ()

[@end](https://my.oschina.net/u/567204)

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
#pragma mark - 创建类、添加实例变量、添加实例方法
    // 新建Person类
    Class Person = objc_allocateClassPair([NSObject class], "Person", 0);
    
    // 给Person类添加一个NSString类型的实例变量(instance variable),第四个参数是对其方式,第五个参数是参数类型
    class_addIvar(Person, "name", sizeof(NSString *), 0, "@");

    // 添加属性
    objc_property_attribute_t type = {"T", "@\"NSString\""};
    objc_property_attribute_t ownership = { "C", "" };
    objc_property_attribute_t backingivar = { "V", "_ivar1"};
    objc_property_attribute_t attrs[] = {type, ownership, backingivar};
    class_addProperty(Person, "property2", attrs, 3);
    
    // 给Person类添加一个实例方法(instance method)
    // "v@:@",解释v-返回值void类型,@-self指针id类型,:-SEL指针SEL类型
    class_addMethod(Person, @selector(getName), (IMP)getName, "v@:");
    // v@:@",解释v-返回值void类型,@-self指针id类型,:-SEL指针SEL类型,@-函数第一个参数为id类型
    class_addMethod(Person, @selector(setName:), (IMP)setName, "v@:@");
    
    // 注册Person类后才可使用
    objc_registerClassPair(Person);
    
    // 实例化一个Person类对象
    id person = [[Person alloc] init];
    
    // 设置实例变量的值
    [person setValue:@"jacedy" forKey:@"name"];
//    object_setInstanceVariable(person, "name", (void *)&str);在ARC下不允许使用
    
    // 调用getName方法
    NSLog(@"nameValue: %@", [person getName]);  // objc_msgSend(person, @selector(getName))
    
    // 重置name值
    [person setName:@"jabit"];
    NSLog(@"nameValue: %@", [person performSelector:@selector(getName)]);
    
    
#pragma mark - 遍历实例变量、属性、实例方法
    // 遍历所有实例变量
    unsigned int ivarCount = 0;
    Ivar *ivars = class_copyIvarList([Person class], &ivarCount);
    for (int i = 0; i < ivarCount; i++)
    {
        Ivar ivar = ivars[i];
        NSLog(@"变量名:%s", ivar_getName(ivar));
    }
    
    // 遍历所有属性(这里已为UIImage类添加了一个sexProperty属性)
    unsigned int propertyCount = 0;
    objc_property_t *propertys = class_copyPropertyList([UIImage class], &propertyCount);
    for (int i = 0; i < propertyCount; i++) {
        objc_property_t property = propertys[i];
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
        if ([propertyName isEqualToString:@"sexProperty"]) {
            NSLog(@"属性名 %s found", property_getName(property));
        }
    }
    
    // 遍历所有实例方法
    unsigned int methodCount = 0;
    Method *methods = class_copyMethodList([Person class], &methodCount);
    for (int i = 0; i < methodCount; i++) {
        Method method = methods[i];
        SEL selector = method_getName(method);
        NSLog(@"方法名:%@", NSStringFromSelector(selector));
    }
    

#pragma mark - 交换两个方法测试
    [UIImage imageNamed:@"123456789.png"];
    
    
#pragma mark - 编码与解码
    Teacher *teacher = [[Teacher alloc] init];
    teacher.name = @"jacedy";
    teacher.age = 18;
    [teacher setValue:@"jabit" forKey:@"_father"];
    //set value of superClass
    teacher.introInBiology = @"I am a biology on earth";
    //[teacher setValue:@(10000) forKey:@"_hairCountInBiology"];//no access to private instance in super
    
    NSLog(@"Before archiver:\n%@", [teacher description]);
    
    WZLSERIALIZE_ARCHIVE(teacher, @"Teacher", [self filePath]);
    Teacher *theTeacher = nil;
    WZLSERIALIZE_UNARCHIVE(theTeacher, @"Teacher", [self filePath]);
    
    Teacher *copyTeacher = [teacher copy];
    NSLog(@"copyTeacher:%@", [copyTeacher description]);
    
    
#pragma mark - Hook
    Method m1 = class_getInstanceMethod([self class], @selector(viewWillAppear:));
    Method m2 = class_getInstanceMethod([self class], @selector(jk_viewWillAppear:));
    
    BOOL isSuccess = class_addMethod([self class], @selector(viewWillAppear:), method_getImplementation(m2), method_getTypeEncoding(m2));
    if (isSuccess) {
        // 添加成功:说明源方法m1现在的实现为交换方法m2的实现,现在将源方法m1的实现替换到交换方法m2中
        
        class_replaceMethod([self class], @selector(jk_viewWillAppear:), method_getImplementation(m1), method_getTypeEncoding(m1));
    }else {
        //添加失败:说明源方法已经有实现,直接将两个方法的实现交换即
        method_exchangeImplementations(m1, m2);
    }
}

-(void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"viewWillAppear");
}

- (void)jk_viewWillAppear:(BOOL)animated {
    NSLog(@"Hook : 拦截到viewwillApear的实现,在其基础上添加了这行代码");
    [self jk_viewWillAppear:YES];
}


#pragma mark - 变量name的get方法
// 这个方法实际上没有被调用,但是必须实现否则不会调用下面的方法
- (id)getName
{
    NSLog(@"getName called");
    
    // 获取实例变量
    Ivar nameIvar = class_getInstanceVariable([self class], "name");
    // 获取实例变量的值
    id name = object_getIvar(self, nameIvar);
    
    return name;
}

// 实际调用的是这个方法
static id getName(id self, SEL _cmd) //self和_cmd是必须的,在之后可以随意添加其他参数
{
    NSLog(@"static getName called");
    
    // 获取实例变量
    Ivar nameIvar = class_getInstanceVariable([self class], "name");
    // 获取实例变量的值
    id name = object_getIvar(self, nameIvar);
    
    return name;
}

#pragma mark - 变量name的set方法
- (void)setName:(id)name
{
    NSLog(@"setName called");
    
    // 获取实例变量
    Ivar nameIvar = class_getInstanceVariable([self class], "name");
    // 设置实例变量的值
    object_setIvar(self, nameIvar, name);
}

static void setName(id self, SEL _cmd, id name) //self和_cmd是必须的,在之后可以随意添加其他参数
{
    NSLog(@"static setName called");
    
    // 获取实例变量
    Ivar nameIvar = class_getInstanceVariable([self class], "name");
    // 设置实例变量的值
    object_setIvar(self, nameIvar, name);
}

#pragma mark - 测试方法
- (void)firstMethod {
    NSLog(@"firstMethod");
}

- (void)secondMethod {
    NSLog(@"secondMethod");
}

- (void)thirdMethod {
    NSLog(@"thirdMethod");
}

- (void)fourthMethod {
    NSLog(@"fourthMethod");
}

- (NSString *)filePath
{
    NSString *archiverFilePath = [NSString stringWithFormat:@"%@/archiver", NSHomeDirectory()];
    return archiverFilePath;
}


/*
 struct objc_class {
 Class isa  OBJC_ISA_AVAILABILITY;
 
 #if !__OBJC2__
 Class super_class                       OBJC2_UNAVAILABLE;  // 父类
 const char *name                        OBJC2_UNAVAILABLE;  // 类名
 long version                            OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0
 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;
 
 >>为什么Class的第一个成员也是Class呢,它的内存布局岂不是和底下的object一样了?其实这就是类对象(class object)与实例对象(instance object)的区别了。
 Object-C对类对象与实例对象中的 isa 所指向的类结构作了不同的命名:类对象中的 isa 指向类结构被称作 metaclass,metaclass 存储类的static类成员变量与static类成员方法(+开头的方法);实例对象中的 isa 指向类结构称作 class(普通的),class 结构存储类的普通成员变量与普通成员方法(-开头的方法).
 
 **************
 
 struct objc_object {
 Class isa  OBJC_ISA_AVAILABILITY;
 };
 
 typedef struct objc_object *id;
 
 >> id可以用来表示任意一个对象,它是一个 objc_object 结构类型的指针,其第一个成员是一个 objc_class 结构类型的指针。
 一个对象(Object)的isa指向了这个对象的类(Class),而这个对象的类(Class)的isa指向了metaclass。这样我们就可以找到静态方法和变量了。
 
 **************
 
 // 获取类中指定名称实例成员变量的信息
 Ivar class_getInstanceVariable ( Class cls, const char *name );
 
 // 获取类成员变量的信息
 Ivar class_getClassVariable ( Class cls, const char *name );
 
 // 添加成员变量
 BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
 
 // 获取实例方法
 Method class_getInstanceMethod ( Class cls, SEL name );
 
 // 获取类方法
 Method class_getClassMethod ( Class cls, SEL name );
 */

@end

github地址: https://github.com/Jacedy/Runtime

© 著作权归作者所有

共有 人打赏支持
_子墨
粉丝 47
博文 157
码字总数 140970
作品 0
深圳
iOS工程师
加载中

评论(3)

zhangFC
zhangFC
mark
我是柱哥啊
+1
一路南漂
一路南漂
:+1:
iOS高仿QQ侧滑控件、下载框架、动画效果、扫一扫、颜色变化、K线图等源码

iOS精选源码 仿京东"加入购物车"转场动画(http://www.code4app.com/thread-28162-1-1.html) ColorTool(颜色转换)(http://www.code4app.com/thread-29256-1-1.html) Swift 专业版K线(http://w......

sunnyaigd ⋅ 04/17 ⋅ 0

面试官自述:面向高级开发人员的iOS面试问题

当您准备进行技术性iOS面试时,了解您可能会询问哪些主题以及经验丰富的iOS开发人员期望什么是非常重要的。 这是许多硅谷公司用来衡量iOS候选人资历水平的一系列问题。 这些问题涉及iOS开发的...

菇哒微课 ⋅ 04/26 ⋅ 0

iOS逆向工程- 学习整理(工具详解)

前言 一、逆向工程的要求 具备丰富的 iOS 开发经验 最好能非常熟悉 iOS 设备的硬件构成,iOS 系统的运行原理。 拿到任意一个 App 之后能够大致推断出它的项目规模和使用的技术,比如它的MVC模...

_小迷糊 ⋅ 05/11 ⋅ 0

iOS开发:学习Runtime

学习iOS开发,runtime这个知识点是绕不过去的,但对于我这种学习OC不是太久,写OC的量不够多的人来说,抽象理解runtime的概念或者是看源代码有点枯燥,效果也不好,以例子的方法学习可能会更...

Andy_Ron ⋅ 06/01 ⋅ 0

【AR】开始使用Vuforia开发iOS(2)

原 设置iOS开发环境 安装Vuforia iOS SDK 如何安装Vuforia iOS示例 编译并运行Vuforia iOS示例 支持iOS金属 iOS 64位迁移 设置iOS开发环境 适用于iOS的Vuforia引擎目前支持运行iOS 9及更高版...

lichong951 ⋅ 06/11 ⋅ 0

HDU ~ 6297 ~ CCPC直播 (模拟,输出格式控制)

思路:模拟就行了,注意Running和RTE的开头字母一样。 iomanip是I/O流控制头文件,就像printf的格式化输出一样。 以下是一些常用的: dec 置基数为10 相当于"%d" hex 置基数为16 相当于"%X" oc...

zscdst ⋅ 05/29 ⋅ 0

如何正确使用初始化方法的标记宏

本文是我首发在iOS知识小集团队的,欢迎关注微博话题#ios知识小集#。我的微博:halohily 在 Objective-C 中有 designated 和 secondary 初始化方法的观念。如果一个实例的初始化需要多个参数...

halohily ⋅ 05/22 ⋅ 0

苹果对体无完肤的 iOS 11 最后的弥补

点击上方“CSDN”,选择“置顶公众号” 关键时刻,第一时间送达! 距离万众瞩目的苹果开发者大会(WWDC 2018)的召开还有不到一周时间,苹果公司于今天凌晨,正式发布了 iOS 11 的第十四次更...

csdnnews ⋅ 05/30 ⋅ 0

天生不凡ios怎么在电脑上玩 天生不凡ios电脑版玩法教程

天生不凡ios怎么在电脑上玩呢?现在有很多小伙伴都在玩天生不凡手游,不过相对于传统的在手机上进行游戏,很多人都倾向于新玩法,就是在电脑上玩天生不凡手游。下面小编就给亲们介绍下天生不...

kaopu8520 ⋅ 05/29 ⋅ 0

Proxy-Go 全平台 SDK v4.9 来啦!

Proxy-Go 全平台 SDK是proxy使用gombile实现了一份go代码编译为android和ios平台下面可以直接调用的sdk类库, 另外还为linux和windows提供sdk支持,基于这些类库,APP开发者可以轻松的开发出各...

狂奔的蜗牛. ⋅ 06/12 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

JavaScript零基础入门——(八)JavaScript的数组

JavaScript零基础入门——(八)JavaScript的数组 欢迎大家回到我们的JavaScript零基础入门,上一节课我们讲了有关JavaScript正则表达式的相关知识点,便于大家更好的对字符串进行处理。这一...

JandenMa ⋅ 今天 ⋅ 0

sbt网络问题解决方案

转自:http://dblab.xmu.edu.cn/blog/maven-network-problem/ cd ~/.sbt/launchers/0.13.9unzip -q ./sbt-launch.jar 修改 vi sbt/sbt.boot.properties 增加一个oschina库地址: [reposit......

狐狸老侠 ⋅ 今天 ⋅ 0

大数据,必须掌握的10项顶级安全技术

我们看到越来越多的数据泄漏事故、勒索软件和其他类型的网络攻击,这使得安全成为一个热门话题。 去年,企业IT面临的威胁仍然处于非常高的水平,每天都会看到媒体报道大量数据泄漏事故和攻击...

p柯西 ⋅ 今天 ⋅ 0

Linux下安装配置Hadoop2.7.6

前提 安装jdk 下载 wget http://mirrors.hust.edu.cn/apache/hadoop/common/hadoop-2.7.6/hadoop-2.7.6.tar.gz 解压 配置 vim /etc/profile # 配置java环境变量 export JAVA_HOME=/opt/jdk1......

晨猫 ⋅ 今天 ⋅ 0

crontab工具介绍

crontab crontab 是一个用于设置周期性被执行的任务工具。 周期性执行的任务列表称为Cron Table crontab(选项)(参数) -e:编辑该用户的计时器设置; -l:列出该用户的计时器设置; -r:删除该...

Linux学习笔记 ⋅ 今天 ⋅ 0

深入Java多线程——Java内存模型深入(2)

5. final域的内存语义 5.1 final域的重排序规则 1.对于final域,编译器和处理器要遵守两个重排序规则: (1)在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用...

江左煤郎 ⋅ 今天 ⋅ 0

面试-正向代理和反向代理

面试-正向代理和反向代理 Nginx 是一个高性能的反向代理服务器,但同时也支持正向代理方式的配置。

秋日芒草 ⋅ 今天 ⋅ 0

Spring 依赖注入(DI)

1、Setter方法注入: 通过设置方法注入依赖。这种方法既简单又常用。 类中定义set()方法: public class HelloWorldOutput{ HelloWorld helloWorld; public void setHelloWorld...

霍淇滨 ⋅ 昨天 ⋅ 0

马氏距离与欧氏距离

马氏距离 马氏距离也可以定义为两个服从同一分布并且其协方差矩阵为Σ的随机变量之间的差异程度。 如果协方差矩阵为单位矩阵,那么马氏距离就简化为欧氏距离,如果协方差矩阵为对角阵,则其也...

漫步当下 ⋅ 昨天 ⋅ 0

聊聊spring cloud的RequestRateLimiterGatewayFilter

序 本文主要研究一下spring cloud的RequestRateLimiterGatewayFilter GatewayAutoConfiguration @Configuration@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMi......

go4it ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部