当前位置: 移动技术网 > IT编程>移动开发>IOS > iOS中使用JSPatch框架使Objective-C与JavaScript代码交互

iOS中使用JSPatch框架使Objective-C与JavaScript代码交互

2019年07月24日  | 移动技术网IT编程  | 我要评论

四川金路集团,重生精灵大陆游txt,巧虎宝宝版2009

jspatch是github上一个开源的框架,其可以通过objective-c的run-time机制动态的使用javascript调用与替换项目中的objective-c属性与方法。其框架小巧,代码简洁,并且通过系统的javascriptcore框架与objective-c进行交互,这使其在安全性和审核风险上都有很强的优势。git源码地址:https://github.com/bang590/jspatch。

一、从一个官方的小demo看起

通过cocoapods将jspath集成进一个xcode工程中,在appdelegate类的中编写如下代码:

- (bool)application:(uiapplication *)application didfinishlaunchingwithoptions:(nsdictionary *)launchoptions {
  //开始初始化引擎
  [jpengine startengine];
  //读取js文件
  nsstring *sourcepath = [[nsbundle mainbundle] pathforresource:@"demo" oftype:@"js"];
  nsstring *script = [nsstring stringwithcontentsoffile:sourcepath encoding:nsutf8stringencoding error:nil];
  //运行js文件
  [jpengine evaluatescript:script];
  self.window = [[uiwindow alloc]initwithframe:[uiscreen mainscreen].bounds];
  self.window.rootviewcontroller = [[viewcontroller alloc]init];
  [self.window addsubview:[self genview]];
  [self.window makekeyandvisible];
  return yes;
}

- (uiview *)genview
{
  uiview * view= [[uiview alloc] initwithframe:cgrectmake(0, 0, 320, 320)];
  view.backgroundcolor = [uicolor redcolor];
  return view;
}

在工程中添加一个js文件,编写如下:

  

 require('uiview, uicolor, uilabel')
  //要替换函数的类
  defineclass('appdelegate', {
      //替换函数
        //要替换函数的名称
        genview: function() {
          var view = self.origgenview();
          view.setbackgroundcolor(uicolor.greencolor())
          var label = uilabel.alloc().initwithframe(view.frame());
          label.settext("jspatch");
          label.settextalignment(1);
          view.addsubview(label);
          return view;
      }
  });

运行工程,可以看到genview方法被替换成了js文件中的方法,原本红色的视图被修改成了绿色。

二、使用javascript代码向objective-c中修改或添加方法

jspatch引擎中支持3中方式进行javascript代码的调用,分别是使用javascript字符串进行代码运行,读取本地的javascript文件进行代码运行和获取网络的javascript文件进行代码运行。例如,如果想要通过javascript代码在项目中弹出一个警告框,在objective-c代码中插入如下代码:

- (void)viewdidload {
  [super viewdidload];
  // ‘\'符用于进行换行
  [jpengine evaluatescript:@"\
   var alertview = require('uialertview').alloc().init();\
   alertview.settitle('alert');\
   alertview.setmessage('alertview from js'); \
   alertview.addbuttonwithtitle('ok');\
   alertview.show(); \
   "];
}

开发者也可以动态在objective-c类文件中添加方法,例如在viewcontroller类中编写如下:

- (void)viewdidload {
  [super viewdidload];
  self.view.backgroundcolor = [uicolor whitecolor];
  [jpengine startengine];
  nsstring *sourcepath = [[nsbundle mainbundle] pathforresource:@"demo" oftype:@"js"];
  nsstring *script = [nsstring stringwithcontentsoffile:sourcepath encoding:nsutf8stringencoding error:nil];
  [jpengine evaluatescript:script];
  [self performselectoronmainthread:@selector(creatview) withobject:nil waituntildone:nil];
}

javascript文件代码如下:

 require('uiview, uicolor, uilabel')
  defineclass('viewcontroller', {
      // replace the -genview method
        creatview: function() {
          var view = uiview.alloc().initwithframe({x:20, y:20, width:100, height:100});
          view.setbackgroundcolor(uicolor.greencolor());
          var label = uilabel.alloc().initwithframe({x:0, y:0, width:100, height:100});
          label.settext("jspatch");
          label.settextalignment(1);
          view.addsubview(label);
        self.view().addsubview(view)
      }
  });

除了上面的代码,在viewcontroller.m文件中没有编写任何其他的方法,运行工程,可以看到程序并没有崩溃,viewcontroller执行了creatview方法。

通过上面的示例,我们发现使用jspatch可以做一些十分有趣的事。对于ios应用来说,通过官方渠道appstore进行应用程序的发布要通过人工审核,有时这个审核周期会非常长,如果在开发者在编写代码时留下了一些小漏洞,应用一旦上线,若要修改掉这个bug就十分艰难了。有了jspatch,我们可以想象,如果可以定位到线上应用有问题的方法,使用js文件来修改掉这个方法,这将是多么cool的一件事,事实上,jspatch的主要用途也是可以实现线上应用极小问题的hotfix。

三、javascript与objective-c交互的基础方法

要使用jspatch来进行objective-c风格的方法编写,需要遵守一些javascript与objective-c交互的规则。

1.在javascript文件中使用objective-c类

在编写javascript代码时如果需要用到objective-c的类,必须先对这个类进行require引用,例如,如果需要使用uiview这个类,需要在使用前进行如下引用:

require('uiview')

同样也可以一次对多个objective-c类进行引用:

require('uiview, uicolor, uilabel')

还有一种更加简便的写法,直接在使用的时候对其进行引用:

require('uiview').alloc().init()

2.在javascript文件中进行objective-c方法的调用

在进行objective-c方法的调用时,分为两种,一种是调用类方法,一种是调用类的对象方法。

调用类方法:通过类名打点的方式来调用类方法,格式类似如下,括号内为参数传递:

uicolor.redcolor()

调用实例方法:通过对象打点的方式调用类的实例方法,格式如下,括号内为参数传递:

view.addsubview(label)

对于objective-c中的多参数方法,转化为javascript将参数分割的位置以_进行分割,参数全部放入后面的括号中,以逗号分割,示例如下:

view.setbackgroundcolor(uicolor.colorwithred_green_blue_alpha(0,0.5,0.5,1))
对于objective-c类的属性变量,在javascript中只能使用getter与setter方法来访问,示例如下:

label.settext("jspatch")

提示:如果原objective-c的方法中已经包含了_符号,则在javascript中使用__代替。

3.在javascript中操作与修改objective-c类

jspatch的最大应用是在应用运行时动态的操作和修改类。

重写或者添加类的方法:

在javascript中使用defineclass来定义和修改类中的方法,其编写格式如下所示:

/*
classdeclaration:要添加或者重写方法的类名 字符串 如果此类不存在 则会创建新的类
instancemethods:要添加或者重写的实例方法 {}
classmethods:要添加或者重写的类方法 {}
*/
defineclass(classdeclaration, instancemethods, classmethods)

示例如下:

defineclass('viewcontroller', {
      // replace the -genview method
        newfunc: function() {
          //编写实例方法
          self.view().setbackgroundcolor(uicolor.redcolor())
        }
  
      },{

        myload:function(){
          //编写类方法
        }

      }
      )

如果在重写了类中的方法后要调用原方法,需要使用orig前缀,示例如下:

defineclass('viewcontroller', {
      // replace the -genview method
        viewdidload: function() {
          //编写实例方法
          self.origviewdidload()
        }
  
      }
      )

对于objective-c中super关键字调用的方法,在javascript中可以使用self.super()来调用,例如:

defineclass('viewcontroller', {
      // replace the -genview method
        viewdidload: function() {
          //编写实例方法
          self.super().viewdidload()
        }
  
      }
      )

同样jspatch也可以为类添加临时属性,用于在方法间参数传递,使用set_prop_forkey()来添加属性,使用getprop()来获取属性,注意,jspatch添加的属性不能使用objective-c的setter与getter方法访问,如下:

defineclass('viewcontroller', {
      // replace the -genview method
        viewdidload: function() {
          //编写实例方法
          self.super().viewdidload()
          self.setprop_forkey("jspatch", "data")
        },
        touchesbegan_withevent(id,touch){
          self.getprop("data")
          self.view().setbackgroundcolor(uicolor.redcolor())
        }
  
      }
      )

关于为类添加协议的遵守,和objective-c中遵守协议的方式一致,如下:

defineclass("viewcontroller2: uiviewcontroller <uialertviewdelegate>", {
      viewdidappear: function(animated) {
      var alertview = require('uialertview')
      .alloc()
      .initwithtitle_message_delegate_cancelbuttontitle_otherbuttontitles(
                                        "alert",
                                        "content",
                                        self,
                                        "ok",
                                        null
                                        )
      alertview.show()
      },
      alertview_clickedbuttonatindex:function(alertview, buttonindex) {
      console.log('clicked index ' + buttonindex)
      }
      })

四、javascript与objective-c交互的几种常用类型

1.结构体

在objective-c代码中,我们经常会使用到结构体,jspatch中原生支持的结构体有如下几种:cgpoint,cgsize,cgrect,nsrange。并且这几种结构体在进行界面操作时也会经常使用到。

对于cgrect类型,javascript使用如下代码创建:

  var view = require('uiview').alloc().init()
  view.setframe({x:100,y:100,width:100,height:100})

对于cgpoint类型,javascript使用如下代码创建:

   

view.setcenter({x:200,y:200})

对于cgsize类型,javascript使用如下代码创建:

  var size = {width:200,height:200}
  view.setframe({x:100,y:100,width:size.width,height:size.height})

对于nsrange类型,javascript使用如下代码创建:

  var range = {location: 0, length: 1}

2.选择器selector

对于objective-c中的方法选择器selector,在javascript中使用字符串的形式创建,例如:

self.performselector_withobject("func:", 1)

3.关于空对象

在javascript中,null与undefined都对应于objective-c中的nil,objective-c中的nsnull空对象,在javascript中使用nsnull来代替。

4.在objective-c与javascript中进行block的交互

在javascript与objective-c进行block交互有两种方式,一种是在javascript文件中调用objective-c中的block,一种是将javascript文件中的函数块作为block参数传递给objective-c。

在javascript文件中使用objective-c中的block十分简单,因为javascript中没有block的概念,objective-c会被自动转换为函数,示例如下:

objective-c:

typedef void(^block)(nsstring * str);
@interface viewcontroller ()
@end
@implementation viewcontroller
-(block)getblock{
  block block = ^(nsstring * str){nslog(@"%@",str);};
  return block;
}
@end

javascript:

defineclass("viewcontroller", {
      viewdidappear: function(animated) {
       var func = self.getblock()
        func("123")
      }
      })

在javascript文件中将func作为参数block传递给objective-c就复杂一些,需要使用block()方法进行包装,例如:

objective-c:

@interface viewcontroller ()
@end
@implementation viewcontroller

-(void)run:(void(^)(nsstring * str))block{
  block(@"123");
}
@end

javascript:

defineclass("viewcontroller", {
      viewdidappear: function(animated) {
      //run 方法中需要传入一个block
      self.run(block("nsstring*",function(str){console.log(str)}))
      }
      })

在使用block()方法对javascript中的func进行包装时,block(param1,param2)有两个参数,第1个参数设置func中的参数类型,如果有多个参数,使用逗号分割;第2个参数为func函数体。

注意:在block()包装的func中不可以使用self指针,如果需要使用self,需要在block外进行临时变量的转换,示例如下:

defineclass("viewcontroller", {
      viewdidappear: function(animated) {
      //run 方法中需要传入一个block
      var slf = self
      self.run(block("nsstring*",
              function(str){
              console.log(str)
              slf.log(str)
              }))
      }
      })

在javascript中分别使用__weak()与__strong来声明弱引用与强引用对象,例如:

 var slf = __weak(self)
 var stgsef = __strong(self)

5.关于gcd与枚举

在jspatch中,可以使用如下javascript代码来调用gcd方法:

//阻塞当前线程一定时间
dispatch_after(1.0, function(){ 
})
//为主线程添加异步任务
dispatch_async_main(function(){ 
})
//为主线程添加同步任务
dispatch_sync_main(function(){ 
})
//向全局队列中添加任务
dispatch_async_global_queue(function(){ 
})
jspatch中不可以直接使用objective-c中定义的枚举,但是可以用其枚举的真实值进行传递。例如:

//uicontroleventtouchupinside的值是1<<6
btn.addtarget_action_forcontrolevents(self, "handlebtn", 1<<6);

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网