问题现象

Xcode13之前的开发工具编译,选用UIWebView框架的页面能够正常访问,js与原生交互的办法能够正常调用。更新Xcode13后,js提示”Can’t find variable”错误。

解决办法

对第三方库的运用

  1. 将第三方开源代码导入工程后,对源代码进行简单修正。
  2. 去除其中的运用的私有api”parentFrame”。
  3. 不需要在- (BOOL)webView: shouldStartLoadWithRequest:navigationType: 中创立一个新的UIWebView。
#import "UIWebView+TS_JavaScriptContext.h"
#import <JavaScriptCore/JavaScriptCore.h>
#import <objc/runtime.h>
static const char kTSJavaScriptContext[] = "ts_javaScriptContext";
static NSHashTable *g_webViews = nil;
@interface UIWebView (TS_JavaScriptCore_private)
- (void) ts_didCreateJavaScriptContext:(JSContext *)ts_javaScriptContext;
@end
@implementation NSObject (TS_JavaScriptContext)
- (void)webView:(id)unused didCreateJavaScriptContext:(JSContext *)ctx forFrame:(id)frame {
    NSLog(@"%s js context 注入模型丢掉问题",__func__);
    void (^notifyDidCreateJavaScriptContext)(void) = ^{
        for (UIWebView *webView in g_webViews) {
            NSString* cookie = [NSString stringWithFormat: @"ts_jscWebView_%lud", (unsigned long)webView.hash ];
            [webView stringByEvaluatingJavaScriptFromString: [NSString stringWithFormat: @"var %@ = '%@'", cookie, cookie]];
            if ([ctx[cookie].toString isEqualToString:cookie]) {
                [webView ts_didCreateJavaScriptContext:ctx];
                return;
            }
        }
    };
    if ([NSThread isMainThread]) {
        notifyDidCreateJavaScriptContext();
    } else {
        dispatch_async(dispatch_get_main_queue(), ^{
            notifyDidCreateJavaScriptContext();
        });
    }
}
@end
@implementation UIWebView (TS_JavaScriptContext)
+ (id)allocWithZone:(struct _NSZone *)zone {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        g_webViews = [NSHashTable weakObjectsHashTable];
    });
    id webView = [super allocWithZone:zone];
    [g_webViews addObject:webView];
    return webView;
}
- (void)ts_didCreateJavaScriptContext:(JSContext *)ts_javaScriptContext {
    NSLog(@"%s runtime 替换办法",__func__);
    [self willChangeValueForKey: @"ts_javaScriptContext"];
    objc_setAssociatedObject( self, kTSJavaScriptContext, ts_javaScriptContext, OBJC_ASSOCIATION_RETAIN);
    [self didChangeValueForKey: @"ts_javaScriptContext"];
    if (self.delegate && [self.delegate respondsToSelector:@selector(webView:didCreateJavaScriptContext:)]) {
        id<TSWebViewDelegate> delegate = (id<TSWebViewDelegate>)self.delegate;
        [delegate webView:self didCreateJavaScriptContext:ts_javaScriptContext];
    }
}
- (JSContext *)ts_javaScriptContext {
    NSLog(@"%s runtime 获取context",__func__);
    JSContext *javaScriptContext = objc_getAssociatedObject(self, kTSJavaScriptContext);
    return javaScriptContext;
}
@end

如何运用

- (void)webView:(UIWebView *)webView didCreateJavaScriptContext:(JSContext *)ctx {
    self.webView.ts_javaScriptContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
        NSLog(@"获取 UIWebView 的 JS 履行环境失败,原因: %@",exception.toString);
    };
    __weak __typeof(ctx) blockContext = ctx;
    __weak __typeof(self) weakSelf    = self;
#pragma mark 初始化登录数据
    ctx[@"getTokenAndUserAccount"] = ^() {
        // 这里是js的回调,把参数回调告知js。
    };
}

运用成果

之前能够正常访问页面了,但是随之带来一个新的现象:屡次运行该web控制器后,呈现崩溃。错误消息为

-[xxx respondsToSelector:]message sent to deallocated instance

查询后,触及空指针问题,以及或许某个delegate设置存在问题。

解决

重写控制器。

分析

参考资料中是不触及Xcode13的,个人感觉应该是Xcode13 中缺少了UIWebViewSDK的某些内容。当web页面在load之后,到注册js context的时分,没有注册成功,使JS在原生代码中找不到他需要履行的办法。

现在选用runtime的方式,使得咱们注入模型的机遇在创立 JavaScriptContext 与 JS调用 之间。

问题解决参考资料

UIWebView-TS_JavaScriptContext

iOS奇淫技巧 —— 解决UIWebView重定向后,JSContext 注入的模型丢掉问题