JSPatch 踩坑日记

写给蠢蠢的自己

一定要记得时刻加括号括号括号()()()啊小伙伴们 一些小细节坑了我一下午


JSPatch 踩坑日记_第1张图片

1. 私有成员变量
============举个栗子==============

// OC
@implementation JPTestViewController { 
       BOOL _hasRead;
}
@end
// JS
defineClass("JPTableViewController", { 
      viewDidLoad: function() {
          var hasRead = self.valueForKey("_hasRead");         //获取成员变量
          self.setValue_forKey(false, "_hasRead");            //设置成员变量
      },
});

2. 特殊类型
JSPatch原生支持 CGRect / CGPoint / CGSize / NSRange 这四个 struct 类型,用 JS 对象表示:

// OC
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 100, 100)];
[view setCenter:CGPointMake(10,10)];
CGSize size = CGSizeMake(100, 100);
CGFloat height = size.height;
[view sizeThatFits:size];
CGFloat x = view.frame.origin.x;
CGFloat width = view.frame.size.width;
NSRange range = NSMakeRange(0, 1);
// JS
var view = UIView.alloc().initWithFrame({x:20, y:20, width:100, height:100});
view.setCenter({x: 10, y: 10});
var size = {width: 100, height:100};
var height = size.height;
view.sizeThatFits(size);
var x = view.frame().x;
var width = view.frame().width;
var range = {location: 0, length: 1};

3. 字符串拼接

  • 方法一 stringWithFormat
    JSPatch 支持调用 stringWithFormat,不过所有参数类型都需改为 %@:
//OC
[NSString stringWithFormat:@"name:%@, age:%d", @"alex", 12];
//JS
NSString.stringWithFormat("name:%@, age:%@", "alex", 12);
  • 方法二 JS语法拼接
    虽然支持 stringWithFormat,但还是建议使用 JS 语法拼接字符串:
    (此处有坑 切记要将字符串转为JS对象才能操作这些类型,详情请见下面的字符串 / 数组 / 字典 操作问题)
//JS
var ret = "name:" + "alex" + " age:(" + 12 + ")"; 
//注:JS中将int类型转为字符串方法为: 
var age = 12;
var ageStr = 12+"";

4. NSNumber 相关问题
NSNumber 与上述四个类型不一样,所有数值类型以及 NSNumber 对象到 JS 后都会变成数值,不能再调用这个数值的任何方法:
翻译过来就是 JS里不存在NSNumber这玩意儿!只能当数值用 所有跟NSNumber有关的方法例如isEqualToNumber或者stringValue等都不能用。

5. 字符串 / 数组 / 字典 操作问题
刚使用 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
//JS
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
///////////////////////////////////////
var str = "I'm JS String";
var info = @{"k": "v"};
var users = ["alex", "bang", "cat"];
//以上三个是 JS 对象,不能调用 OC 方法:
str.rangeOfString("I'm"); //crash
info.addObject_forKey("a", "b"); //crash
users.firstObject(); //crash
@end
  1. 若要用JS语法操作这些类型,要确保它是 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 ++) {       //注:JS中数组长度为.length不是.count
        var user = transArr[i];
}
//错误: ocInfo 不是 JS 对象,不能用[]语法
var v = ocInfo['k'];
//正确:已用 .toJS() 接口转为 JS 对象,可以用 JS语法操作
var transDict = ocInfo.toJS();
var v = transDict['k'];

============举个栗子==============

//JS
textField_shouldChangeCharactersInRange_replacementString: function(textField, range, string) {
        var str = "string: " + string;    //❌错误写法 传进来的string是OC对象 不能使用JS语法拼接
        var str = "string: " + string.toJS();   //✅正确  先将string转成JS对象 进行拼接
        return true;
},

既然讲到了NSArray,这里顺便讲讲数组遍历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
}
//JS
var arr = ["name", "age"];
for (var o in arr) { 
      console.log(o); //输出 0, 1,表示遍历数组的序号 
      console.log(arr[o]); //输出 name age,这样才表示数组的值
}

============再来个栗子==============

    tableView_cellForRowAtIndexPath: function(tableView, indexPath)
    {
        var identifier = "Cell";
        var cell = tableView.dequeueReusableCellWithIdentifier(identifier);
        if (!cell) {
            cell = UITableViewCell.alloc().initWithStyle_reuseIdentifier(0, identifier);
        }
        //错误一
        for (var a in cell.contentView().subviews()) {    
            a.removeFromSuperview();
        }
        //错误原因:需要调用.JS()才能使用for...in方法遍历 如用OC对象 则可以使用以下方法遍历
        for (int i = 0; index < cell.contentView().subviews().count(); i++) { 
            var subView = cell.contentView().subviews().objectAtIndex(i);
            subView.removeFromSuperview();
        }
        //或者可以使用以下方法 讲OC对象转成JS对象进行for...in遍历
        //此处有坑 错误二
        var subViews = cell.contentView().subviews().toJS();
        for (var a in subViews) {
            var.removeFromSuperview();    //❌错误 JS中输入的var表示遍历数组的序号 会crash
        }
        //正确姿势
        for (var a in subViews) {
            subViews[a].removeFromSuperview();
        }
        cell.setBackgroundColor(UIColor.whiteColor());
        cell.textLabel().setText("");
        return cell;
    },

6. Block
当要把 JS 函数作为 block 参数给 OC时,需要先使用 block(paramTypes, function) 接口包装:

//OC
@implementation JPObject
+ (void)request:(void(^)(NSString *content, BOOL success))callback{   
     if (success){
          NSLog(@"%@",content);    
          [self doSomething];
     }
}
@end
//JS
//在 block 里无法使用 self 变量,需要在进入 block 之前使用临时变量保存它
var slf = self;
//weak变量跟strong变量的申明
var weakSelf = __weak(self);
require('JPObject').request(block("NSString *, BOOL", function(content, success) { 
      if (success){
          console.log(content);    
          slf.doSomething();
          //若要在使用 weakSelf 时把它变成 strong 变量,可以用 __strong() 接口:
          var strongSelf = __strong(weakSelf)
      }
}))     //这里注意括号有没有包错

7. 常量、枚举、宏

  • Objective-C 里的常量/枚举不能直接在 JS 上使用,可以直接在 JS 上用具体值代替或者在 JS 上重新定义同名的全局变量.
  • Objective-C 里的同样不能直接在 JS 上使用。若定义的宏是一个值,可以在 JS 定义同样的全局变量代替,若定义的宏是程序,可以在JS展开宏.
    此外,可以通过在某个类或实例方法里将它返回,或者用添加扩展的方式提供支持
    ============举个栗子==============

新建一个文件JPMacroSupport(随便取个名)继承自JPExtension,记得在JPMacroSupport.h里导入头文件#import

JSPatch 踩坑日记_第2张图片
11139459-6EE2-4C02-BD39-DA30C1A3FE7C.png

上图中我对Masonry中的装箱宏做了扩展支持,下面将结合masonry和扩展宏进行自动布局实例操作。

//JS
   viewDidLoad: function() {
       self.ORIGviewDidLoad();      //在方法名前加 ORIG 即可调用未覆盖前的 OC 原方法
       self.masonryTest();
   },
    masonryTest:function()
    {
         require('JPEngine').addExtensions(['JPMacroSupport']);
        var view = UIView.alloc().init();
        view.setBackgroundColor(UIColor.redColor());
        self.view().addSubview(view);
        view.mas__makeConstraints(block('MASConstraintMaker *',function(make)
        {
            make.left().equalTo()(self.view()).offset()(20);
            make.right().equalTo()(self.view()).offset()(-20);
            make.top().equalTo()(self.view()).offset()(80);
            make.height().equalTo()(MASBoxValue(KScreenHeight()-100));
        }));
    },

补充一下,看到有些同学说定义宏扩展在调用的时候会报NSMallocBlock的错,因为在调用的时候忘记加括号,一定要记住加括号,比如KScreenHeight()这个

8. 其他的坑

  • 关于对象的比较
    viewA和viewB比较在OC里可以用viewA == viewB,但是在JS里进行类似的比较却是会出问题的,在OC里面相等而在JS里面未必相等,不要使用==判断。
    正确办法是用isEqual来进行viewA和viewB的比较。

你可能感兴趣的:(JSPatch 踩坑日记)