runtime运行机制方法学习
runtime这玩意第一次听说时都不知道是什么,经过了解后才知道它就是oc动态语言的机制,没有它那oc就不能称为动态语言。在之前可能大家对runtime了解都不深,随着编程技能的日益加深和需要,大家开始更加关心底层的实现,并用自己更需要的方式实现。这时runtime开始慢慢火起来了,作为一个iOS程序员,如果出去说自己不知道runtime无疑是一件很丢分的事情。由于runtime的底层实现笔者也没搞得太明白,在这里就跟大家提提几个关于runtime的方法。
1.runtime之动态添加属性
在最开始学习oc之时,我相信大家都听过,在类目中是无法添加属性的。而我在这里将要实现给UIButton增加一个block属性,方便实现其点击方法。首先创建一个UIButton的类目(UIButton+Block),接着我们来实现
UIButton+Block.h文件
//定义一个block
typedef void(^ActionBlock)(UIButton *sender); @interface UIButton (Block)
//定义方法当button通过某点击状态使用一个block
- (void)handleClickEvent:(UIControlEvents)aEvent UsingBlock:(ActionBlock)block;
@end
.h文件较为简单一看明白不多做解释,接下来是.m文件
//定义添加属性所对应的关键字
static char *overViewKey;
@implementation UIButton (Block) - (void)handleClickEvent:(UIControlEvents)aEvent UsingBlock:(ActionBlock)block{
//设置关联对象 (被关联者,关键字,关联者,属性状态)
objc_setAssociatedObject(self, &overViewKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
//给button增加点击事件
[self addTarget:self action:@selector(buttonClicked:) forControlEvents:aEvent];
} - (void)buttonClicked:(UIButton *)sender{
//获取关联对象 (被关联者,关键字)
ActionBlock clickedBlock = objc_getAssociatedObject(self, &overViewKey);
//如果block存在则调用block
if (clickedBlock != nil) {
clickedBlock(sender);
}
}
在这里 objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>)
便为runtime方法。此方法有4个参数,第一个为类别:self(给谁增加属性),第二个参数为属性关键字:&overViewKe(作为属性的标示符来查找属性),第三个参数为属性:block(要添加的属性),第四个参数为属性修饰符:OBJC_ASSOCIATION_COPY_NONATOMIC(作为属性的修饰符,nonatomic,copy)。通过此方法我们便可以动态的给一个类增加属性了。
属性增加后我们便需要获取此属性 objc_getAssociatedObject(<#id object#>, <#const void *key#>),此方法就较为简单了。在这里需要注意的是属性关键字,由于要保证属性关键字的作用域,所以在这里使用static作为其修饰符,另外由于runtime机制属于c语言实现,因此这里的关键字需要使用char类型!
这里定义好后的使用就简单了直接一句代码使用
//定义一个uibutton 并使用其方法
[button handleClickEvent:UIControlEventTouchUpInside UsingBlock:^(UIButton *sender) {
NSLog(@"你点了%@按钮",[sender currentTitle]);
}];
这样通过runtime改写的是UIButton的点击事件就更方便理解,阅读了。
2.runtime之归档使用方法
在自定类归档时,我们需要将属性一个一个的写入,在属性较少时可能不觉得有什么问题,但若是属性较多时,此种写法就显得笨拙了,此处我们便利用runtime的方法来动态的获取其属性,并写入。
首先自己新建一个类,这里笔者创建一个BQPerson类
BQPerson.h文件
@interface BQPerson : NSObject <NSCoding>
//假定这里有好几十个属性
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *crad;
@property (nonatomic, copy) NSString *birthday;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) NSString *iphone;
@property (nonatomic, copy) NSString *adress;
@property (nonatomic, assign) CGFloat height;
@property (nonatomic, assign) CGFloat weight; @end
接下来我们便在.m文件中通过runtime来实现其归档
- (id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
//定义长度
unsigned int index = ;
//动态获取成员变量数组
Ivar *ivars = class_copyIvarList([self class], &index);
for (int i = ; i < index; i++) {
//取出变量
Ivar ivar = ivars[i];
//获取变量名
const char *name = ivar_getName(ivar);
//输出属性名字及其在内存偏移量
NSLog(@"%s %td",name,ivar_getOffset(ivar));
//解档:解档调用方法decodeObjectForKey
NSString *key = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:key];
//赋值
[self setValue:value forKey:key];
}
//由于这里是使用的C语言,需要自己手动管理内存
free(ivars);
}
return self;
} - (void)encodeWithCoder:(NSCoder *)aCoder{
//定义长度
unsigned int index = ;
//动态获取类成员变量数组
Ivar *ivars = class_copyIvarList([self class], &index);
for (int i = ; i < index; i++) {
//取出对应的变量
Ivar ivar = ivars[i];
//获取取出的变量名
const char *name = ivar_getName(ivar);
//归档:归档调用方法encodeObject: forKey:
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
//释放获取的变量数组
free(ivars);
}
在这里我们首先使用 class_copyIvarList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>) 方法来获取一个类的成员变量数组,其中第一个参数为一个类:[self class](获取哪个类的成员变量),第二个参数为一个无符号整型指针:index(用以记录数组长度)。接下来我们通过ivar_getName(<#Ivar v#>)来返回一个变量的名字,接着将此名字转化为NSString类型。便可以进行解档和归档了。需要注意的是这里由于使用的是C语言,所以数组需要自己手动释放!
3.runtime之方法调换
在工程项目变得较为庞大后,由于功能需要我们要实现2个方法的交换,若是在代码里一个一个修改相对来说较为困难,在这里我们便可以使用runtime通过几句代码来直接交换我们需要交换的2个方法。示例如下:
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method testfirst = class_getInstanceMethod(self, @selector(testfirst));
Method secondtest = class_getInstanceMethod(self, @selector(secondtest));
if (testfirst != nil && secondtest != nil) {
method_exchangeImplementations(testfirst, secondtest);
}else{
NSLog(@"方法获取失败");
}
});
} - (void)viewDidLoad {
[super viewDidLoad]; [self testfirst];
[self secondtest];
} - (void)testfirst{
NSLog(@"1===%s",__func__);
}
- (void)secondtest{
NSLog(@"2===%s",__func__);
}
按照正常程序来讲在viewDidLoad中应该是先走testfirst方法,接着再走secondtest方法,但这里我们在load(类第一次加载进内存的时候调用)方法中使用
class_getInstanceMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)返回一个方法,其中第一个参数:self(类实例对象),第二个参数:@selector(testfirst)(方法选择器,选择一个方法)。最后我们使用method_exchangeImplementations(<#Method m1#>, <#Method m2#>) 将获取的2个方法进行动态的交换,这样在交换后的方法实现中,此2种方法都会互换,需要注意的是此处交换后,交换的是2个方法的实现地址,如调用[self testfirst]方法时会走secondtest的方法!
4.runtime之使assgin修饰符具有weak修饰符的特性(对象不存在后置为nil特性)
由于此方法实用性不强,只作为理论补充所以简单描述即可,首先给NSObject对象增加一个属性,若对象被销毁前都销毁其属性,那么在属性被销毁的时候就将对象置为nil。具体其做法如下
4.1.重写assgin的set方法
- (void)setTestView:(UIView *)testView{
_testView = testView;
[testView psp_rundealloc:^{
_testView = nil;
}];
}
4.2.写一个NSObject类目,动态增加一个属性
- (void)psp_rundealloc:(void (^)())block{
if (block) {
PSPObjectBlock *pspobjc = [[PSPObjectBlock alloc] initWithBlock:block];
objc_setAssociatedObject(self, "pspobjc", pspobjc, OBJC_ASSOCIATION_RETAIN);
}
}
4.3.pspobjc属性内部实现如下
@interface PSPObjectBlock(){
BlockTest _block;
}
@end
@implementation PSPObjectBlock
- (instancetype)initWithBlock:(BlockTest)block{
self = [super init];
if (self) {
_block = [block copy];
}
return self;
}
//销毁时调用block使非weak属性值变为nil
- (void)dealloc{
if (_block) {
_block();
}
}
有兴趣的朋友可以试一试实现此方法,关于runtime知识的记录分享就到这里了,如果其中有什么错误,望大家指出,谢谢!
runtime运行机制方法学习的更多相关文章
- OC的runtime运行机制
什么是runtime runtime就是一套底层的c语言API(Application Programming Interface)里面包括很多强大实用的c语言类型.c语言函数. 实际上,平时我们编写 ...
- ARP协议格式、ARP运行机制入门学习
相关学习资料 http://baike.baidu.com/view/149421.htm?fromtitle=ARP%E5%8D%8F%E8%AE%AE&fromid=1742212& ...
- oc - runtime运行机制
Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时做的事放到了运行时来处理.同时OC也是一门简单的语言,很大一部分是C的内容,只是在语言层面上加了关键字和语法,真正让OC强大 ...
- IOS runtime运行机制详解(一)
OC运行机制是指,可以运行的时候动态调用函数.因为C语言必须在编译的时候就决定调用哪个函数. 我们平时写的OC代码,它在运行的时候也是转换成了runtime的方式运行的.任何方法调用本质:就是发送一个 ...
- JavaScript运行机制的学习
今天在偶然在网上看到一个JavaScript的面试题,尝试着看了一下,很正常的就做错了,然后给我们前端做,哈哈,他居然也顺理成章做的错了,代码大概是这样的 /*1 下面代码会怎样执行?执行结果是什么* ...
- runtime 运行机制2
Mike_zh QQ:82643885 end: blogTitle 博客的标题和副标题 博客园 首页 新随笔 联系 订阅 <a id="MyLinks1_XMLLink" ...
- runtime 运行机制
// // HKPerson.h // runtimeDemo1 // // Created by 123 on 16/5/23. // Copyright © 2016年 123. All ...
- runtime 运行时机制 完全解读
runtime 运行时机制 完全解读 目录[-] import import 我们前面已经讲过一篇runtime 原理,现在这篇文章主要介绍的是runtime是什么以及怎么用!希望对读者有所帮助! ...
- Objective-C Runtime 运行时之三:方法与消息
基础数据类型 SEL SEL又叫选择器,是表示一个方法的selector的指针,其定义如下: typedef struct objc_selector *SEL; objc_selector结构体的详 ...
随机推荐
- mysqli_multi_query($link, $sql_w);
$sql_w = 'INSERT INTO w1 (wint) VALUES (55);'; $sql_w .= 'INSERT INTO w1 (wint) VALUES (505);'; var_ ...
- android NumberPicker 数组越界的坑
被这个问题耽误了一个多小时... 直接上解决方案,参考红色部分. private void initViews() { wheel = (NumberPicker) findViewById(R.id ...
- git -c diff.mnemonicprefix=false -c core.quotepath=false -c credential.helper=sourcetree fetch origin
git -c diff.mnemonicprefix=false -c core.quotepath=false -c credential.helper=sourcetree fetch origi ...
- LeetCode House Robber
原题链接在这里:https://leetcode.com/problems/house-robber/ 题目: You are a professional robber planning to ro ...
- Git stash 常见用法
Git stash git stash这个命令可以将当前的工作状态保存到git栈,在需要的时候再恢复 1.1 git stash 保存当前的工作区与暂存区的状态,把当前的工作隐藏起来,等以后需要的时 ...
- mac 升级vim
首先,要下载vim的源代码.Vim source archives : vim online,下载7.4的新建一个目录用于安装vim 7.4:sudo mkdir /usr/local进入源代码的sr ...
- DuiLib事件分析(一)——鼠标事件响应
最近在处理DuiLib中自定义列表行元素事件,因为处理方案得不到较好的效果,于是只好一层一层的去剥离DuiLib事件是怎么来的,看能否在某一层截取消息,自己重写. 我这里使用CListContaine ...
- java学习规划
今天在网上看到一位大学生的java学习线路规划,觉得蛮适合我,就详细阅读了一下,规划路线应该适用于大部分学习java语言的人,贴出来与大家共勉. 在学习的过程中,不能急于去学习更多的知识,因为知识是无 ...
- Velocity(5)——#set指令
引用可以让模板设计者生成动态内容,而指令允许设计者真正的负责页面的展现和内容. 指令是以#开头,后面紧跟一个关键字,比如if,else,foreach等.而这个关键字,是可以被放在{}中间的.这是规范 ...
- LaTeX学习教程
本来我对LaTeX不是看好的,毕竟都是命令格式的.觉得有word就足够啦word可视化操作方便快捷. 但是由于要写论文等,在导师要求下潜心学习一下,不知不觉间被LaTeX的强大功能所吸引.现在很多出版 ...