【原】iOS动态性(二):运行时runtime初探(强制获取并修改私有变量,强制增加及修改私有方法等)
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 = ;
}
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 = ;
Ivar *members = class_copyIvarList([Father class], &count);
for (int i = ; i < count; i++) {
Ivar var = members[i];
const char *memberName = ivar_getName(var);
const char *memberType = ivar_getTypeEncoding(var);
NSLog(@"%s----%s", memberName, memberType);
}
}
显示如下:
-- ::28.003 WZLCodeLibrary[:] before runtime:name:wengzilin, age:
-- ::28.003 WZLCodeLibrary[:] _name----@"NSString"
-- ::28.003 WZLCodeLibrary[:] _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[];
object_setIvar(father, m_name, @"zhanfen");
NSLog(@"after runtime:%@", [father description]);
显示如下:
-- ::28.004 WZLCodeLibrary[:] after runtime:name:zhanfen, age:
我们发现,_name属性被强制改过来了,有wengzilin改为现在zhanfen。
(2)控制私有函数
对于私有变量,我们能做的顶多修改变量的值,但对于私有函数,我们可以玩非常多的花样,比如:在运行时动态添加新的函数、修改私有函数、交换其中两个私有函数的实现、替换私有函数...
同样地,控制的第一步是获得Father类的所有私有方法,我们可以得到.m文件中所有有显式实现的方法以及属性变量的setter+getter方法都会被找到:
- (void)tryMemberFunc
{
unsigned int count = ;
Method *memberFuncs = class_copyMethodList([Father class], &count);//所有在.m文件显式实现的方法都会被找到
for (int i = ; i < count; i++) {
SEL name = method_getName(memberFuncs[i]);
NSString *methodName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
NSLog(@"member method:%@", methodName);
}
}
显示如下:
-- ::33.343 WZLCodeLibrary[:] member method:setAge:
-- ::33.343 WZLCodeLibrary[:] member method:age
-- ::33.344 WZLCodeLibrary[:] member method:sayHello
-- ::33.344 WZLCodeLibrary[:] member method:sayGoodbay
-- ::33.344 WZLCodeLibrary[:] member method:description
-- ::33.344 WZLCodeLibrary[:] member method:dealloc
-- ::33.344 WZLCodeLibrary[:] 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 ;
}
- (void)tryMemberFunc
{
//动态添加方法
[self tryAddingFunction];
count = ;
memberFuncs = class_copyMethodList([Father class], &count);//所有在.m文件显式实现的方法都会被找到
for (int i = ; 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: :@""];//当你敲入father实例后,是无法获得method的提示的,只能靠手敲。而且编译器会给出"-method" not found的警告,可以忽略
[father release];
}
输出结果:
-- ::33.345 WZLCodeLibrary[:] member method:method::
-- ::33.345 WZLCodeLibrary[:] member method:setAge:
-- ::33.345 WZLCodeLibrary[:] member method:age
-- ::33.345 WZLCodeLibrary[:] member method:sayHello
-- ::33.345 WZLCodeLibrary[:] member method:sayGoodbay
-- ::33.345 WZLCodeLibrary[:] member method:description
-- ::33.346 WZLCodeLibrary[:] member method:dealloc
-- ::33.346 WZLCodeLibrary[:] 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]);
}
输出结果:
-- ::16.073 WZLCodeLibrary[:] lowcase of WENG zilin:WENG ZILIN
-- ::16.290 WZLCodeLibrary[:] uppercase of WENG zilin:weng zilin
我们可以看到,结果乱套了!
私有函数控制还有其他多种玩法,这里就不一一举例了,算是抛砖引玉吧!
=======================================================
原创文章,转载请注明 编程小翁@博客园,邮件zilin_weng@163.com,欢迎各位与我在C/C++/Objective-C/机器视觉等领域展开交流!
=======================================================
【原】iOS动态性(二):运行时runtime初探(强制获取并修改私有变量,强制增加及修改私有方法等)的更多相关文章
- iOS动态性 运行时runtime初探(强制获取并修改私有变量,强制增加及修改私有方法等)
借助前辈的力量综合一下资料. OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法.利用runtime机制让我们可以在程序运行时动态修改类.对象中的所有属性.方法,就算是 ...
- iOS运行时Runtime浅析
运行时是iOS中一个很重要的概念,iOS运行过程中都会被转化为runtime的C代码执行.例如[target doSomething];会被转化成objc)msgSend(target,@select ...
- CUDA运行时 Runtime(二)
CUDA运行时 Runtime(二) 一. 概述 下面的代码示例是利用共享内存的矩阵乘法的实现.在这个实现中,每个线程块负责计算C的一个方子矩阵C sub,块内的每个线程负责计算Csub的一个元素.如 ...
- CUDA运行时 Runtime(一)
CUDA运行时 Runtime(一) 一. 概述 运行时在cudart库中实现,该库通过静态方式链接到应用程序库cudart.lib和libcudart.a,或动态通过cuda ...
- CUDA运行时 Runtime(四)
CUDA运行时 Runtime(四) 一. 图 图为CUDA中的工作提交提供了一种新的模型.图是一系列操作,如内核启动,由依赖项连接,依赖项与执行分开定义.这允许定义一次图形,然后重复启动.将 ...
- CUDA运行时 Runtime(三)
CUDA运行时 Runtime(三) 一.异步并发执行 CUDA将以下操作公开为可以彼此并发操作的独立任务: 主机计算: 设备计算: 从主机到设备的内存传输: 从设备到主机的存储器传输: 在给定设备的 ...
- Deep Learning部署TVM Golang运行时Runtime
Deep Learning部署TVM Golang运行时Runtime 介绍 TVM是一个开放式深度学习编译器堆栈,用于编译从不同框架到CPU,GPU或专用加速器的各种深度学习模型.TVM支持来自Te ...
- “ compiler-rt”运行时runtime库
" compiler-rt"运行时runtime库 编译器-rt项目包括: Builtins-一个简单的库,提供了代码生成和其他运行时runtime组件所需的特定于目标的低级接口. ...
- 【原】iOS动态性(五)一种可复用且解耦的用户统计实现(运行时Runtime)
声明:本文是本人 编程小翁 原创,转载请注明. 为了达到更好的阅读效果,强烈建议跳转到这里查看文章. iOS动态性是我的关于iOS运行时的系列文章,由浅入深,从理论到实践.本文是第5篇.有兴趣可以看看 ...
随机推荐
- 列表视图(ListView和ListActivity)
在ListView中显示网络图片 ImageView 类虽然有一个 setImageUri 方法,但不能直接接受一个由网络地址生成的uri作为参数从而显示图片,我们只好使用其 setImageBitm ...
- Android数据库表的创建和数据升级操作
之前的文章有提到,可以在xml文件中配置数据库信息:http://www.cnblogs.com/wenjiang/p/4492303.html,现在就讲如何利用这些信息类构建数据库. xml文件大概 ...
- JS魔法堂:函数重载 之 获取变量的数据类型
Brief 有时我们需要根据入参的数据类型来决定调用哪个函数实现,就是说所谓的函数重载(function overloading).因为JS没有内置函数重载的特性,正好给机会我们思考和实现一套这样的机 ...
- 用php怎么写一个用户注册登录的页面呢?
想写就会尽快去写.如果用php写了就一定要用nodejs写出来啊,不写是小狗啊! 补充一下,想要实现的功能: 1.用户名重复检测 2.检测信息填写是否完整 3.邮箱是否已经被注册 4.实现ajax无刷 ...
- 使用.NET 4.0+ 操作64位系统中的注册表
一.64位系统中的注册表 以 LocalMachine 中的启动项为例: 64位应用的注册表位置还是在: SOFTWARE\Microsoft\Windows\CurrentVersion\Run 而 ...
- ADB pm 命令
usage: pm list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_TER] pm list permission-grou ...
- Winform防止程序重复运行
需求:1.点击“关闭”按钮时,程序最小化到托盘,并没有退出,这时再次运行程序,不会重复运行,而是显示已运行的程序:2.支持不同目录:3.支持修改名称. 代码(不支持修改名称,不支持不同目录): usi ...
- 获取MS SQL TABLE列名列表
在MS SQL Server中,想获取表的所有列名,可以使用下面SQL语句: SELECT [COLUMN_NAME] FROM [INFORMATION_SCHEMA].[Columns] WHER ...
- 基于C#的MongoDB数据库开发应用(3)--MongoDB数据库的C#开发之异步接口
在前面的系列博客中,我曾经介绍过,MongoDB数据库的C#驱动已经全面支持异步的处理接口,并且接口的定义几乎是重写了.本篇主要介绍MongoDB数据库的C#驱动的最新接口使用,介绍基于新接口如何实现 ...
- 【vbs】vbs写ini文件
这两天在折腾给一个项目打安装包,第一次接触软件打包,用的Advanced Installer(以下简称AI),应该说如果安装过程没有特殊动作(常规动作指释放文件.写注册表.建快捷方式等)的话,倒挺傻瓜 ...