iOS 运行时runtime控制私有变量以及私有方法

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

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

1
2
3
4
5
#import <Foundation/Foundation.h>
 
@interface  Father :  NSObject
@property  ( nonatomic , assign)  int  age;
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#import "Father.h"
 
@interface  Father ()
{<br>   NSString  *_name;<br>}<br>
- ( 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都可以让他们无所遁形!先看代码,看不懂不要紧,后面会有解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- ( 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);
     }
}

显示如下:

1
2
3
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同意偷偷修改它呢?我们继续往下做:(接着上面的代码)

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

显示如下:

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

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

(2)控制私有函数

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

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

1
2
3
4
5
6
7
8
9
10
- ( 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);
     }
}

显示如下:

1
2
3
4
5
6
7
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<br><br>

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

class_copyMethodList:获取所有方法

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

=========

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

1
2
3
4
5
6
7
8
9
10
11
- ( 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- ( 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];
}

输入结果:

1
2
3
4
5
6
7
8
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方法吗?答案是可以的,我试验过,尽管无法获得代码提示,但请坚定不移地敲入[father method:xx :xx]方法!

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

1
2
3
4
5
6
7
8
- ( 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]);
}

输出结果:

1
2
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


                     注:本文非原创,转自编程小翁@博客园。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值