-
Notifications
You must be signed in to change notification settings - Fork 2.2k
JSPatch 常见问题
字符串 / 数组 / 字典 操作问题
stringWithFormat
NSNumber 相关问题
for...in
block 相关问题
内存释放问题
JSPatch执行顺序问题
如何获取和修改property
如何获取和修改私有变量
如何修改类方法
如何调用 super 方法
带下划线的方法如何调用/如何替换
如何传 @selector 参数
如何使用和修改常量/宏/静态变量
如何用于Swift
如何调用 C 函数
webView 无法使用问题
dealloc 问题
特殊类的初始化问题
刚使用 JSPatch 经常会对 NSString / NSArray / NSDictionary / NSDate 这四个类的使用感到迷惑,因为 JS 语言本身有对应的这四个类型,会跟 OC 的这四个类混淆。要避免混淆,要弄清楚两点:
1.需要认清这四个类有 JS 跟 OC 两种类型
//OC
@implementation JPTestObject
+ (NSString *)name {
return @"I'm NSString";
}
+ (NSMutableDictionary *)info {
return @{@"k": @"v"};
}
+ (NSArray *)users {
return @[@"alex", @"bang", @"cat"];
}
@end
var ocStr = JPTestObject.name();
var ocInfo = JPTestObject.info();
var ocUsers = JPTestObject.users();
//以上三个是从 OC 返回的 OC 对象,可以调用 OC 方法:
ocStr.rangeOfString("I'm"); //OK
ocInfo.addObject_forKey("a", "b"); //OK
ocUsers.firstObject(); //OK
ocInfo["a"]; //错误,不能使用[]语法操作OC对象
ocUsers[0]; //错误,不能使用[]语法操作OC对象
///////////////////////////////////////
var str = "I'm JS String";
var info = @{"k": "v"};
var users = ["alex", "bang", "cat"];
//以上三个是 JS 对象,不能调用 OC 方法:
str.rangeOfString("I'm"); //错误
info.addObject_forKey("a", "b"); //错误
users.firstObject(); //错误
info["k"]; //正确,可以用[]语法操作JS对象
user[0]; //正确,可以用[]语法操作JS数组
2.若要用JS语法操作这些类型,要确保它是 JS 对象。
//错误:ocStr 不是 JS 对象,不能用 JS 语法拼接字符串
var newStr = ocStr + "js string";
//正确:已用 .toJS() 接口转为 JS 对象,可以用 JS语法操作
var transStr = ocStr.toJS();
var newStr = transStr + "js string";
//错误:ocUsers 不是 JS 对象,不能用[]语法,也不能用 JS 语法遍历
var firstUser = ocUser[0];
for (var i = 0; i < ocUsers.length; i ++) {
var user = ocUsers[i];
}
//正确:已用 .toJS() 接口转为 JS 对象,可以用 JS语法操作
var transArr = ocUsers.toJS();
var firstUser = transArr[0];
for (var i = 0; i < transArr.length; i ++) {
var user = transArr[i];
}
//错误: ocInfo 不是 JS 对象,不能用[]语法
var v = ocInfo['k'];
//正确:已用 .toJS() 接口转为 JS 对象,可以用 JS语法操作
var transDict = ocInfo.toJS();
var v = transDict['k'];
JSPatch 支持调用 stringWithFormat,不过所有参数类型都需改为 %@
:
//OC
[NSString stringWithFormat:@"name:%@, age:%d", @"alex", 12];
//JS
NSString.stringWithFormat("name:%@, age:%@", "alex", 12);
虽然支持 stringWithFormat
,但还是建议使用 JS 语法拼接字符串:
var ret = "name:" + "alex" + " age:(" + 12 + ")";
NSNumber 与上述四个类型不一样,所有数值类型以及 NSNumber 对象到 JS 后都会变成数值,不能再调用这个数值的任何方法:
//OC
@implementation JPTestObject
+ (NSNumber *)returnNSNumber {
return @(42);
}
+ (int)returnInt
{
return 42;
}
@end
var numFromOC = JPTestObject.returnNumber()
var intFromOC = JPTestObject.returnInt()
var jsNum = 42
//以上三个变量是相等的:
console.log(numFromOC == intFromOC == jsNum) //true
//不能调用 NSNumber 方法:
numFromOC.isEqualToNumber(42); //crash
//所有数值操作都应该在 JS 语法下操作:
parseInt(numFromOC);
var all = numFromOC + intFromOC + jsNum;
首先从 OC 返回的 NSArray / NSDictionary 对象是不能直接用 for...in 遍历的,需要调用 .toJS()
后才能进行遍历,详情见上文。
然后在遍历数组时,JavaScript 的 for...in 语法定义与 Objective-C 不同:
//OC
NSArray *arr = @[@"name", @"age"];
for (var o in arr) {
NSLog(@"%@", o); //输出 name age
}
var arr = ["name", "age"];
for (var o in arr) {
console.log(o); //输出 0, 1,表示遍历数组的序号
console.log(arr[o]); //输出 name age,这样才表示数组的值
}
注意:建议不要使用for...in来遍历数组,详细原因请参见mdn。
若使用 block 时出现 crash,最常见的原因是在 block 里使用了 self
变量,应该在 JS 声明另一个变量持有 self,详情见这里。
另外请详读 block 的传递规则,以及使用 block 的几个限制。
如果一个 OC 对象被 JS 引用,或者在 JS 创建这个对象,这个 OC 对象在退出作用域后不会马上释放,而是会等到 JS 垃圾回收时才释放,这会导致一些 OC 对象延迟释放:
defineClass('JPTestObject', {
testObj: function() {
var view = UIView.alloc().init();
//函数执行完退出作用域,或者手动把这个变量置为null,这个 OC 对象都不会立即释放
//在 JS 没有变量持有这些对象后,JS 引擎会在垃圾回收时才释放
view = null;
}
});
没有被 JS 引用过的 OC 对象不受影响。更多讨论见 #69
JSPatch所有动态替换的函数,都必须在JS执行完了之后,第二次再执行,才会全面以新替换的js代码进行工作。
时间顺序
- application:didFinishLaunchingWithOptions:
- JSPatch发起网络请求拉patch
- app的rootViewController触发ViewDidload运行完毕,依然是未修正的错误界面
- JSPatch网络请求拉取回来,执行JS
- JS已经执行成功ViewDidLoad已经被替换,但是界面已经生成,新的正确的ViewDidLoad并不会再次执行
效果:我的viewDidLoad为啥不能修改啊?
比喻:
- viewDidload的函数代码就好比建筑设计图
- 运行起来后的界面就好比建好的建筑
时间顺序:
- viewDidLoad有bug需要改(建筑设计图图纸错了)
- 旧viewDidLoad先执行,并且创建好了界面(工人已经按着错图纸把建筑建好了)
- JSPatch执行了hotfix(设计师修改设计图纸)
- JSPatch看起来没效果(就算你改好了建筑图纸,已经建好的建筑是不会有任何改变的)
解决办法:2个
- 在建造建筑之前,把图纸改好
JSPatch在使用的时候,第一次下载网络请求是要时间的,所以才会发生修改图纸,在建筑建好之后。但是补丁已经下载完成,第二次运行app,新的图纸已经存在本地,是可以在创建rootViewController之前,就先把patch运行,让新图纸生效的。
- 不要修改图纸了,直接去修改建筑
当你网络请求在JSPatch下载完Patch之后,通过callback,进行完全自定义的处理,窗户坏了,直接改窗户,门坏了修门,你也可以自定义把房子推倒了重建
如果你使用的是JSPatchSDK,那么头文件有一个callback的API,JSPatchSDK提供了JS下载完成的这个时机,具体怎么修,纯看使用者自己。
如果你是用的是Github源码,那么自己按着这个思路自行处理
可以用 JSPatch 为类添加 dealloc 方法,但无法覆盖原 OC 上的 dealloc 方法,在执行完 JS 的 dealloc 后会自动执行 OC 上的 dealloc 方法,因为若不执行 OC 的 dealloc 方法,对象无法正常释放,会产生内存泄漏:
@implementation JPViewController
- (void)dealloc {
NSLog(@"dealloc from OC");
}
@end
defineClass('JPViewController', {
dealloc: function() {
console.log('dealloc from JS');
}
}
会输出
dealloc from JS
dealloc from OC
某些Class的alloc方法的返回值是同一值,经过**__bridge_transfer**之后可能会导致后续初始化混乱(可能由释放alloc返回值引起)。所以建议优先使用Class方法来生成对象,而非Instance方法。
此现象在不同版本的系统中有不同的表现,某些版本会引起crash,另外一些不会。有兴趣的同学来做详细研究吧。
目前已知存在此问题的包括:
- NSCalendar
- NSURL
- CAShapeLayer
相关issue:
NSCalendar初始化问题 NSURL 的初始化问题。 NSURL初始化容易crash CAShapeLayer初始化问题