onePatch 修复指南
- 方法名约定
- 修复实例方法
- 修复类方法
- 调用原来的方法实现
- 输出日志
- weak与strong 指针
- 获得一个Class指针
- 调用类方法或实例方法
- 调用实例的父类的方法
- 设置或获取实例的属性值-成员变量
- 创建 block
- nil的使用
方法名约定
所有方法名中的“:”(冒号)需要用“”(下划线)代替。方法名中的每个“”(下划线)用“__”(两个下划线)代替。
示例:
@interface TestView : UIView
- (void)ig_setTitle:(NSString *) forState:(NSInteger)state;
@end
[testView ig_setTitle:'title' forState:1];
对于以上oc方法,需要替换为:
// ig后面本来是一个下划线,需要写成两个,setTitle后面的冒号,用下划线代替
testView.ig__setTitle_forState('title', 1);
修复实例方法
class OPViewController { // 需要修复的方法所在的类名
test(param1, param2) { // 参数会自动带有两个隐藏参数:self和_cmd
// 可以使用super.xxx()调用父类的方法
// something to do
}
anotherMethod() {
// ...
}
}
修复类方法
class OPViewController { // 需要修复的方法所在的类名
static test(param1, param2) { // 参数会自动带有两个隐藏参数:self和_cmd
// 可以使用super.xxx()调用父类的方法
// something to do
}
static anotherMethod() {
// ...
}
}
类方法和实例方法唯一的区别是:方法名前面是否带有static关键字。带static的为类方法。
在一个补丁文件中,允许同时写多个类,但不能在class中嵌套class。
在一个class中,允许同时修复多个方法。
调用原来的方法实现
当您在修复某个方法时,您可以用origin__+方法名()调用原来的方法。注意origin后面是两条下划线。
这个功能非常有用,假设您要修复的方法中,有很多行代码,这些代码中,大部分是没问题的,只有其中一两行有问题, 您并不需要将整个方法用热修复代码重写一遍,只需要先调用原来的方法,再把有问题的代码纠正一下即可,前提是这个方法不会crash。
代码示例
@implementation OPViewController
viewDidLoad() {
// 假设这里有很多代码...
self.title = @"错误的标题";
// 这里也有很多业务代码...
}
}
@end
对于以上的viewDidLoad方法,热修补丁代码的推荐写法为:
class OPViewController {
viewDidLoad() {
self.origin__viewDidLoad(); // 调用原来的viewDidLoad()方法 注意:origin后面是两条`下划线`
self.setTitle('正确的标题'); // 修复代码
}
}
输出日志
class OPViewController {
test(param1, param2) { // 参数会自动带有两个隐藏参数:self和_cmd
// log(xxx)
log('Hello World' + '!');
}
}
weak与strong 指针
class OPViewController {
test(param1, param2) {
var weak_instance = __weak(param1);
// ...
var strong_instance = __strong(param2);
// ...
}
}
获得一个Class指针
// 返回Class指针,等价于:Class uiView = [UIView class];
const UIView = require('UIView');
Class指针常用于调用类方法,或者其他需要用到Class指针的场景,比如isKindOfClass()中的参数。
调用类方法或实例方法
// 调用类方法 require('类名').方法名(参数1, 参数2...)
// 方法调用支持链式调用:xxx.xxx().xxx()...
const instance = require('OPTestCallMethod').alloc().init();
instance.isKindOfClass(require('OPTestCallMethod')); /// true
// 调用实例方法 实例.方法名(实例变量, 方法名, 参数1, 参数2...)
// 这里需要注意方法名中的冒号需要被替换为下划线
instance.testWithArg1_arg2(100, 'Hello World');
调用实例的父类的方法
class OPViewController {
test(param1, param2) {
super.test(param1, param2); // 直接使用super关键字
}
}
设置或获取实例的属性值-成员变量
// 设置实例的属性值
instance.setTitle('OPViewController');
// 获取实例的属性值
const title = instance.title(); // return 'OPViewController'
简而言之,获取/设置属性,其实就是调用setter和getter方法。
如果发现有不能正常使用的情况,或者需要设置或获取没有setter和getter的属性值,比如:
@interface OPViewController() {
NSString *_name;
}
@end
上面的_name属性没有setter和getter,所以需要使用setInstanceProperty和getInstanceProperty来设置或者获取属性值: 代码示例:
// 设置实例的属性值
setInstanceProperty(self, '_name', 'Hello world');
// 获取实例的属性值
const name = getInstanceProperty(self, '_name'); // name is 'Hello world'
创建 block
使用 block 函数来创建 Native block 函数。 block 函数第一个参数为:函数签名编码,第一个字符为返回参数类型编码,后面跟着入参类型编码。参数类型对应的编码字符参考 TypeEncoding。
block 函数第二个参数为回调js函数,回调函数参数为 native block 入参变量。
class OPTestBlock {
testReturnBlock(blk) {
// 函数签名编码:第一个字符为返回参数类型编码,后面跟着入参类型编码。
let blk_obj = block("vcislqCISLQfdB@#", function(c, i, s, l, q, C, I, S, L, Q, f, d, B, anObject, classParm) {
log(c);
log('i=' + i);
log('s=' + s);
log('l=' + l);
log('q=' + q);
log('C=' + C);
log('I=' + I);
log('S=' + S);
log('L=' + L);
log('Q=' + Q);
log('f=' + f);
log('d=' + d);
log('B=' + B);
log(anObject);
var isOPTestClass = self.isKindOfClass(classParm);
log('self class is OPTestClass : ' + isOPTestClass);
});
return blk_obj;
}
}
nil的使用
函数参数、属性读写、函数返回值均支持nil的转换。在JS代码中undefined等同于nil,更推荐使用全局符号nil来表示:
class OPTestNilTransform {
test() {
self.testNil(nil);
self.setState(nil);
return nil;
}
}