美文网首页
JS与OC交互采坑记

JS与OC交互采坑记

作者: woniu | 来源:发表于2019-06-11 20:00 被阅读0次

工作中有个需求,需要H5页面中的退出按钮需要调用OC的pop返回上个页面,所以就用到JS和OC的交互,本以为手拿把来没得问题,但是我还是高估自己了。下面就复盘一下问题吧:

一、简介

OC和JS交互立刻就想到了苹果原生的库JavaScriptCore,我们先在类中导入#import <JavaScriptCore/JavaScriptCore.h>,然后监听相应的事件。

二、问题

但是这里出现一个问题,JS中通过相应的对象来调用方法,而不是直接调用方法。这类就引申出第一个知识点:

js调用iOS分两种情况:

1、js里面直接调用方法
2、js里面通过对象调用方法

a:我们先来看下直接调用的方法:

1、由于在重定向等其它情况下回多次调动,所以用webView.isLoading让其加载完之后,我们再调用,这样就保证了之加载一次的情况。

-(void)webViewDidFinishLoad:(UIWebView *)webView  
{  
    if (webView.isLoading) {
        return;
    }
    //iOS调用js  
    //首先创建JSContext 对象(此处通过当前webView的键获取到jscontext)  
    JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];  
    
    //js调用iOS  
    //第一种情况  
    //其中test1就是js的方法名称,赋给是一个block 里面是iOS代码  
    //此方法最终将打印出所有接收到的参数,js参数是不固定的 我们测试一下就知道  
    context[@"test1"] = ^() {  
        NSArray *args = [JSContext currentArguments];  
        for (id obj in args) {  
            NSLog(@"%@",obj);  
        }  
    };  
}

b、里面通过对象调用方法

好巧不巧我们后台就是通过对象调的方法window.playbackapp.exit(),而我采用的还是第一种方法,所以怎么也无法调用到方法。和前端也讨论了半天,最终还是改动iOS这边的处理,所以通过对象调用方法就被应用了。

b1:首先,我们创建一个类,继承自NSObject,然后导入JavaScriptCore库。

b2、创建一个遵守JSExport的协议,并让我们创建的类实现该协议。

#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>

@protocol JSObjectProtocol <JSExport>
//js中的退出方法,要和我们OC的方法一致,否则不执行哦
-(void)exit;
@end

//创建的类实现上面的协议
@interface CCPBInterface : NSObject<JSObjectProtocol>
@property(nonatomic,weak) id<JSObjectProtocol> delegate;

@end

b3、加入方法和实现

在.m文件中,实现我们的协议。

-(void)exit
{
   //在webview的代理中是子线程,所以一定要在主线程中处理。给通知,退出回放页面。
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.delegate exit];
    });
}

可以把所有需要提供给js调用的方法都写在CCPBInterface这个类当中,通过代理的方式与需要执行这些操作的类进行连接,也就是说CCPBInterface这个类只负责提供方法,不负责实现,具体的操作可以在具体的添加有webView的控制器里实现
这里特别强调一点,不要在CCPBInterface这个类中执行方法,经过代码执行,通知是不执行的。我们去除掉主线程打印一下结果:

2019-06-11 19:46:04.681493+0800 CCClassRoom[12812:2824719] ~~~~~~~NSThread:<NSThread: 0x28153bdc0>{number = 2, name = (null)}

可以看出在webViewDidFinishLoad中执行的方法是在子线程中,而子线程是不可以更新UI的,虽然有时候也会更新成功,但是会有大量的警告打印。并且也会出现莫名其妙的崩溃,所以回到主线程时必须必要的。

b4、回到WebView中,我们调用代理,然后更新操作,此时是成功的。

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    //1、禁止在重定向时多次加载,由于会调动一次,所以用i讲第一次绕过去。
    if (webView.isLoading) {
        return;
    }
        self.context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        CCPBInterface *ccpb=[CCPBInterface new];
        ccpb.delegate =self;
        //添加对象
        self.context[@"playbackapp"]=ccpb;

//        此处是OC调用JS方法,如果使用它,那么不管你点击的是不是exit方法,每次调用这个方法。让你觉得好像没啥用。切记哦!
//        NSString *jsStr1=@"playbackapp.exit()";
//        [self.context evaluateScript:jsStr1];

}

b5、执行代理,发送出去通知,在Controller页面执行pop操作。

- (void)exit{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"goBackLoginView" object:nil];
}
- (void)dealloc{
    [[NSNotificationCenter defaultCenter] removeObserver:@"goBackLoginView"];
}

10月29号补充:
OC调用JS的方法,此处用的控件是WKWebView。

  NSString *shareMethod = @"pauseTimers()";
  [_webview evaluateJavaScript:shareMethod completionHandler:^(id _Nullable obj, NSError * _Nullable error) {
            NSLog(@"~~~~~~~~~~obj:%@~~~~error:%@",obj,error);
   }];

相关文章

网友评论

      本文标题:JS与OC交互采坑记

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