美文网首页
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