In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-17 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article is to share with you about the problems that iOs will encounter when moving to WKWebView. The editor thinks it is very practical, so share it with you as a reference and follow the editor to have a look.
Preface
There are two web page views in iOS that can load web pages except for the controller of the system. One is UIWebView, the other is WKWebView, in fact, WKWebView wants to replace UIWebView, because we all know that UIWebView takes up a lot of memory and other problems, but now many people are still using UIWebView this is why? And officials have announced that UIWebView has been abandoned in iOS12 so that we can use WKWebView as soon as possible. In fact, these are the things: * * page size issues, JS interaction, request interception, cookie can not bring the problem. * * so sometimes you have to solve these problems if you want to migrate, so it's still very annoying, so solve them one by one.
The problem of page size
We know that some pages are displayed well on UIWebView, and there will be a size problem when using WKWebView. I wonder if Android won't either. You don't say it's the front end, do you? But in fact, the page in WKWebView needs to be adapted, so add your own JS, of course, if you have a good relationship with the front end, you can ask him to add it. Let's add JS by setting the userContentController in the configuration.
WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc] init]; NSString * jScript = @ "var meta = document.createElement ('meta'); meta.setAttribute (' name', 'viewport'); meta.setAttribute (' content', 'width=device-width'); document.getElementsByTagName (' head') [0] .appendChild (meta);"; WKUserScript * wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; WKUserContentController * wkUController = [WKUserContentController alloc] init]; [wkUController addUserScript:wkUScript]; configuration.userContentController = wkUController
JS interaction
We all know that it is very convenient to use your own JavaScriptCore to interact with each other in UIWebView. There are three commonly used in JavaScriptCore that are JSContext (context), JSValue (type conversion), and JSExport (js call OC model method).
Convenient interaction method in UIWebView
/ / JSContext provides it with the running environment H5 context-(void) webViewDidFinishLoad: (UIWebView *) webView {JSContext * jsContext = [self.webView valueForKeyPath:@ "documentView.webView.mainFrame.javaScriptContext"]; self.jsContext = jsContext;} / / add the js global variable [self.jsContext evaluateScript:@ "var arr = [3, '3clients,' abc'];"] / / ⚠️ add the JS method, it should be noted that the added method will overwrite the original JS method, because we obtain the context after the web page is loaded successfully. / / self.jsContext with no parameters [@ "alertMessage"] = ^ () {NSLog (@ "the JS side will come here when it calls alertMessage!") ;}; / / with parameters, the value must be converted self.jsContext [@ "showDict"] = ^ (JSValue * value) {NSArray * args = [JSContext currentArguments]; JSValue * dictValue = args [0]; NSDictionary * dict = dictValue.toDictionary; NSLog (@ "% @", dict);}; / / get arr data JSValue * arrValue = self.jsContext [@ "arr"] in JS / / exception catch self.jsContext.exceptionHandler = ^ (JSContext * context, JSValue * exception) {weakSelf.jsContext.exception = exception; NSLog (@ "exception = =% @", exception);}; / / reassign OMJSObject * omObject = [[OMJSObject alloc] init] to objects in JS; self.jsContext [@ "omObject"] = omObject;NSLog (@ "omObject = =% d", [omObject getSum:20 num2:40]) / / when we all know that object must comply with the JSExport protocol, js can directly call methods in object and need to give the function an individual name. You can call getS,OC on the JS side and continue to use this getSum method @ protocol OMProtocol / / protocol-protocol method JSExportAs (getS,-(int) getSum: (int) num1 num2: (int) num2); @ end
What do you do in WKWebView?
Unlike the above, the system is provided through the following two methods, so it is more difficult, and the front end has to use messageHandler to call, that is, Android and iOS are handled separately.
/ / directly call jsNSString * jsStr = @ "var arr = [3, '3percent,' abc'];"; [self.webView evaluate_JavaScript:jsStr completionHandler: ^ (id _ Nullable result, NSError * _ Nullable error) {NSLog (@ "% @--% @", result, error);}] / / after the registered name is listed below, js will enter the agent after calling the specified name using messageHandlers / / OC. After we add the js name-(void) viewDidLoad {/ /... [wkUController addScriptMessageHandler:self name:@ "showtime"]; configuration.userContentController = wkUController;} / / when messageHandlers calls us with the same name in OC, we will enter the following proxy window.webkit.messageHandlers.showtime.postMessage ('') to OC; / / Agent, judgment logic-(WKUserContentController *) userContentController didReceiveScriptMessage: (WKScriptMessage *) message {if ([message.name isEqualToString:@ "showtime"]) {NSLog (@ "coming!") ;} NSLog (@ "message = =% @--% @", message.name,message.body);} / / finally, [self.userContentController removeScriptMessageHandlerForName:@ "showtime"] must be removed in dealloc; / / if it is a pop-up window, you must implement the proxy method-(void) webView: (WKWebView *) webView runJavaScriptAlertPanelWithMessage: (NSString *) message initiatedByFrame: (WKFrameInfo *) frame completionHandler: (void (^) (void)) completionHandler {UIAlertController * alert = [UIAlertController alertControllerWithTitle:@ "remind" message:message preferredStyle:UIAlertControllerStyleAlert] [alert addAction: [UIAlertAction actionWithTitle:@ "knows" style:UIAlertActionStyleCancel handler: ^ (UIAlertAction * _ Nonnull action) {completionHandler ();}]]; [self presentViewController:alert animated:YES completion:nil];}
Always use a straightforward interaction
We wrote above some interaction between the two, although it can be used, but without a very simple and easy realm, so there is an open source library: WebViewJavaScriptBridge. This open source library can be compatible with both, and the interaction is simple, but you have to be at the front end, or else.
/ use self.wjb = [WebViewJavascriptBridge bridgeForWebView:self.webView]; / / if you want to implement the UIWebView proxy method in VC, implement the following code (otherwise omitted) [self.wjb setWebViewDelegate:self]; / / register the js method name [self.wjb registerHandler:@ "jsCallsOC" handler: ^ (id data, WVJBResponseCallback responseCallback) {NSLog (@ "currentThread = =% @", [NSThread currentThread]); NSLog (@ "data = =% @ -% @", data,responseCallback);}] / call JSdispatch_async (dispatch_get_global_queue (0,0), ^ {[self.wjb callHandler:@ "OCCallJSFunction" data:@ "OC calls JS" responseCallback: ^ (id responseData) {NSLog (@ "currentThread = =% @", [NSThread currentThread]); NSLog (@ "callback after calling JS:% @", responseData);}];})
Examples of frontend usage are as follows. For more information on how to use it, please see WebViewJavaScriptBridge.
Function setupWebViewJavascriptBridge (callback) {if (window.WebViewJavascriptBridge) {return callback (WebViewJavascriptBridge);} if (window.WVJBCallbacks) {return window.WVJBCallbacks.push (callback);} window.WVJBCallbacks = [callback]; var WVJBIframe = document.createElement ('iframe'); WVJBIframe.style.display =' none'; WVJBIframe.src = 'https://__bridge_loaded__'; document.documentElement.appendChild (WVJBIframe) SetTimeout (function () {document.documentElement.removeChild (WVJBIframe)}, 0)} setupWebViewJavascriptBridge (function (bridge) {/ * Initialize your app here * / bridge.registerHandler ('JS Echo', function (data, responseCallback) {console.log ("JS Echo called with:", data) responseCallback (data)}) bridge.callHandler ('ObjC Echo' {'key':'value'}, function responseCallback (responseData) {console.log ("JS received response:", responseData)}))
Request intercept
In the early days, UIWebView used-(BOOL) webView: (UIWebView *) webView shouldStartLoadWithRequest: (NSURLRequest *) request navigationType: (UIWebViewNavigationType) navigationType to intercept according to scheme, host and pathComponents to do custom logic processing. But this method is not very flexible, so use NSURLProtocol to intercept, such as Wechat intercepting Taobao, directly display a prompt. Or intercept requests and call local interfaces to turn on cameras, recordings, photo albums and other functions. It can also directly intercept and change the original request, and directly return data or other url, which can be used when removing ads.
We have to use a subclass of NSURLProtocol to do something when we use it. And you need to register a custom Class before using it. Remember to mark it after interception to prevent multi-execution of the self-loop. Unfortunately, post-intercept processing cannot be performed in WKWebView, and it can only be monitored but cannot be changed. Originated from WKWebView, it uses webkit loading, the same mechanism as the browser of the system.
/ / subclass @ interface OMURLProtocol: NSURLProtocol@property (nonatomic, strong) NSURLSession * session;@end// register [NSURLProtocol registerClass: [OMURLProtocol class]]; / / 1. First of all, it will be intercepted here. If you return YES, you need to go through our custom processing, and NO will go through the system processing + (BOOL) canInitWithRequest: (NSURLRequest *) request;// 2. Interception processing will move on to the next step, returning a standardized request, where you can redirect + (NSURLRequest *) canonicalRequestForRequest: (NSURLRequest *) request;// 3. Whether the interception is successful or not will take this method, and you can do some custom processing here-(void) startLoading;// 4. Any network request will go through the above intercept processing. Even if we go through the process again or again after redirection, we need a tag to process / / to determine whether to intercept according to the tag value obtained by request. Process + (nullable id) propertyForKey: (NSString *) key inRequest: (NSURLRequest *) request;// tag + (void) setProperty: (id) value forKey: (NSString *) key inRequest: (NSMutableURLRequest *) request in canInitWithRequest / / remove tag + (void) removePropertyForKey: (NSString *) key inRequest: (NSMutableURLRequest *) request
Request header or data confusion
It should also be noted that if we implement online interception processing, when we use AFN and URLSession for access, we will find that the data or request header may not match your expectations for intercepting the processed data or request. This is because when we intercept, we only request A first and then request B, which is not in line with expectations. Although URLConnection will not be used, it is abandoned and not worth promoting. When we print the protocol configured in the session through LLDB when intercepting, we find that it does not contain our custom protocol, we exchange protocolClasses methods through the Runtime exchange method, and we implement our own protocolClasses method. However, in order to ensure the original properties of the system, we should add our protocol class to the original protocol table of the system. At present, we can get the default protocol class of the system through [NSURLSession sharedSession] .designation.protocol classes;, but if we write protocolClasses in the current custom class, it will cause an endless loop because we exchange the getter method of this property. We use to save the class name and store it to NSUserDefaults, and restore the class when we take the value.
Po session.configuration.protocolClasses (_ NSURLHTTPProtocol,_NSURLDataProtocol,_NSURLFTPProtocol,_NSURLFileProtocol,NSAboutURLProtocol) / / Custom returns our protocol class-(NSArray *) protocolClasses {NSArray * originalProtocols = [OMURLProtocol readOriginalProtocols]; NSMutableArray * newProtocols = [NSMutableArray arrayWithArray:originalProtocols]; [newProtocols addObject: [OMURLProtocol class]]; return newProtocols } / / when we print again, we find that our custom protocol class has been added to the original po session.configuration.protocolClasses (_ NSURLHTTPProtocol,_NSURLDataProtocol,_NSURLFTPProtocol,_NSURLFileProtocol,NSAboutURLProtocol,OMURLProtocol) / / storage system original protocol class + (void) saveOriginalProtocols: (NSArray *) protocols {NSMutableArray * protocolNameArray = [NSMutableArray array]; for (Class protocol in protocols) {[protocolNameArray addObject:NSStringFromClass (protocol)];} NSLog (@ "protocol array:% @", protocolNameArray) [[NSUserDefaults standardUserDefaults] setObject:protocolNameArray forKey:originalProtocolsKey]; [[NSUserDefaults standardUserDefaults] synchronize];} / / obtain the original protocol class of the system + (NSArray *) readOriginalProtocols {NSArray * classNames = [[NSUserDefaults standardUserDefaults] valueForKey:originalProtocolsKey]; NSMutableArray * origianlProtocols = [NSMutableArray array]; for (NSString * name in classNames) {Class class = NSClassFromString (name); [origianlProtocols addObject: class];} return origianlProtocols;} + (void) hookNSURLSessionConfiguration {NSArray * originalProtocols = [NSURLSession sharedSession] .promotion.classes.[ self saveOriginalProtocols:originalProtocols] Class cls = NSClassFromString (@ "_ NSCFURLSessionConfiguration"): NSClassFromString (@ "NSURLSessionConfiguration"); Method originalMethod = class_getInstanceMethod (cls, @ selector (protocolClasses)); Method stubMethod = class_getInstanceMethod ([self class], @ selector (protocolClasses)); if (! originalMethod | |! stubMethod) {[NSException raise:NSInternalInconsistencyException format:@ "cannot be exchanged without this method"];} method_exchangeImplementations (originalMethod, stubMethod);}
The problem of carrying Cookie
Many application scenarios need to use session for processing, and it is easy to carry these Cookie in UIWebView, but because the mechanism of WKWebView is different, it is very bad that cookie will be lost across domains. There are currently two uses: scripting and manually adding cookie. The script is not very reliable, so it is recommended to add it manually.
/ / use script to add cookie// to get cookie data-(NSString *) cookieString {NSMutableString * script = [NSMutableString string]; [script appendString:@ "var cookieNames = [xss_clean] .split (';') .map (function (cookie) {return cookie.split ('=') [0]});\ n"]; for (NSHTTPCookie * cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {if ([cookie.value rangeOfString:@ "'"]. Location! = NSNotFound) {continue } [script appendFormat:@ "if (cookieNames.indexOf ('% @') =-1) {[xss_clean] ='% @';};\ n", cookie.name, cookie.kc_formatCookieString];} return script;} / / add cookieWKUserScript * cookieScript = [[WKUserScript alloc] initWithSource: [self cookieString] injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]; [WKUserContentController alloc] init] addUserScript: cookieScript] / / add a category to fix missing cookie problems @ interface NSURLRequest (Cookie)-(NSURLRequest *) fixCookie;@end@implementation NSURLRequest (Cookie)-(NSURLRequest *) fixCookie {NSMutableURLRequest * fixedRequest; if ([self isKindOfClass: [NSMutableURLRequest class]]) {fixedRequest = (NSMutableURLRequest *) self;} else {fixedRequest = self.mutableCopy;} / / prevent Cookie from losing NSDictionary * dict = [NSHTTPCookie requestHeaderFieldsWithCookies: [NSHTTPCookieStorage sharedHTTPCookieStorage] .cookies]; if (dict.count) {NSMutableDictionary * mDict = self.allHTTPHeaderFields.mutableCopy [mDict setValuesForKeysWithDictionary:dict]; fixedRequest.allHTTPHeaderFields = mDict;} return fixedRequest;} @ end / / usage scenarios-(void) webView: (WKWebView *) webView decidePolicyForNavigationAction: (WKNavigationAction *) navigationAction decisionHandler: (void (^) (WKNavigationActionPolicy)) decisionHandler {[navigationAction.request fixCookie]; decisionHandler (WKNavigationActionPolicyAllow);} Thank you for reading! This is the end of the article on "what problems will be encountered when iOs moves to WKWebView". I hope the above content can be of some help to you, so that you can learn more knowledge. if you think the article is good, you can share it out for more people to see!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.