1. require
在使用Objective-C類之前需要調(diào)用require('className’):
require('UIView')varview=UIView.alloc().init()
可以用逗號(hào),分隔荷并,一次性導(dǎo)入多個(gè)類:
require('UIView, UIColor')varview=UIView.alloc().init()varred=UIColor.redColor()
或者直接在使用時(shí)才調(diào)用require():
require('UIView').alloc().init()
varredColor=UIColor.redColor();
varview=UIView.alloc().init();view.setNeedsLayout();
跟在OC一樣傳遞參數(shù):
var view = UIView.alloc().init();
var superView = UIView.alloc().init()
superView.addSubview(view)
獲取/修改 Property 等于調(diào)用這個(gè) Property 的 getter / setter 方法,獲取時(shí)記得加():
view.setBackgroundColor(redColor);varbgColor=view.backgroundColor();
多參數(shù)方法名使用_分隔:
varindexPath=require('NSIndexPath').indexPathForRow_inSection(0,1);
若原 OC 方法名里包含下劃線_青扔,在 JS 使用雙下劃線__代替:
// Obj-C: [JPObject _privateMethod];JPObject.__privateMethod()
defineClass(classDeclaration, [properties,] instanceMethods, classMethods)
@paramclassDeclaration: 字符串源织,類名/父類名和Protocol
@paramproperties: 新增property,字符串?dāng)?shù)組微猖,可省略
@paraminstanceMethods: 要添加或覆蓋的實(shí)例方法
@paramclassMethods: 要添加或覆蓋的類方法
1.在 defineClass 里定義 OC 已存在的方法即可覆蓋谈息,方法名規(guī)則與調(diào)用規(guī)則一樣,使用_分隔:
// OC@implementationJPTestObject- (void)tableView:(UITableView *)tableViewdidSelectRowAtIndexPath:(NSIndexPath*)indexPath{}@end
// JSdefineClass("JPTableViewController", {tableView_didSelectRowAtIndexPath:function(tableView,indexPath) {...},})
2.使用雙下劃線__代表原OC方法名里的下劃線_:
// OC@implementationJPTableViewController- (NSArray*)_dataSource{}@end
// JSdefineClass("JPTableViewController", {__dataSource:function() {? },})
3.在方法名前加ORIG即可調(diào)用未覆蓋前的 OC 原方法:
// OC@implementationJPTableViewController- (void)viewDidLoad{}@end
// JSdefineClass("JPTableViewController", {viewDidLoad:function() {self.ORIGviewDidLoad();? },})
defineClass()第三個(gè)參數(shù)就是要添加或覆蓋的類方法凛剥,規(guī)則與上述覆蓋實(shí)例方法一致:
// OC@implementationJPTestObject+ (void)shareInstance{}@end
// JSdefineClass("JPTableViewController", {//實(shí)例方法}, {//類方法shareInstance:function() {...},})
覆蓋 Category 方法與覆蓋普通方法一樣:
@implementationUIView(custom)- (void)methodA{}+ (void)clsMethodB{}@end
defineClass('UIView', {methodA:function() {? }}, {clsMethodB:function() {? }});
使用self.super()接口代表 super 關(guān)鍵字侠仇,調(diào)用 super 方法:
// JSdefineClass("JPTableViewController", {viewDidLoad:function() {self.super().viewDidLoad();? }})
用調(diào)用 getter / setter 的方式獲取/修改已在 OC 定義的 Property:
// OC@interfaceJPTableViewController@property(nonatomic)NSArray*data;@property(nonatomic)NSString*shareURL;@property(nonatomic)NSString*shareTitle;@end@implementationJPTableViewController@end
// JSdefineClass("JPTableViewController", {viewDidLoad:function() {vardata=self.data();//get property valueself.setData(data.toJS().push("JSPatch"));//set property valuevarsel=self;self.bridge().registerHandler_handler('h5ToNativeShareDialog',block('NSDictionary *',function(data,responseCallback) {sel.setShareURL(data.objectForKey('url'));sel.setShareTitle(data.objectForKey('title'));? ? }));})
可以在defineClass()第二個(gè)參數(shù)為類新增 property,格式為字符串?dāng)?shù)組犁珠,使用時(shí)與 OC property 接口一致:
defineClass("JPTableViewController", ['data','totalCount'], {init:function() {? ? self=self.super().init()self.setData(["a","b"])//添加新的 Property (id data)self.setTotalCount(2)returnself? },viewDidLoad:function() {vardata=self.data()//獲取 Property 值vartotalCount=self.totalCount()? },})
使用valueForKey()和setValue_forKey()獲取/修改私有成員變量:
// OC@implementationJPTableViewController{NSArray*_data;}@end
// JSdefineClass("JPTableViewController", {viewDidLoad:function() {vardata=self.valueForKey("_data")//get member variablesself.setValue_forKey(["JSPatch"],"_data")//set member variables},})
可以給一個(gè)類隨意添加 OC 未定義的方法逻炊,但所有的參數(shù)類型都是id:
// OC@implementationJPTableViewController- (void)viewDidLoad{NSString* data = [selfdataAtIndex:@(1)];NSLog(@"%@", data);//output: Patch}@end
// JSvardata=["JS","Patch"]defineClass("JPTableViewController", {dataAtIndex:function(idx) {returnidx
若新增的方法屬于 Protocol 里的接口互亮,需要在 defineClass 的類聲明參數(shù)里指明實(shí)現(xiàn)的 Protocol,詳見(jiàn)下文嗅骄。
可以在定義時(shí)讓一個(gè)類實(shí)現(xiàn)某些 Protocol 接口胳挎,寫法跟 OC 一樣:
defineClass("JPViewController: UIViewController", {})
這樣做的作用是,當(dāng)添加 Protocol 里定義的方法溺森,而類里沒(méi)有實(shí)現(xiàn)的方法時(shí)慕爬,參數(shù)類型不再全是id,而是自動(dòng)轉(zhuǎn)為 Protocol 里定義的類型:
@protocolUIAlertViewDelegate...- (void)alertView:(UIAlertView *)alertViewclickedButtonAtIndex:(NSInteger)buttonIndex;...@end
defineClass("JPViewController: UIViewController ", {viewDidAppear:function(animated) {varalertView=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)? }})
JSPatch原生支持 CGRect / CGPoint / CGSize / NSRange 這四個(gè) struct 類型屏积,用 JS 對(duì)象表示:
// Obj-CUIView *view = [[UIViewalloc]initWithFrame:CGRectMake(20,20,100,100)];[viewsetCenter:CGPointMake(10,10)];[viewsizeThatFits:CGSizeMake(100,100)];CGFloatx = view.frame.origin.x;NSRangerange =NSMakeRange(0,1);
// JSvarview=UIView.alloc().initWithFrame({x:20, y:20, width:100, height:100})view.setCenter({x:10, y:10})view.sizeThatFits({width:100, height:100})varx=view.frame().xvarrange={location:0, length:1}
其他 Struct 類型的支持請(qǐng)參照添加 struct 類型支持
在JS使用字符串代表 Selector:
//Obj-C[selfperformSelector:@selector(viewWillAppear:)withObject:@(YES)];
//JSself.performSelector_withObject("viewWillAppear:",1)
JS 上的null和undefined都代表 OC 的nil医窿,如果要表示NSNull, 用nsnull代替,如果要表示NULL, 也用null代替:
//Obj-C@implemention JPTestObject+ (BOOL)testNull(NSNull*null) {return[nullisKindOfClass:[NSNullclass]]}@end
//JSrequire('JPTestObject').testNull(nsnull)//return 1require('JPTestObject').testNull(null)//return 0
在JS里面判斷是否為空要判斷false:
varurl="";varrawData=NSData.dataWithContentsOfURL(NSURL.URLWithString(url));if(rawData!=null) {}//這樣判斷是錯(cuò)誤的應(yīng)該如下判斷:if(!rawData){}在JSPatch.js源碼里_formatOCToJS方法對(duì)undefined,null,isNil轉(zhuǎn)換成了false炊林。
5. NSArray / NSString / NSDictionary
NSArray / NSString / NSDictionary 不會(huì)自動(dòng)轉(zhuǎn)成對(duì)應(yīng)的JS類型姥卢,像普通 NSObject 一樣使用它們:
//Obj-C@implementationJPObject+ (NSArray*)data{return@[[NSMutableStringstringWithString:@"JS"]]}+ (NSMutableDictionary*)dict{return[[NSMutableDictionaryalloc]init];}@end
// JSrequire('JPObject')varocStr=JPObject.data().objectAtIndex(0)ocStr.appendString("Patch")vardict=JPObject.dict()dict.setObject_forKey(ocStr,'name')console.log(dict.objectForKey('name'))//output: JSPatch
如果要把 NSArray / NSString / NSDictionary 轉(zhuǎn)為對(duì)應(yīng)的 JS 類型,使用.toJS()接口:
// JSvardata=require('JPObject').data().toJS()//data instanceof Array === truedata.push("Patch")vardict=JPObject.dict()dict.setObject_forKey(data.join(''),'name')dict=dict.toJS()console.log(dict['name'])//output: JSPatch
當(dāng)要把 JS 函數(shù)作為 block 參數(shù)給 OC時(shí)渣聚,需要先使用block(paramTypes, function)接口包裝:
// Obj-C@implementationJPObject+ (void)request:(void(^)(NSString*content,BOOLsuccess))callback{callback(@"I'm content",YES);}@end
// JSrequire('JPObject').request(block("NSString *, BOOL",function(ctn,succ) {if(succ)log(ctn)//output: I'm content}))
這里 block 里的參數(shù)類型用字符串表示独榴,寫上這個(gè) block 各個(gè)參數(shù)的類型,用逗號(hào)分隔奕枝。NSObject 對(duì)象如NSString *,NSArray *等可以用id表示棺榔,但 block 對(duì)象要用NSBlock*表示。
從 OC 返回給 JS 的 block 會(huì)自動(dòng)轉(zhuǎn)為 JS function隘道,直接調(diào)用即可:
// Obj-C@implementationJPObjecttypedefvoid(^JSBlock)(NSDictionary*dict);+ (JSBlock)genBlock{NSString*ctn =@"JSPatch";? JSBlock block = ^(NSDictionary*dict) {NSLog(@"I'm%@, version:%@", ctn, dict[@"v"])? };returnblock;}+ (void)execBlock:(JSBlock)blk{}@end
// JSvarblk=require('JPObject').genBlock();blk({v:"0.0.1"});//output: I'm JSPatch, version: 0.0.1
若要把這個(gè)從 OC 傳過(guò)來(lái)的 block 再傳回給 OC症歇,同樣需要再用block()包裝,因?yàn)檫@里blk已經(jīng)是一個(gè)普通的 JS function谭梗,跟我們上面定義的 JS function 沒(méi)有區(qū)別:
// JSvarblk=require('JPObject').genBlock();blk({v:"0.0.1"});//output: I'm JSPatch, version: 0.0.1require('JPObject').execBlock(block("id", blk));
總結(jié):JS 沒(méi)有 block 類型的變量忘晤,OC 的 block 對(duì)象傳到 JS 會(huì)變成 JS function,所有要從 JS 傳 block 給 OC 都需要用block()接口包裝激捏。
在 block 里無(wú)法使用self變量设塔,需要在進(jìn)入 block 之前使用臨時(shí)變量保存它:
defineClass("JPViewController", {viewDidLoad:function() {varslf=self;require("JPTestObject").callBlock(block(function(){//`self` is not available here, use `slf` instead.slf.doSomething();? ? });? }}
從 JS 傳 block 到 OC,有兩個(gè)限制:
A. block 參數(shù)個(gè)數(shù)最多支持6個(gè)缩幸。(若需要支持更多壹置,可以修改源碼)
B. block 參數(shù)類型不能是double/NSBlock/struct類型。
另外不支持 JS 封裝的 block 傳到 OC 再傳回 JS 去調(diào)用(原因見(jiàn)issue #155):
- (void)callBlock:(void(^)(NSString*str))block {}
defineClass('JPTestObject', {run:function() {self.callBlock(block('NSString*',function(str) {console.log(str);? ? ? ? }));? ? },callBlock:function(blk) {//blk 這個(gè) block 是上面的 run 函數(shù)里 JS 傳到 OC 再傳過(guò)來(lái)的表谊,無(wú)法調(diào)用。blk("test block");? ? ? }});
可以在 JS 通過(guò)__weak()聲明一個(gè) weak 變量盖喷,主要用于避免循環(huán)引用爆办。
例如我們?cè)?OC 里為了避免 block 導(dǎo)致的循環(huán)引用,經(jīng)常這樣寫:
- (void)test {? ? __weakidweakSelf = self;? ? [selfsetCompleteBlock:^(){? ? ? ? [weakSelfblabla];? ? }]}
在 JS 對(duì)應(yīng)的可以這樣寫:
varweakSelf=__weak(self)self.setCompleteBlock(block(function(){weakSelf.blabla();}))
若要在使用 weakSelf 時(shí)把它變成 strong 變量课梳,可以用__strong()接口:
varweakSelf=__weak(self)self.setCompleteBlock(block(function(){varstrongSelf=__strong(weakSelf)strongSelf.blabla();}))
使用dispatch_after()dispatch_async_main()dispatch_sync_main()dispatch_async_global_queue()接口調(diào)用GCD方法:
// Obj-Cdispatch_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});
// JSdispatch_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})
如果你需要傳遞id*參數(shù)距辆,像NSURLConnection里的這個(gè)接口里的NSError **:
+ (NSData*)sendSynchronousRequest:(NSURLRequest*)request returningResponse:(NSURLResponse**)response error:(NSError**)error;
這里傳入的是一個(gè)指向 NSObject 對(duì)象的指針余佃,在方法里可以修改這個(gè)指針指向的對(duì)象,調(diào)用后外部可以拿到新指向的對(duì)象跨算,對(duì)于這樣的參數(shù)爆土,首先需要引入JPMemory 擴(kuò)展,然后按以下步驟進(jìn)行傳遞和獲戎畈稀:
使用malloc(sizeof(id))創(chuàng)建一個(gè)指針
把指針作為參數(shù)傳給方法
方法調(diào)用完步势,使用pval()拿到指針新指向的對(duì)象
使用完后調(diào)用releaseTmpObj()釋放這個(gè)對(duì)象
使用free()釋放指針
舉個(gè)例子:
//OC- (void)testPointer:(NSError**)error {NSError*err = [[NSErroralloc]initWithDomain:@"com.jspatch"code:42userInfo:nil];? ? *error = err;}
//JS//malloc() pval() free() is provided by JPMemory extensionrequire('JPEngine').addExtensions(['JPMemory'])varpError=malloc(sizeof("id"))self.testPointer(pError)varerror=pval(pError)if(!error) {console.log("success")}else{console.log(error)}releaseTmpObj(pError)free(pError)
若反過(guò)來(lái)你想在 JS 替換上述-testPointer:方法,構(gòu)建NSError對(duì)象賦給傳進(jìn)來(lái)的指針背犯,可以這樣寫:
defineClass('JPClassName', {testPointer:function(error){vartmp=require('NSError').errorWithDomain_code_userInfo("test",1,null);varnewErrorPointer=getPointer(tmp)memcpy(error, newErrorPointer,sizeof('id'))? ? });
Objective-C 里的常量/枚舉不能直接在 JS 上使用倔矾,可以直接在 JS 上用具體值代替:
//OC[btnaddTarget:selfaction:@selector(handleBtn)forControlEvents:UIControlEventTouchUpInside];
//UIControlEventTouchUpInside的值是1<<6btn.addTarget_action_forControlEvents(self,"handleBtn",1<<6);
或者在 JS 上重新定義同名的全局變量:
//jsvarUIControlEventTouchUpInside=1<<6;btn.addTarget_action_forControlEvents(self,"handleBtn", UIControlEventTouchUpInside);
有些常量字符串,需要在 OC 用 NSLog 打出看看它的值是什么:
//OC[[NSAttributedStringalloc].initWithString:@"str"attributes:@{NSForegroundColorAttributeName: [UIColorredColor]];
上面代碼中NSForegroundColorAttributeName是一個(gè)靜態(tài)字符串常量柱锹,源碼里看不出它的值哪自,可以先用 NSLog 打出它的值再直接寫在 JS 上:
//OCNSLog(@"%@",NSForegroundColorAttributeName)//output 'NSColor'
NSAttributedString.alloc().initWithString_attributes("無(wú)效啊", {'NSColor': UIColor.redColor()});
Objective-C 里的宏同樣不能直接在 JS 上使用。若定義的宏是一個(gè)值禁熏,可以在 JS 定義同樣的全局變量代替壤巷,若定義的宏是程序,可以在JS展開(kāi)宏:
#defineTABBAR_HEIGHT40#defineSCREEN_WIDTH[[UIScreenmainScreen]bounds].size.height[viewsetWidth:SCREEN_WIDTHheight:TABBAR_HEIGHT];
//JSview.setWidth_height(UIScreen.mainScreen().bounds().height,40);
若宏的值是某些在底層才能獲取到的值匹层,例如CGFLOAT_MIN隙笆,可以通過(guò)在某個(gè)類或?qū)嵗椒ɡ飳⑺祷兀蛘哂锰砑訑U(kuò)展的方式提供支持:
@implementationJPMacroSupport+ (void)main:(JSContext *)context{? context[@"CGFLOAT_MIN"] = ^CGFloat() {returnCGFLOAT_MIN;? }}@end
require('JPEngine').addExtensions(['JPMacroSupport'])varfloatMin=CGFLOAT_MIN();
JSPatch 不支持修改宏的值升筏,若要修改撑柔,需要替換所有使用到這個(gè)宏的方法。例如:
#defineVIEW_HEIGHT40@implementationJPMethodDemo+ (void)func{? UIView *view = [[UIViewalloc]initWithFrame:CGRectMake(0,0,100, VIEW_HEIGHT)];? ...}@end
//JSvarVIEW_HEIGHT_NEW=20;defineClass('JPMethodDemo', {func:function() {varview=UIView.alloc().initWithFrame({x:0, y:0, width:100, height:VIEW_HEIGHT_NEW});...}});
在類里定義的static全局變量無(wú)法在 JS 上獲取到您访,若要在 JS 拿到這個(gè)變量铅忿,需要在 OC 有類方法或?qū)嵗椒ò阉祷兀?/p>
staticNSString*name;@implementationJPTestObject+ (NSString*)name{returnname;}@end
varname=JPTestObject.name()//拿到全局變量值
使用defineClass()覆蓋Swift類時(shí),類名應(yīng)為項(xiàng)目名.原類名灵汪,例如項(xiàng)目 demo 里用 Swift 定義了 ViewController 類檀训,在 JS 覆蓋這個(gè)類方法時(shí)要這樣寫:
defineClass('demo.ViewController', {})
對(duì)于調(diào)用已在 swift 定義好的類,也是一樣:
require('demo.ViewController')
需要注意幾點(diǎn):
只支持調(diào)用繼承自NSObject的 Swift 類
繼承自NSObject的 Swift 類享言,其繼承自父類的方法和屬性可以在 JS 調(diào)用峻凫,其他自定義方法和屬性同樣需要加dynamic關(guān)鍵字才行。
若方法的參數(shù)/屬性類型為 Swift 特有(如 Character / Tuple)览露,則此方法和屬性無(wú)法通過(guò) JS 調(diào)用荧琼。
Swift 項(xiàng)目在 JSPatch 新增類與 OC 無(wú)異,可以正常使用。
對(duì)于iOS內(nèi)置的動(dòng)態(tài)庫(kù)命锄,若原 APP 里沒(méi)有加載堰乔,可以通過(guò)以下方式動(dòng)態(tài)加載,以加載SafariServices.framework為例:
var bundle = NSBundle.bundleWithPath("/System/Library/Frameworks/SafariServices.framework");
bundle.load();
加載后就可以使用SafariServices.framework了脐恩。
可以使用console.log()打印一個(gè)對(duì)象镐侯,作用相當(dāng)于NSLog(),會(huì)直接在 XCode 控制臺(tái)打出驶冒。
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拼接字符串