Skip to content

JSPatch 常见问题

bang edited this page May 6, 2017 · 38 revisions

索引

字符串 / 数组 / 字典 操作问题
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'];

stringWithFormat

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 与上述四个类型不一样,所有数值类型以及 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;

for...in

首先从 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 相关问题

若使用 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的执行顺序问题

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源码,那么自己按着这个思路自行处理

dealloc 问题

可以用 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,另外一些不会。有兴趣的同学来做详细研究吧。

目前已知存在此问题的包括:

  1. NSCalendar
  2. NSURL
  3. CAShapeLayer

相关issue:

NSCalendar初始化问题 NSURL 的初始化问题。 NSURL初始化容易crash CAShapeLayer初始化问题

Clone this wiki locally