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

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);
时间: 2016-06-18

js判断客户端是iOS还是Android等移动终端的方法

判断原理: JavaScript是前端开发的主要语言,我们可以通过编写JavaScript程序来判断浏览器的类型及版本.JavaScript判断浏览器类型一般有两种办法,一种是根据各种浏览器独有的属性来分辨,另一种是通过分析浏览器的userAgent属性来判断的.在许多情况下,值判断出浏览器类型之后,还需判断浏览器版本才能处理兼容性问题,而判断浏览器的版本一般只能通过分析浏览器的userAgent才能知道. 浏览器类型 ⑴浏览器特有属性 ⑵根据userAgent 浏览器版本 ⑴根据userAge

javascript实现阻止iOS APP中的链接打开Safari浏览器

上次根据网上的教程给自己的网站弄了一个Web APP,但是给用户的感觉却十分糟糕. 问题说明: 怎么了?原来是打开WEB APP后在主页上随意打开连接,就会自作主张地打开Safari浏览器.原来好好的伪装和心情就全被破坏掉了.这该如何是好?原来解决方法十分简单.仅仅加入这些代码就好了.实验测试在本人的 iPhone (iOS 7.1)和iPod (iOS 6.1.4)上测试通过,根据原作者的叙述,最新的 iOS 7.0.4(iPhone 与 iPad)测试通过,代码应该兼容性不错,在这里分享:

js和html5实现手机端刮刮卡抽奖效果完美兼容android/IOS

绝对值得看的来篇,哈哈.本人亲自完成,有错误请大家指出: 现在的手机完美支持html5,所以如果手机端想要做个抽奖模块的话,用刮刮卡抽奖效果,相信这个互动体验是非常棒的 ​ps:由于本人没有wp8系统的手机,所以没法兼容wp8系统的,目前完美兼容android,IOS 如果要在pc浏览的话,得改下js,目前支持谷歌,火狐,ie>=10,如果网友想要的话我就去写个 代码如下: 复制代码 代码如下: <!DOCTYPE html> <html lang="en"&g

iOS开发之用javascript调用oc方法而非url

先来看看如何在项目中的webview上面点击一个按钮,就能达到调用oc代码 上面的这个页面是webview里面嵌套的一个项目的网页,打印订单点击之后(点击事件是一个js方法),需要调用oc里面集成好的蓝牙打印机功能,来完成打印. 所以这里只能用js代码来直接调用oc代码. 1.首先创建一个iOS类,因为这里一般都需要安卓端做一套,iOS端做一套,所以一般这样命名以示区别 .h #import <Foundation/Foundation.h> #import <JavaScriptCor

JS辨别访问浏览器判断是android还是ios系统

项目中需要扫描二维码之后自动分辨出是android还是ios系统,针对于不同的系统进行不同的下载. <script type="text/javascript"> /* * 智能机浏览器版本信息: * */ var browser = { versions: function() { var u = navigator.userAgent, app = navigator.appVersion; return {//移动终端浏览器版本信息 trident: u.indexO

IOS与网页JS交互详解及实例

 IOS与网页JS交互 随着移动APP的快速迭代开发趋势,越来越多的APP中嵌入了html网页,但在一些大中型APP中,尤其是电商类APP,html页面已经不仅仅满足展示功能,这时html要求能与原生语言进行交互.相互传值.比如携程APP中一个热门景点的网页中,点击某个景点,可以跳转到原生中的该景点详情页控制器. 为此,我整理了三种最常用最便捷有效的OC与JS交互的方式,供大家学习交流. 第一种:JS给OC传值. 1. 技术方案:使用JavaScriptCore.framework框架 2. 使

IOS中Json解析实例方法详解(四种方法)

作为一种轻量级的数据交换格式,json正在逐步取代xml,成为网络数据的通用格式. 有的json代码格式比较混乱,可以使用此"http://www.bejson.com/"网站来进行JSON格式化校验(点击打开链接).此网站不仅可以检测Json代码中的错误,而且可以以视图形式显示json中的数据内容,很是方便. 从IOS5开始,APPLE提供了对json的原生支持(NSJSONSerialization),但是为了兼容以前的iOS版本,可以使用第三方库来解析Json. 本文将介绍Tou

详解利用exif.js解决ios手机上传竖拍照片旋转90度问题

HTML5+canvas进行移动端手机照片上传时,发现iOS手机上传竖拍照片会逆时针旋转90度,横拍照片无此问题:Android手机没这个问题. 因此解决这个问题的思路是:获取到照片拍摄的方向角,对非横拍的ios照片进行角度旋转修正. 利用exif.js读取照片的拍摄信息,这里主要用到Orientation属性. Orientation属性说明如下: 下面就直接上代码了. 主要有html5页面和一个js,示例功能包含了图片压缩和旋转. 自己写的是uploadImage.js. html5测试页面

iOS中json解析出现的null,nil,NSNumber的解决办法

在iOS开发过程中经常需要与服务器进行数据通讯,Json就是一种常用的高效简洁的数据格式. JSON建构有两种结构: json简单说就是javascript中的对象和数组,所以这两种结构就是对象和数组2种结构,通过这两种结构可以表示各种复杂的结构 1.对象:对象在js中表示为"{}"扩起来的内容,数据结构为 {key:value,key:value,...}的键值对的结构,在面向对象的语言中,key为对象的属性,value为对应的属性值,所以很容易理解,取值方法为对象.key 获取属性

iOS开发使用JSON解析网络数据

前言:对服务器请求之后,返回给客户端的数据,一般都是JSON格式或者XML格式(文件下载除外) 本篇随便先讲解JSON解析. 正文: 关于JSON: JSON是一种轻量级的数据格式,一般用于数据交互JSON的格式很像Objective-C中的字典和数组:{"name":"jack","age":10} 补充: 标准的JSON格式的注意点:key必须用双引号.(但是在Java中是单引号) JSON-OC的转换对照表 其中:null--返回OC里的N

微信JSSDK多图片上传并且解决IOS系统上传一直加载的问题

微信多图片上传必须挨个上传,也就是不能并行,得串行: 那么我们可以定义一个如下所示的上传函数: var serverIds = []; function uploadImages(localImagesIds) { if (localImagesIds.length === 0) { $.showPreloader('正在提交数据...'); $('form').submit(); } wx.uploadImage({ localId: localImagesIds[0], // 需要上传的图片

微信jssdk在iframe页面失效问题的解决措施

项目需求 微信端添加拍品的页面有照片上传功能,上传时打开一个iframe,该页面用canvas加载用微信jssdk的选择图片接口选择的图片(这里微信会返回一个形如weixin://xxxx的localid,可直接放在img的src里进行预览),可进行放大缩小移动旋转等操作,点击确定按钮将编辑好的图片发送到服务器存成图片 遇到的问题 当子页面功能开发完成后,在主页面通过iframe打开子页面,无法调用微信jssdk接口中的选择图片接口,其他jssdk中的接口也无法正常工作.但是单独打开子页面可以正

快速解决Django关闭Debug模式无法加载media图片与static静态文件

开发时,通常打开Debug模式会快速定位开发时的一些问题. 项目开始部署时,关闭Debug模式,url.py路由静态文件和图片写法: # url.py from django.views import static from django.conf import settings #路由静态文件和图片 urlpatterns = [ url(r'^static/(?P<path>.*)$', static.serve, {'document_root': settings.STATIC_ROO

解决Android WebView拦截url,视频播放加载失败的问题

需求:Android调用webView加载网页的时候,拦截某一个链接不执行此链接,执行指定跳转到其他activity页面. webview的setWebViewClient方法中提供了几个api: // 此回调是拦截点击要跳转的url链接,并对请求的url链接做修改(添加删除字段) public WebResourceResponse shouldInterceptRequest(WebView view, String url) // 在点击请求的是链接是才会调用,重写此方法返回true表明点

iOS开发中用imageIO渐进加载图片及获取exif的方法

imageIO完成渐进加载图片 一.常见渐进加载图片模式   目前我们看到的渐进加载主要有以下三种实现方式:   1)  依次从web上加载不同尺寸的图片,从小到大.最开始先拉取一个小缩略图做拉伸显示,然后拉取中等规格的图,拉取完毕直接覆盖显示,最后拉取原图,拉取完成后显示原图.   2)直接从web上拉取最大的图片,每接受一点儿数据就显示一点儿图片,这样就会实现从上到下一点点刷新出来的效果.   3)结合第1种和第2种,先拉取一个缩略图做拉伸显示,然后采用第二种方法直接拉取原图,这样即可以实现

在IOS系统上滚动条滚动到指定的位置出现空白页面的解决方案

原因: -webkit-overflow-scrolling:touch 解释: 由于使用-webkit-overflow-scrolling这个属性,苹果手机会使用硬件加速,从而促使页面滑动得更加流畅,然而也导致了页面出现空白的情况. 解决办法: 滚动之前,先设-webit-overflow-scrolling的属性值为auto,然后页面滚动完了,再设为touch即可. 实例: $("#id").css('-webkit-overflow-scrolling','auto'); $(

解决python 虚拟环境删除包无法加载的问题

项目开发一直在docker的虚拟环境上,遇到了一个问题,就是把虚拟环境的包删掉(rm -rf xxx)之后,再重新拷贝一个(跟原来包一模一样的文件夹)进去发现pycharm再也找不到这个包了,后来在同事的帮助下一步步的解决了这个问题: 解决流程: 1.定位问题 在虚拟环境下引入这个包: #进入虚拟环境 source bin/activate #1.进入python #2.引入报错的包 (xenwebsite-env)[root@aeb02c10de04 xenwebsite-env]# pyth

解决IDEA插件市场Plugins无法加载的问题

问题:IDEA Plugin中搜索插件慢甚至无法加载出来,无法安装插件 解决方案: 加载半天无响应结果 1.检查网络环境,一般来说,如果wifi卡了,可以用4G热点试一下,糟糕的网络环境也可能是你加载不出来的原因之一 2.如果多次更换网络仍然不能打开,可以通过修改idea内置的代理服务器解决 首先打开Setting 依次找到Appearance&Behavior----->System Settings------>HTTP Proxy 配置代理服务器,输入URL 127.0.0.1:

微信 jssdk 签名错误invalid signature的解决方法

几乎每一个开发用于微信公众号页面的工程师都遇到过微信jssdk报的各种错误,通常是permission denied,类似这样: 通常他们会建议你把debug选项开开,比如这样: wechat.config({ debug: true, appId: appId, timestamp: timestamp, nonceStr: nonceStr, signature: signature, jsApiList: ['scanQRCode'], }); 结果你又遇到了invalid signatu