【原】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 = 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

 本文转自编程小翁博客园博客,原文链接:http://www.cnblogs.com/wengzilin/p/4344952.html,如需转载请自行联系原作者

相关文章
|
1月前
|
移动开发 前端开发 数据安全/隐私保护
iOS发布证书.p12文件无密码解决办法及导出带密码的新.p12文件方法
iOS发布证书.p12文件无密码解决办法及导出带密码的新.p12文件方法
28 0
|
3月前
|
存储 监控 iOS开发
iOS应用崩溃了,如何通过崩溃手机连接电脑查找日志方法
在iOS应用开发过程中,调试日志和奔溃日志是开发者必不可少的工具。当iOS手机崩溃时,我们可以连接电脑并使用Xcode Console等工具来查看日志。然而,这种方式可能不够方便,并且处理奔溃日志也相当繁琐。克魔助手的出现为开发者带来了极大的便利,本文将详细介绍其功能和使用方法。 克魔助手会提供两种日志,一种是实时的,一种的是崩溃的。(由于崩溃日志的环境很麻烦,目前只展示实时日志操作步骤)
|
3月前
|
存储 iOS开发 开发者
使用克魔助手进行iOS数据抓包和HTTP抓包的方法详解
使用克魔助手进行iOS数据抓包和HTTP抓包的方法详解
47 0
|
3月前
|
安全 编译器 开发工具
​iOS安全加固方法及实现
​iOS安全加固方法及实现
32 0
​iOS安全加固方法及实现
|
4月前
|
iOS开发 开发者
📝 App备案与iOS云管理式证书 ,公钥及证书SHA-1指纹的获取方法
在iOS应用程序开发过程中,进行App备案并获取公钥及证书SHA-1指纹是至关重要的步骤。本文将介绍如何通过appuploader工具获取iOS云管理式证书 Distribution Managed 公钥及证书SHA-1指纹,帮助开发者更好地理解和应用该过程。
|
3月前
|
小程序 前端开发 Android开发
解决小程序中textarea ios端样式不兼容的两种方法
解决小程序中textarea ios端样式不兼容的两种方法
|
1月前
|
Android开发 iOS开发 开发者
App备案-iOS云管理式证书 Distribution Managed 公钥及证书SHA-1指纹的获取方法
App备案-iOS云管理式证书 Distribution Managed 公钥及证书SHA-1指纹的获取方法
89 0
|
1月前
|
安全 编译器 开发工具
​iOS安全加固方法及实现
​iOS安全加固方法及实现
21 0
|
2月前
|
安全 编译器 开发工具
iOS 安全加固方法及实现
iOS 安全加固方法及实现
|
2月前
|
安全 算法 数据安全/隐私保护
iOS 代码加固与保护方法详解 - 提升 iOS 应用安全性的关键步骤
iOS 代码加固与保护方法详解 - 提升 iOS 应用安全性的关键步骤