JSPatch 基础用法

作者: 李国安 | 来源:发表于2016-08-17 16:32 被阅读128次

    原文: JSPatch 基础用法

    JSPatch Platform


    索引

    1. Request
    2. 调用 OC 方法
      2.1 调用类方法
      2.2 调用实例方法
      2.3 参数传递
      2.4 Property
      2.5 方法名转换
    3. defineClass
      3.1 API
      3.2 覆盖方法
      3.3 覆盖类方法
      3.4 覆盖 Category 方法
      3.5 Super
      3.6 Property
      3.7 获取/修改 OC 定义的 Property
      3.8 动态新增 Property
      3.9 私有成员变量
      3.10 添加新方法
      3.11 Protocol
    4. 特殊类型
      4.1 Struct
      4.2 Selector
      4.3 nil
    5. NSArray / NSString / NSDictionary
    6. Block
      6.1 Block 传递
      6.2 Block 里使用 Self 变量
      6.3 限制
    7. __weak / __strong
    8. GCD
    9. 传递 id* 参数
    10. 常亮, 枚举, 宏定义, 全局变量
      10.1 常量, 枚举
      10.2 宏定义
      10.3 全局变量
    11. Swift
    12. 加载动态库
    13. 调试

    <br />

    Require


    在使用 Objective-C 类之前需要调用 require('className’):

    require('UIView')
    var view = UIView.alloc().init()
    

    可以使用逗号,分隔, 一次性导入多个类:

    require('UIView, UIColor')
    var view = UIView.alloc().init()
    var red = UIColor.redColor()
    

    或者直接在使用时才调用 require():

    require('UIView').alloc().init()
    

    <br />

    调用 OC 方法


    调用类方法
    var redColor = UIColor.redColor();
    
    调用实例方法
    var view = UIView.alloc().init();
    view.setNeedsLayout();
    
    参数传递

    跟在 OC 一样传递参数:

    var view = UIView.alloc().init();
    var superView = UIView.alloc().init()
    superView.addSubview(view)
    
    Property

    获取/修改 Property 等于调用这个 Property 的 getter / setter 方法, 获取时记得加():

    view.setBackgroundColor(redColor);
    var bgColor = view.backgroundColor();
    
    方法名转换

    多参数方法名使用 _ 分隔, 参数使用 , 分隔:

    var indexPath = require('NSIndexPath').indexPathForRow_inSection(0, 1);
    

    若原 OC 方法名里包含下划线_, 在 JS 使用双下划线 __ 代替:

    // Obj-C: [JPObject _privateMethod];
    JPObject.__privateMethod()
    

    <br />

    defineClass


    API

    defineClass(classDeclaration, [properties,] instanceMethods, classMethods)
    @param classDeclaration: 字符串,类名/父类名和Protocol
    @param properties: 新增property,字符串数组,可省略
    @param instanceMethods: 要添加或覆盖的实例方法
    @param classMethods: 要添加或覆盖的类方法

    覆盖方法

    在 defineClass 里面定义 OC 已存在的方法即可覆盖, 方法名规则与调用规则一样,使用 _ 分隔:

    // OC
    @implementation JPTestObject
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
      // Code Here
    }
    @end
    
    // JS
    defineClass("JPTableViewController", { 
      tableView_didSelectRowAtIndexPath: function(tableView, indexPath) { 
        ... 
      },
    })
    

    使用双下划线 __ 代表原 OC 方法名里的下划线 _:

    // OC
    @implementation JPTableViewController
    - (NSArray *) _dataSource {
      // Code Here
    }
    @end
    
    // JS
    defineClass("JPTableViewController", { 
      __dataSource: function() {
          // Code Here
       },
    })
    

    在方法名前加 ORIG 即可调用未覆盖之前的 OC 原方法:

    // OC
    @implementation JPTableViewController
    - (void)viewDidLoad {
      // Code Here
    }
    @end
    
    // JS
    defineClass("JPTableViewController", { 
      viewDidLoad: function() { 
        self.ORIGviewDidLoad(); 
      },
    })
    
    覆盖类方法

    defineClass() 第三个参数就是要添加或覆盖的类方法,规则与上述覆盖实例方法一致:

    // OC
    @implementation JPTestObject
    + (void)shareInstance {
      // Code Here
    }
    @end
    
    // JS
    defineClass("JPTableViewController", 
    {
            //实例方法
    }, 
    { 
            //类方法 
          shareInstance: function() {
             ... 
          },
    })
    
    覆盖 Category 方法

    覆盖 Category 方法与覆盖普通方法一样:

    @implementation UIView (custom)
    - (void)methodA {
      // Code Here
    }
    
    + (void)clsMethodB {
      // Code Here
    }
    @end
    
    defineClass('UIView', 
    { 
      methodA: function() { 
        // Code Here
      }
    },
     { 
      clsMethodB: function() {
        // Code Here
      }
    });
    
    Super

    使用 self.super() 接口代表 super 关键字, 调用 super 方法:

    // JS
    defineClass("JPTableViewController", 
    { 
      viewDidLoad: function()
      { 
          self.super().viewDidLoad();
      }
    })
    
    Property
    获取/修改 OC 定义的 Property

    用调用 getter / setter 的方式获取/修改已在 OC 定义的 Property

    // OC
    @interface JPTableViewController
    
    @property (nonatomic) NSArray *data;
    
    @end
    
    @implementation JPTableViewController
    
    @end
    
    // JSdefineClass("JPTableViewController", 
    {
       viewDidLoad: function() {
         var data = self.data(); //get property value 
         self.setData(data.toJS().push("JSPatch"));  //set property value 
      },
    })
    
    动态新增 Property

    可以在 defineClass() 第二个参数为类新增 property,格式为字符串数组,使用时与 OC property 接口一致:

    defineClass("JPTableViewController", ['data', 'totalCount'],
    { 
      init: function() 
      { 
        self = self.super().init() 
        self.setData(["a", "b"]) //添加新的 Property (id data) 
        self.setTotalCount(2) 
        return self 
      },
      viewDidLoad: function() 
      { 
        var data = self.data() //获取 Property 值 
        var totalCount = self.totalCount() 
      },
    })
    
    私有成员变量

    使用 valueForKey()setValue_forKey() 获取/修改私有成员变量:

    // OC
    @implementation JPTableViewController
    { 
      NSArray *_data;
    }
    @end
    
    // JS
    defineClass("JPTableViewController",
     { 
      viewDidLoad: function()
       { 
        var data = self.valueForKey("_data") //get member variables 
        self.setValue_forKey(["JSPatch"], "_data") //set member variables
      },
    })
    
    添加新方法

    可以给一个类随意添加 OC 未定义的方法,但所有的参数类型都是 id :

    // OC
    @implementation JPTableViewController
    - (void)viewDidLoad 
    { 
      NSString* data = [self dataAtIndex:@(1)]; 
      NSLog(@"%@", data); //output: Patch
    }
    @end
    
    // JS
    var data = ["JS", "Patch"]
    defineClass("JPTableViewController", 
    { 
      dataAtIndex: function(idx) 
      { 
        return idx < data.length ? data[idx]: "" 
      }
    })
    
    Protocol

    可以在定义时让一个类实现某些 Protocol 接口,写法跟 OC 一样:

    defineClass("JPViewController: UIViewController<UIScrollViewDelegate, UITextViewDelegate>", 
    {
      // Code Here
    })
    

    这样做的作用是,当添加 Protocol 里定义的方法,而类里没有实现的方法时,参数类型不再全是 id ,而是自动转为 Protocol 里定义的类型:

    @protocol UIAlertViewDelegate <NSObject>
    ...
    - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex;
    ...
    @end
    
    defineClass("JPViewController: UIViewController <UIAlertViewDelegate>", 
    {
       viewDidAppear: function(animated) 
      { 
        var alertView = require('UIAlertView') 
            .alloc() 
            .initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles( 
              "Alert", 
              self.dataSource().objectAtIndex(indexPath.row()), 
              self, 
              "OK", 
              null ) 
         alertView.show()
      } 
    
      alertView_clickedButtonAtIndex: function(alertView, buttonIndex) 
      { 
        console.log('clicked index ' + buttonIndex) 
      }
    })
    

    <br />

    特殊类型


    Struct

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

    // Obj-C
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 100, 100)];
    [view setCenter:CGPointMake(10,10)];
    [view sizeThatFits:CGSizeMake(100, 100)];
    CGFloat x = view.frame.origin.x;
    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})
    view.sizeThatFits({width: 100, height:100})
    var x = view.frame().x
    var range = {location: 0, length: 1}
    

    其他 Struct 类型的支持请参照 添加 Struct 类型支持

    Selector

    在JS使用字符串代表 Selector:

    /Obj-C
    [self performSelector:@selector(viewWillAppear:) withObject:@(YES)];
    
    //JS
    self.performSelector_withObject("viewWillAppear:", 1)
    
    nil

    JS 上的 nullundefined 都代表 OC 的 nil ,如果要表示 NSNull , 用 nsnull 代替,如果要表示 NULL , 也用 null 代替:

    //Obj-C
    @implemention JPTestObject
    + (BOOL)testNull(NSNull *null) 
    { 
      return [null isKindOfClass:[NSNull class]];
    }
    @end
    
    //JS
    require('JPTestObject').testNull(nsnull) //return 1
    require('JPTestObject').testNull(null) //return 0
    

    在JS里面判断是否为空要判断false:

    var url = "";
    var rawData = NSData.dataWithContentsOfURL(NSURL.URLWithString(url));
    if (rawData != null) {} //这样判断是错误的
    
    // 应该如下判断:
    if (!rawData){}
    // 在JSPatch.js源码里_formatOCToJS方法对undefined,null,isNil转换成了false。
    

    <br />

    NSArray / NSString / NSDictionary


    NSArray / NSString / NSDictionary 不会自动转成对应的JS类型,像普通 NSObject 一样使用它们:

    //Obj-C
    @implementation JPObject
    + (NSArray *)data
    { 
      return @[[NSMutableString stringWithString:@"JS"]];
    }
    
    + (NSMutableDictionary *)dict
    { 
      return [[NSMutableDictionary alloc] init];
    }
    
    @end
    
    // JS
    require('JPObject')var 
    ocStr = JPObject.data().objectAtIndex(0)
    ocStr.appendString("Patch")
    
    var dict = JPObject.dict()
    dict.setObject_forKey(ocStr, 'name')
    console.log(dict.objectForKey('name')) //output: JSPatch
    

    如果要把 NSArray / NSString / NSDictionary 转为对应的 JS 类型,使用 .toJS() 接口:

    // JS
    var data = require('JPObject').data().toJS()
    //data instanceof Array === true
    data.push("Patch")
    
    var dict = JPObject.dict()
    dict.setObject_forKey(data.join(''), 'name')
    dict = dict.toJS()
    console.log(dict['name']) //output: JSPatch
    

    <br />

    Block


    Block 传递

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

    // Obj-C
    @implementation JPObject
    + (void)request:(void(^)(NSString *content, BOOL success))callback
    { 
      callback(@"I'm content", YES);
    }
    @end
    
    // JS
    require('JPObject').request(block("NSString *, BOOL", function(ctn, succ)
    { 
      if (succ) log(ctn) //output: I'm content
    }))
    

    这里 block 里的参数类型用字符串表示,写上这个 block 各个参数的类型,用逗号分隔。NSObject 对象如 NSString *, NSArray *等可以用 id 表示,但 block 对象要用 NSBlock* 表示。
    <br />
    从 OC 返回给 JS 的 block 会自动转为 JS function,直接调用即可:

    // Obj-C
    @implementation JPObject
    
    typedef void (^JSBlock)(NSDictionary *dict);
    
    + (JSBlock)genBlock
    { 
      NSString *ctn = @"JSPatch";
       JSBlock block = ^(NSDictionary *dict) { 
        NSLog(@"I'm %@, version: %@", ctn, dict[@"v"]) ;
      }; 
      return block;
    }
    + (void)execBlock:(JSBlock)blk
    {
      // Code Here
    }
    @end
    
    // JS
    var blk = require('JPObject').genBlock();
    blk({v: "0.0.1"}); //output: I'm JSPatch, version: 0.0.1
    

    若要把这个从 OC 传过来的 block 再传回给 OC,同样需要再用 block() 包装,因为这里 blk 已经是一个普通的 JS function,跟我们上面定义的 JS function 没有区别:

    // JS
    var blk = require('JPObject').genBlock();
    blk({v: "0.0.1"}); //output: I'm JSPatch, version: 0.0.1
    require('JPObject').execBlock(block("id", blk));
    

    总结:JS 没有 block 类型的变量,OC 的 block 对象传到 JS 会变成 JS function,所有要从 JS 传 block 给 OC 都需要用 block() 接口包装。

    Block 里使用 Self 变量

    在 block 里无法使用 self 变量,需要在进入 block 之前使用临时变量保存它:

    defineClass("JPViewController", 
    { 
      viewDidLoad: function() 
      { 
        var slf = self; 
        require("JPTestObject").callBlock(block(function() 
        { 
          //`self` is not available here, use `slf` instead. 
          slf.doSomething(); 
        }); 
      }
    }
    
    限制

    从 JS 传 block 到 OC,有两个限制:
    A. block 参数个数最多支持6个。(若需要支持更多,可以修改源码)
    B. block 参数类型不能是 double
    <br />
    另外不支持 JS 封装的 block 传到 OC 再传回 JS 去调用 (原因: issue #155)

    - (void)callBlock:(void(^)(NSString *str))block
    {
      // Code Here
    }
    
    defineClass('JPTestObject', 
    { 
      run: function() 
      { 
        self.callBlock(block('NSString*', function(str) 
        { 
          console.log(str); 
        })); 
      }, 
    
      callBlock: function(blk) 
      { 
        //blk 这个 block 是上面的 run 函数里 JS 传到 OC 再传过来的,无法调用。 
        blk("test block"); 
      }
    });
    

    <br />

    __weak / __strong


    可以在 JS 通过 __weak() 声明一个 weak 变量,主要用于避免循环引用。
    例如我们在 OC 里为了避免 block 导致的循环引用,经常这样写:

    - (void)test 
    { 
      __weak id weakSelf = self; 
      [self setCompleteBlock:^() { 
        [weakSelf blabla]; 
      }]
    }
    

    在 JS 对应的可以这样写:

    var weakSelf = __weak(self)
    self.setCompleteBlock(block(function() { 
      weakSelf.blabla();
    }))
    

    若要在使用 weakSelf 时把它变成 strong 变量,可以用 __strong() 接口:

    var weakSelf = __weak(self)
    self.setCompleteBlock(block(function() { 
      var strongSelf = __strong(weakSelf) 
      strongSelf.blabla();
    }))
    

    <br />

    GCD


    使用 dispatch_after() dispatch_async_main() dispatch_sync_main() dispatch_async_global_queue() 接口调用GCD方法:

    // Obj-C
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 
      // do something
    });
    
    dispatch_async(dispatch_get_main_queue(), ^{ 
      // do something
    });
    
    // JS
    dispatch_after(1.0, function() { 
      // do something
    })
    
    dispatch_async_main(function() { 
      // do something
    })
    
    dispatch_sync_main(function() { 
      // do something
    })
    
    dispatch_async_global_queue(function() { 
      // do something
    })
    

    <br />

    传递 id* 参数


    如果你需要传递 id* 参数,像 NSURLConnection 里的这个接口里的 NSError ** :

    + (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error;
    

    这里传入的是一个指向 NSObject 对象的指针,在方法里可以修改这个指针指向的对象,调用后外部可以拿到新指向的对象,对于这样的参数,首先需要引入 JPMemory 扩展,然后按以下步骤进行传递和获取:

    1. 使用 malloc(sizeof(id)) 创建一个指针
    2. 把指针作为参数传给方法
    3. 方法调用完, 使用 pval() 拿到指针新指向的对象
    4. 使用完后调用 releaseTmpObj() 释放这个对象
    5. 使用 free() 释放指针

    举个例子:

    //OC
    - (void)testPointer:(NSError **)error { 
      NSError *err = [[NSError alloc]initWithDomain:@"com.jspatch" code:42 userInfo:nil];
      *error = err;
    }
    
    //JS
    //malloc() pval() free() is provided by JPMemory extension
    require('JPEngine').addExtensions(['JPMemory'])
    
    var pError = malloc(sizeof("id"))
    self.testPointer(pError)
    var error = pval(pError)
    
    if (!error) { 
      console.log("success")
    } else { 
      console.log(error)
    }
    
    releaseTmpObj(pError)
    free(pError)
    

    若反过来你想在 JS 替换上述 -testPointer: 方法,构建 NSError 对象赋给传进来的指针,可以这样写:

    defineClass('JPClassName', 
    { 
      testPointer: function(error)
      { 
        var tmp = require('NSError').errorWithDomain_code_userInfo("test", 1, null); 
        var newErrorPointer = getPointer(tmp) 
        memcpy(error, newErrorPointer, sizeof('id')) 
      }
    });
    

    <br />

    常亮, 枚举, 宏定义, 全局变量


    常量, 枚举

    Objective-C 里的常量/枚举不能直接在 JS 上使用,可以直接在 JS 上用具体值代替:

    //OC
    [btn addTarget:self action:@selector(handleBtn) forControlEvents:UIControlEventTouchUpInside];
    
    //UIControlEventTouchUpInside的值是1<<6
    btn.addTarget_action_forControlEvents(self, "handleBtn", 1<<6);
    

    或者在 JS 上重新定义同名的全局变量:

    //js
    var UIControlEventTouchUpInside = 1 << 6;
    btn.addTarget_action_forControlEvents(self, "handleBtn", UIControlEventTouchUpInside);
    

    有些常量字符串,需要在 OC 用 NSLog 打出看看它的值是什么:

    //OC
    [[NSAttributedString alloc].initWithString:@"str" attributes:@{NSForegroundColorAttributeName: [UIColor redColor]];
    

    上面代码中 NSForegroundColorAttributeName 是一个静态字符串常量,源码里看不出它的值,可以先用 NSLog 打出它的值再直接写在 JS 上:

    //OC
    NSLog(@"%@", NSForegroundColorAttributeName) //output 'NSColor'
    
    NSAttributedString.alloc().initWithString_attributes("无效啊", {'NSColor': UIColor.redColor()});
    
    宏定义

    Objective-C 里的宏同样不能直接在 JS 上使用。若定义的宏是一个值,可以在 JS 定义同样的全局变量代替,若定义的宏是程序,可以在JS展开宏:

    #define TABBAR_HEIGHT 40
    #define SCREEN_WIDTH [[UIScreen mainScreen] bounds].size.height
    [view setWidth:SCREEN_WIDTH height:TABBAR_HEIGHT];
    
    //JS
    view.setWidth_height(UIScreen.mainScreen().bounds().height, 40);
    

    若宏的值是某些在底层才能获取到的值,例如 CGFLOAT_MIN ,可以通过在某个类或实例方法里将它返回,或者用添加扩展的方式提供支持:

    @implementation JPMacroSupport
    + (void)main:(JSContext *)context
    { 
      context[@"CGFLOAT_MIN"] = ^CGFloat() { 
        return CGFLOAT_MIN; 
      }
    }
    @end
    
    全局变量

    在类里定义的 static 全局变量无法在 JS 上获取到,若要在 JS 拿到这个变量,需要在 OC 有类方法或实例方法把它返回:

    static NSString *name;
    @implementation JPTestObject
    + (NSString *)name
    { 
      return name;
    }
    @end
    
    var name = JPTestObject.name() //拿到全局变量值
    

    <br />

    Swift


    使用 defineClass() 覆盖 Swift 类时,类名应为 项目名.原类名 ,例如项目 demo 里用 Swift 定义了 ViewController 类,在 JS 覆盖这个类方法时要这样写:

    defineClass('demo.ViewController', {})
    

    对于调用已在 swift 定义好的类,也是一样:

    require('demo.ViewController')
    

    需要注意几点:

    1. 只支持调用继承自 NSObject 的 Swift 类
    2. 继承自 NSObject 的 Swift 类,其继承自父类的方法和属性可以在 JS 调用,其他自定义方法和属性同样需要加 dynamic 关键字才行。
    3. 若方法的参数/属性类型为 Swift 特有(如 Character / Tuple),则此方法和属性无法通过 JS 调用。
    4. Swift 项目在 JSPatch 新增类与 OC 无异,可以正常使用。
      详见这篇文章

    <br />

    加载动态库


    对于 iOS 内置的动态库,若原 APP 里没有加载,可以通过以下方式动态加载,以加载 SafariServices.framework 为例:

    var bundle = NSBundle.bundleWithPath("/System/Library/Frameworks/SafariServices.framework");
    bundle.load();
    

    加载后就可以使用 SafariServices.framework 了。

    <br />

    调试


    可以使用 console.log() 打印一个对象,作用相当于 NSLog() ,会直接在 XCode 控制台打出。console.log() 支持任意参数,但不支持像 NSLog 这样 NSLog(@"num:%f", 1.0) 的拼接:

    var view = UIView.alloc().init();
    var str = "test";
    var num = 1;
    console.log(view, str, num)
    console.log(str + num);   //直接在JS拼接字符串
    

    也可以通过 Safari 的调试工具对 JS 进行断点调试,详见 JS 断点调试


    Lemon龙说:

    如果您在文章中看到了错误 或 误导大家的地方, 请您帮我指出, 我会尽快更改

    如果您有什么疑问或者不懂的地方, 请留言给我, 我会尽快回复您

    如果您觉得本文对您有所帮助, 您的喜欢是对我最大的鼓励

    如果您有好的文章, 可以投稿给我, 让更多的 iOS Developer 在简书这个平台能够更快速的成长

    相关文章

      网友评论

        本文标题:JSPatch 基础用法

        本文链接:https://www.haomeiwen.com/subject/tpbmsttx.html