借助前辈的力量综合一下资料.

OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法。利用runtime机制让我们可以在程序运行时动态修改类、对象中的所有属性、方法,就算是私有方法以及私有属性都是可以动态修改的。本文旨在对runtime的部分特性小试牛刀,更多更全的方法可以参考系统API文件<objc/runtime.h>,demo例子可以参见CSDN的runtime高级编程系列文章

我们出发吧!

先看一个非常平常的Father类:

#import <Foundation/Foundation.h>

@interface Father : NSObject
@property (nonatomic, assign) int age;
@end
#import "Father.h"

@interface Father ()
{
  NSString *_name;
} - (void)sayHello; @end @implementation Father - (id)init
{
if (self = [super init]) {
_name = @"wengzilin";
[_name copy];
self.age = 27;
}
return self;
}
- (void)dealloc
{
[_name release];
_name = nil;
[super dealloc];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"name:%@, age:%d", _name, self.age];
}
- (void)sayHello
{
NSLog(@"%@ says hello to you!", _name);
}
- (void)sayGoodbay
{
NSLog(@"%@ says goodbya to you!", _name);
}

如果你没接触过runtime,那当我问你:“Father之外的类能控制的属性有哪些?能控制的方法有哪些?”时,你估计会回答:“我们可以访问age属性,不能访问_name变量;可以访问age的setter/getter方法,其他方法都不行”。这种回答是OK的,因为教科书上以及面向对象的思想告诉我们,事实如此。但是,我会说,有一种方法是APPLE允许的而且可以不受这些规则限制的途径可以做到想访问什么就访问什么、想修改什么就修改什么,那就是本文的主题:RUNTIME!

现在我们简单地将本文的主题分为两部分:(1)控制私有变量  (2)控制私有函数,因为二者所用的runtime差异较大,函数部分会复杂一些

(1)控制变量

想要控制一个类的私有变量,那第一步就要知道这个类到底有哪些隐藏的变量,以及这些隐藏的变量类型是什么。或许你会说:“这不是很显然吗?.h文件都写着呢!”。如果你真这么想就特错特错了,很多正规的写法都是尽量避免在.h文件中出现私有变量,绝大部分都会选择方法.m文件的extension中,extension就是匿名的category。我猜测这也是一种防止hack的措施吧。不管这些变量放在何处,runtime都可以让他们无所遁形!先看代码,看不懂不要紧,后面会有解释:

- (void)tryMember
{
Father *father = [[Father alloc] init];
NSLog(@"before runtime:%@", [father description]); unsigned int count = 0;
Ivar *members = class_copyIvarList([Father class], &count);
for (int i = 0 ; i < count; i++) {
Ivar var = members[i];
const char *memberName = ivar_getName(var);
const char *memberType = ivar_getTypeEncoding(var);
NSLog(@"%s----%s", memberName, memberType);
}
}

显示如下:

2015-03-17 16:10:28.003 WZLCodeLibrary[38574:3149577] before runtime:name:wengzilin, age:27
2015-03-17 16:10:28.003 WZLCodeLibrary[38574:3149577] _name----@"NSString"
2015-03-17 16:10:28.003 WZLCodeLibrary[38574:3149577] _age----i

从log中我们知道了,Father类有两个变量,一个公开的包装成属性的age, 类型是int,一个花括号{}内的私有变量_name,类型是NSString。代码中标红色的部分就是runtime.h的api,

class_copyIvarList:获取类的所有属性变量,count记录变量的数量IVar是runtime声明的一个宏,是实例变量的意思,instance variable,在runtime中定义为 typedef struct objc_ivar *Ivari

var_getName:将IVar变量转化为字符串

ivar_getTypeEncoding:获取IVar的类型

如果我们现在想对_name动手,不经过Father同意偷偷修改它呢?我们继续往下做:(接着上面的代码)

    Ivar m_name = members[0];
object_setIvar(father, m_name, @"zhanfen");
NSLog(@"after runtime:%@", [father description]);

显示如下:

2015-03-17 16:10:28.004 WZLCodeLibrary[38574:3149577] after runtime:name:zhanfen, age:27

我们发现,_name属性被强制改过来了,有wengzilin改为现在zhanfen。

(2)控制私有函数

对于私有变量,我们能做的顶多修改变量的值,但对于私有函数,我们可以玩非常多的花样,比如:在运行时动态添加新的函数、修改私有函数、交换其中两个私有函数的实现、替换私有函数...

同样地,控制的第一步是获得Father类的所有私有方法,我们可以得到.m文件中所有有显式实现的方法以及属性变量的setter+getter方法都会被找到:

- (void)tryMemberFunc
{
unsigned int count = 0;
Method *memberFuncs = class_copyMethodList([Father class], &count);//所有在.m文件显式实现的方法都会被找到
for (int i = 0; i < count; i++) {
SEL name = method_getName(memberFuncs[i]);
NSString *methodName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
NSLog(@"member method:%@", methodName);
}
}

显示如下:

2015-03-17 17:02:33.343 WZLCodeLibrary[38748:3170794] member method:setAge:
2015-03-17 17:02:33.343 WZLCodeLibrary[38748:3170794] member method:age
2015-03-17 17:02:33.344 WZLCodeLibrary[38748:3170794] member method:sayHello
2015-03-17 17:02:33.344 WZLCodeLibrary[38748:3170794] member method:sayGoodbay
2015-03-17 17:02:33.344 WZLCodeLibrary[38748:3170794] member method:description
2015-03-17 17:02:33.344 WZLCodeLibrary[38748:3170794] member method:dealloc
2015-03-17 17:02:33.344 WZLCodeLibrary[38748:3170794] member method:init

Method:runtime声明的一个宏,表示一个方法,typedef struct objc_method *Method;

class_copyMethodList:获取所有方法

method_getName:读取一个Method类型的变量,输出我们在上层中很熟悉的SEL

=========

接下来我们试着添加新的方法试试(这种方法等价于对Father类添加Category对方法进行扩展):

- (void)tryAddingFunction
{
class_addMethod([Father class], @selector(method::), (IMP)myAddingFunction, "i@:i@"); }
//具体的实现,即IMP所指向的方法
int myAddingFunction(id self, SEL _cmd, int var1, NSString *str)
{
NSLog(@"I am added funciton");
return 10;
}
- (void)tryMemberFunc
{
//动态添加方法
[self tryAddingFunction];
count = 0;
memberFuncs = class_copyMethodList([Father class], &count);//所有在.m文件显式实现的方法都会被找到
for (int i = 0; i < count; i++) {
SEL name = method_getName(memberFuncs[i]);
NSString *methodName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
NSLog(@"member method:%@", methodName);
}
//尝试调用新增的方法
Father *father = [[Father alloc] init];
[father method:10 :@"111"];//当你敲入father实例后,是无法获得method的提示的,只能靠手敲。而且编译器会给出"-method" not found的警告,可以忽略
[father release];
}

输出结果:

2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:method::
2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:setAge:
2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:age
2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:sayHello
2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:sayGoodbay
2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:description
2015-03-17 17:02:33.346 WZLCodeLibrary[38748:3170794] member method:dealloc
2015-03-17 17:02:33.346 WZLCodeLibrary[38748:3170794] member method:init

我们可以看到,method::方法的确被添加进类中了。有童鞋会问,如果在其他类文件中实例化Father类,还能调用到-method方法吗?答案是可以的,我试验过,在MRC下尽管无法获得代码提示,但请坚定不移地敲入[father method:xx :xx]方法!(在ARC下会报no visible @interface 错误)

接下来,我们拿系统函数玩玩,目标是让NSString函数的大小写转换功能对调,让APPLE乱套:

- (void)tryMethodExchange
{
Method method1 = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method method2 = class_getInstanceMethod([NSString class], @selector(uppercaseString));
method_exchangeImplementations(method1, method2);
NSLog(@"lowcase of WENG zilin:%@", [@"WENG zilin" lowercaseString]);
NSLog(@"uppercase of WENG zilin:%@", [@"WENG zilin" uppercaseString]);
}

输出结果:

2015-03-17 17:20:16.073 WZLCodeLibrary[38861:3180978] lowcase of WENG zilin:WENG ZILIN
2015-03-17 17:20:16.290 WZLCodeLibrary[38861:3180978] uppercase of WENG zilin:weng zilin

iOS动态性 运行时runtime初探(强制获取并修改私有变量,强制增加及修改私有方法等)的更多相关文章

  1. 【原】iOS动态性(二):运行时runtime初探(强制获取并修改私有变量,强制增加及修改私有方法等)

    OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法.利用runtime机制让我们可以在程序运行时动态修改类.对象中的所有属性.方法,就算是私有方法以及私有属性都是可以动 ...

  2. iOS运行时Runtime浅析

    运行时是iOS中一个很重要的概念,iOS运行过程中都会被转化为runtime的C代码执行.例如[target doSomething];会被转化成objc)msgSend(target,@select ...

  3. Deep Learning部署TVM Golang运行时Runtime

    Deep Learning部署TVM Golang运行时Runtime 介绍 TVM是一个开放式深度学习编译器堆栈,用于编译从不同框架到CPU,GPU或专用加速器的各种深度学习模型.TVM支持来自Te ...

  4. CUDA运行时 Runtime(四)

    CUDA运行时 Runtime(四) 一.     图 图为CUDA中的工作提交提供了一种新的模型.图是一系列操作,如内核启动,由依赖项连接,依赖项与执行分开定义.这允许定义一次图形,然后重复启动.将 ...

  5. CUDA运行时 Runtime(三)

    CUDA运行时 Runtime(三) 一.异步并发执行 CUDA将以下操作公开为可以彼此并发操作的独立任务: 主机计算: 设备计算: 从主机到设备的内存传输: 从设备到主机的存储器传输: 在给定设备的 ...

  6. CUDA运行时 Runtime(二)

    CUDA运行时 Runtime(二) 一. 概述 下面的代码示例是利用共享内存的矩阵乘法的实现.在这个实现中,每个线程块负责计算C的一个方子矩阵C sub,块内的每个线程负责计算Csub的一个元素.如 ...

  7. CUDA运行时 Runtime(一)

    CUDA运行时 Runtime(一)             一. 概述 运行时在cudart库中实现,该库通过静态方式链接到应用程序库cudart.lib和libcudart.a,或动态通过cuda ...

  8. “ compiler-rt”运行时runtime库

    " compiler-rt"运行时runtime库 编译器-rt项目包括: Builtins-一个简单的库,提供了代码生成和其他运行时runtime组件所需的特定于目标的低级接口. ...

  9. 【原】iOS动态性(五)一种可复用且解耦的用户统计实现(运行时Runtime)

    声明:本文是本人 编程小翁 原创,转载请注明. 为了达到更好的阅读效果,强烈建议跳转到这里查看文章. iOS动态性是我的关于iOS运行时的系列文章,由浅入深,从理论到实践.本文是第5篇.有兴趣可以看看 ...

随机推荐

  1. Kaggle实战之二分类问题

    0. 前言 1. MNIST 数据集 2. 二分类器 3. 效果评测 4. 多分类器与误差分析 5. Kaggle 实战 0. 前言 "尽管新技术新算法层出不穷,但是掌握好基础算法就能解决手 ...

  2. JQuery使用笔记

    1.选择器 id选择器: $('#btnShow') class选择器: $('.banner') tag选择器: $('input') 2.常用方法 取 / 设value: $('#btnShow' ...

  3. lua API 小记2

    1. 创建lua虚拟机 lua_State *lua_newstate (lua_Alloc f, void *ud) 创建一个新的独立的lua虚拟机. 参数指定了内存分配策略及其参数, 注意, 让用 ...

  4. Android App插件式换肤实现方案

    背景 目前很多app都具有换肤功能,用户可以根据需要切换不同的皮肤,为使我们的App支持换肤功能,给用户提供更好的体验,在这里对换肤原理进行研究总结,并选择一个合适的换肤解决方案. 换肤介绍 App换 ...

  5. SQL---存储过程---sp_addextendedproperty表字段加描述

    相信很多朋友对利用SQL创建表已经很熟悉了,但我们发现在创建表的同时不能像添加默认值或者主键一样为列加上说明信息,所以我们经常是创建表后再到表的可视化设计器中为列加上说明,这样操作起来就相当麻烦了,本 ...

  6. ccf 火车购票

    import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class Main2 { pu ...

  7. python爬虫实战 获取豆瓣排名前250的电影信息--基于正则表达式

    一.项目目标 爬取豆瓣TOP250电影的评分.评价人数.短评等信息,并在其保存在txt文件中,html解析方式基于正则表达式 二.确定页面内容 爬虫地址:https://movie.douban.co ...

  8. sql执行时间过长,请高手指点!

    需求:查询出每一位"社工员"通过23门社工课进度100%的数量和23门社工课对应的考试通过的数量. 业务解析: 1.社工员--针对特定学员的一批人.在表USERS_SW_REGIS ...

  9. Cesium中Clock控件及时间序列瓦片动态加载

    前言 前面已经写了两篇博客介绍Cesium,一篇整体上简单介绍了Cesium如何上手,还有一篇介绍了如何将Cesium与分布式地理信息处理框架Geotrellis相结合.Cesium的强大之处也在于其 ...

  10. ArrayList与数组间的转换

    关键句:String[] array = (String[])list.toArray(new String[size]); public class Test { public static voi ...