RunTime简介及实践 - 刘康
前言
今年年初的面试中,基本上超过了60%的公司会问RunTime的一些用法。
在很多开源项目中用到了RunTime的方法,使我们理解起来比较吃力。
RunTime确实很强大,借助它可以实现很酷的功能。比如JSPatch
。
我本身对于RunTime的理解也没有很深入,所以这里讨论的主要是RunTime的具体应用场景。此番探索希望能起到抛砖引玉的作用,大家可以将RunTime应用起来。
简介
- RunTime简称运行时。是一套底层的
C语言API
,包含很多强大实用的C语言数据类型和C语言函数,平时我们编写的ObjC代码,底层都是基于runtime实现的。ObjC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制。比如:
1 | // ***********发送消息*********** |
- 对于C语言,函数的调用在编译的时候会决定调用哪个函数。
- 对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
- 事实证明:
- 在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。
- 在编译阶段,C语言调用未实现的函数就会报错。
实践
一. 方法交换
- 开发使用场景:系统自带的方法功能不够,给系统自带的方法扩展一些功能,并且保持原有的功能。
- 方式一:继承系统的类,重写方法.
- 方式二:使用runtime,交换方法.
例如,有需求如下:
给imageNamed方法提供功能,每次加载图片就判断图片是否加载成功。
runtime实现思路:
- 写一个UIImage分类,在分类中定义一个能加载图片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
- 交换imageNamed和imageWithName的实现,就能调用imageWithName,间接调用imageWithName的实现。
1 | @implementation UIImage (image) |
二. 动态添加方法
- 开发使用场景:如果一个类,方法非常多,加载类到内存的时候也比较耗费资源,因为需要给每个方法生成映射表。可以使用动态给某个类添加方法解决。
- 面试题:有没有使用
performSelector
,其实主要想问你有没有动态添加过方法。
例如,给person类增加walk的功能, 默认person,没有实现walk方法,通过performSelector调用会报错。但当我动态添加了方法调用[p performSelector:@selector(walk)]
就不会报错了:
1 | // 默认方法都有两个隐式参数 |
三. 给分类添加属性
- 原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。
例如:给NSObject
添加一个name
属性。
思路:在NSObject中,setName方法中设置关联值,name方法中通过取出关联值。达到添加属性的效果。
1 | // 定义关联的key |
四. 更便捷的archive/unarchive
需求:实现对象的归档和解档
思路:利用Runtime实现快速遍历对象的所有属性,减少大量类似于self.property=[aDecoder decodeObjectForKey:type];
以及[aCoder encodeObject:self.property forKey:type]
这种代码。
1 | - (void)encodeWithCoder:(NSCoder *)encoder{ |
五. 字典转模型
需求:能不能自动根据一个字典,生成对应的属性。
- 思路一:KVC。通过KVC方法
setValuesForKeysWithDictionary
可以进行转换。- 弊端:必须保证模型中的属性和字典中的key一一对应。
- 如果不一致,就会调用
[<Status 0x7fa74b545d60> setValue:forUndefinedKey:]
报key找不到的错。 - 分析:模型中的属性和字典的key不一一对应,系统就会调用setValue:forUndefinedKey:报错。
- 解决:重写对象的setValue:forUndefinedKey:,把系统的方法覆盖, 就能继续使用KVC,字典转模型了。
- 如果不一致,就会调用
- 弊端:必须保证模型中的属性和字典中的key一一对应。
思路二:RunTime。利用运行时,遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值。
1 | @implementation NSObject (Model) |
Swift Runtime
- 纯Swift的类,不能通过runtime获取到属性与方法。比如tuple:纯Swift类的函数调用已经不再是OC的运行时发消息objc_msgsend,而是类似C++的vtable,在编译时就确定了调用什么函数,所以runtime获取不到。
- 继承于NSObject的类依然拥有动态性,所以可以拿的到。
- @objc
- @objc是用来将Swift的API导出给OC与OC runtime使用的,如果你继承NSObject的类,将会被自动的加入这个标识。
- dynamic
- 加了@objc标识的方法、属性都无法保证都会被运行时调用,因为Swift会做静态优化。要想完全被动态调用就要使用dynamic修饰词了。使用这个标识也会隐形的加入@objc。这也就解释了为什么上边VC中的方法无法被替换了,被Swift优化成静态调用了,而ViewDidAppear本身为OC的方法,拥有动态特性,所以我们加入dynamic关键字
小结
- Runtime是运行时特性 ,方法调用的本质是给对象发消息:
objc_msgSend
- class_getClassMethod: 获取类方法地址
- class_getInstanceMethod: 获取实例方法地址
- method_exchangeImplementations:交换方法的实现
+ (BOOL)resolveInstanceMethod:(SEL)sel
: 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.- class_addMethod:给实例添加方法
- objc_setAssociatedObject:给对象设置关联值
- objc_getAssociatedObject:获取对象的关联值
- class_copyPropertyList:获得指向该类所有属性的指针
- property_getName:取出属性指针对应的名称,返回字符串(char*)
- class_copyIvarList:获取指向该类所有成员属性指针
- ivar_getName:取出成员属性对应的名称
参考
Runtime简介: http://www.jianshu.com/p/94657b7d31d0
Swift Runtime: http://www.jianshu.com/p/9c36a5b7820a
MJExtension: https://github.com/CoderMJLee/MJExtension
SourceTree rebase 用法 - 曾铭
push with --rebase
push 前调整本地的 commits tree
- Add some commits append to the branch
- Right-click on a commit and hit
Rebase children of <commit-id> interactively...
- Try
reorder
orsquash
oredit message
ordelete
- Click
OK
- Enjoy
the history tree
注意:
- 仅适用于 local branch 还没有 push 的情况!!!
- push 时不要使用
-f
参数
参考
- Git Book 中文版 - rebase
- 团队开发里频繁使用 git rebase 来保持树的整洁好吗? - SegmentFault
- 三個使用版本控制系統的建議 | ihower { blogging } 推荐此人博客
- Interactive rebase in SourceTree | Atlassian Blogs
附带一提
- 对于同一个 repo, 本地 clone 一份即可, 用 branchs 切换不同状态. 否则每个 repo 都要 sync( pull&push ) 很麻烦
- 控制写代码的节奏, 半小时整理思路, 小步提交(起来走走喝杯水), 善用
amend last commit
和rebase
, 便于 reviewer 理解
Firebase - 王胜
Firebase是一个帮助你快速开发高质量的应用程序,增强你的用户基础,并提高盈利能力的移动开发平台。Firebase有很多互补性的功能组成,你可以根据具体需求,找到适用的功能模块搭配使用。
功能介绍
总体功能预览:
分析功能
Firebase的核心功能是Firebases分析,一个免费且没有限制的分析解决方案。通过一个统一的Dashboard看板,可以查看用户的行为以及属性的定量分析。同时支持iOS和Android平台:
- 最多可以无限制报告500个时间类型,每一个时间类型可支持25个属性
- 统一的dashbaord查看用户行为以及跨网络性能分析
- 人口分布,包括年龄、性别以及位置信息等
- 提供可导出的BigQuery自定义查询
Develop
构建更好的应用程序,并预留接口给开发者。节省关键的开发时间,产出高质量、无bug的应用程序。
- 云端消息
提供可靠的跨平台的消息分发和接受通道 - 认证
提供健壮的认证机制 - 实时数据
实时存储和同步应用数据 - 存储
便捷的文件存储 - 寄主
快速分发网络内容 - 远程配置
可远程自定义应用的配置信息 - 测试Lab
提供云端测试功能 - Crash报告
保证应用的稳定性
Grow
在恰当的时机,培养并吸引合适的用户。促进潜在用户的增长。
- 通知
恰当的时机吸引用户 - App索引
驱动有效的搜索流量到你的App - 动态链接
应用内发送动态链接,引导用户到正确的地方 - 邀请
引导用户分享你的应用程序 - AdWords广告
通过Google搜索获取用户
Earn
通过向全球用户展示吸引的广告赚取到收入。
- AdMob
通过吸引性的广告盈利
开发使用
前置条件
- 一个运行Google Play服务9.0.0以上版本的Android设备
- 通过 Android SDK Manager 获取Google Play services
- Android Studio 1.5或者更高版本
- 一个Android Studio项目和包名
注意事项:
Android Studio低于2.2版本中的Instant Run存在Firebase Analytics和特定事件保护不兼容问题,官方建议禁用 Instant run或者升级Android Studio到2.2预览版。
创建Firebase项目
- 访问 控制台,创建一个Firebase项目。
- 添加Firebase到应用程序
选择项目 -> 项目设置 -> 将Firebase添加到您的Android/iOS/网页应用,按照向导一步一步完成配置。 - 选择需要的功能模块,加入项目中
具体的开发文档,参见官网