JSPatch 基礎用法

原文: JSPatch 基礎用法

JSPatch Platform


索引

  1. Request
  2. 調(diào)用 OC 方法
    2.1 調(diào)用類方法
    2.2 調(diào)用實例方法
    2.3 參數(shù)傳遞
    2.4 Property
    2.5 方法名轉(zhuǎn)換
  3. defineClass
    3.1 API
    3.2 覆蓋方法
    3.3 覆蓋類方法
    3.4 覆蓋 Category 方法
    3.5 Super
    3.6 Property
    3.7 獲取/修改 OC 定義的 Property
    3.8 動態(tài)新增 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* 參數(shù)
  10. 常亮, 枚舉, 宏定義, 全局變量
    10.1 常量, 枚舉
    10.2 宏定義
    10.3 全局變量
  11. Swift
  12. 加載動態(tài)庫
  13. 調(diào)試

<br />

Require


在使用 Objective-C 類之前需要調(diào)用 require('className’):

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

可以使用逗號,分隔, 一次性導入多個類:

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

或者直接在使用時才調(diào)用 require():

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

<br />

調(diào)用 OC 方法


調(diào)用類方法
var redColor = UIColor.redColor();
調(diào)用實例方法
var view = UIView.alloc().init();
view.setNeedsLayout();
參數(shù)傳遞

跟在 OC 一樣傳遞參數(shù):

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

獲取/修改 Property 等于調(diào)用這個 Property 的 getter / setter 方法, 獲取時記得加():

view.setBackgroundColor(redColor);
var bgColor = view.backgroundColor();
方法名轉(zhuǎn)換

多參數(shù)方法名使用 _ 分隔, 參數(shù)使用 , 分隔:

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,字符串數(shù)組,可省略
@param instanceMethods: 要添加或覆蓋的實例方法
@param classMethods: 要添加或覆蓋的類方法

覆蓋方法

在 defineClass 里面定義 OC 已存在的方法即可覆蓋, 方法名規(guī)則與調(diào)用規(guī)則一樣,使用 _ 分隔:

// 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 即可調(diào)用未覆蓋之前的 OC 原方法:

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

defineClass() 第三個參數(shù)就是要添加或覆蓋的類方法,規(guī)則與上述覆蓋實例方法一致:

// 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 關鍵字, 調(diào)用 super 方法:

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

用調(diào)用 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 
  },
})
動態(tài)新增 Property

可以在 defineClass() 第二個參數(shù)為類新增 property,格式為字符串數(shù)組,使用時與 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 未定義的方法,但所有的參數(shù)類型都是 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

可以在定義時讓一個類實現(xiàn)某些 Protocol 接口,寫法跟 OC 一樣:

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

這樣做的作用是,當添加 Protocol 里定義的方法,而類里沒有實現(xiàn)的方法時,參數(shù)類型不再全是 id ,而是自動轉(zhuǎn)為 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轉(zhuǎn)換成了false。

<br />

NSArray / NSString / NSDictionary


NSArray / NSString / NSDictionary 不會自動轉(zhuǎn)成對應的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 轉(zhuǎn)為對應的 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 函數(shù)作為 block 參數(shù)給 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 里的參數(shù)類型用字符串表示,寫上這個 block 各個參數(shù)的類型,用逗號分隔。NSObject 對象如 NSString *, NSArray *等可以用 id 表示,但 block 對象要用 NSBlock* 表示。
<br />
從 OC 返回給 JS 的 block 會自動轉(zhuǎn)為 JS function,直接調(diào)用即可:

// 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 已經(jīng)是一個普通的 JS function,跟我們上面定義的 JS function 沒有區(qū)別:

// 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));

總結(jié):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 參數(shù)個數(shù)最多支持6個。(若需要支持更多,可以修改源碼)
B. block 參數(shù)類型不能是 double
<br />
另外不支持 JS 封裝的 block 傳到 OC 再傳回 JS 去調(diào)用 (原因: 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 函數(shù)里 JS 傳到 OC 再傳過來的,無法調(diào)用。 
    blk("test block"); 
  }
});

<br />

__weak / __strong


可以在 JS 通過 __weak() 聲明一個 weak 變量,主要用于避免循環(huán)引用。
例如我們在 OC 里為了避免 block 導致的循環(huán)引用,經(jīng)常這樣寫:

- (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() 接口調(diào)用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* 參數(shù)


如果你需要傳遞 id* 參數(shù),像 NSURLConnection 里的這個接口里的 NSError ** :

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

這里傳入的是一個指向 NSObject 對象的指針,在方法里可以修改這個指針指向的對象,調(diào)用后外部可以拿到新指向的對象,對于這樣的參數(shù),首先需要引入 JPMemory 擴展,然后按以下步驟進行傳遞和獲取:

  1. 使用 malloc(sizeof(id)) 創(chuàng)建一個指針
  2. 把指針作為參數(shù)傳給方法
  3. 方法調(diào)用完, 使用 pval() 拿到指針新指向的對象
  4. 使用完后調(diào)用 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 是一個靜態(tài)字符串常量,源碼里看不出它的值,可以先用 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 ,可以通過在某個類或?qū)嵗椒ɡ飳⑺祷兀蛘哂锰砑訑U展的方式提供支持:

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

在類里定義的 static 全局變量無法在 JS 上獲取到,若要在 JS 拿到這個變量,需要在 OC 有類方法或?qū)嵗椒ò阉祷兀?/p>

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', {})

對于調(diào)用已在 swift 定義好的類,也是一樣:

require('demo.ViewController')

需要注意幾點:

  1. 只支持調(diào)用繼承自 NSObject 的 Swift 類
  2. 繼承自 NSObject 的 Swift 類,其繼承自父類的方法和屬性可以在 JS 調(diào)用,其他自定義方法和屬性同樣需要加 dynamic 關鍵字才行。
  3. 若方法的參數(shù)/屬性類型為 Swift 特有(如 Character / Tuple),則此方法和屬性無法通過 JS 調(diào)用。
  4. Swift 項目在 JSPatch 新增類與 OC 無異,可以正常使用。
    詳見這篇文章

<br />

加載動態(tài)庫


對于 iOS 內(nèi)置的動態(tài)庫,若原 APP 里沒有加載,可以通過以下方式動態(tài)加載,以加載 SafariServices.framework 為例:

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

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

<br />

調(diào)試


可以使用 console.log() 打印一個對象,作用相當于 NSLog() ,會直接在 XCode 控制臺打出。console.log() 支持任意參數(shù),但不支持像 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 的調(diào)試工具對 JS 進行斷點調(diào)試,詳見 JS 斷點調(diào)試


Lemon龍說:

如果您在文章中看到了錯誤 或 誤導大家的地方, 請您幫我指出, 我會盡快更改

如果您有什么疑問或者不懂的地方, 請留言給我, 我會盡快回復您

如果您覺得本文對您有所幫助, 您的喜歡是對我最大的鼓勵

如果您有好的文章, 可以投稿給我, 讓更多的 iOS Developer 在簡書這個平臺能夠更快速的成長

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容

  • JSPatch 1.打印console.log(); 2.Protocol 3.Masonry使用JSPatch如...
    BestJoker閱讀 1,565評論 5 9
  • 6.1殷丹種子實踐 感恩: 感恩葉武濱老師講的時間管理。最近逐步的應用在生活和工作中,感覺思路清晰了很多 感恩同事...
    殷丹閱讀 202評論 0 0
  • 不要說古詩詞離我們有多遠,平時用不上,誰說的!這不,中秋佳節(jié)又至,與家人親朋團聚把盞言歡、賞月吃餅之際,不正是吟誦...
    可聞桃杏香閱讀 3,097評論 2 6
  • 今天下午去給女兒送飯看見女兒捂著肚子說不舒服(女兒生理期)我認為那動作聲音都有點過,再加上看見她外套也不穿就又些控...
    Q3陳翠玲閱讀 203評論 0 0
  • 在當下這個和平年代,娃娃們最好的挫折教育,我認為就是體育鍛煉! 今天,我要跟大家分享一件我很驕傲的事情,就是我把娃...
    笨媽先飛閱讀 1,701評論 0 0