webview转wkwebview遇到的坑
1、wkwebview里通过新窗品进行ajax的post请求时,cookie参数丢失
解决方案:不创建新wkwebview
2、原生网络请求请求后cookie无法同步到wkwebview
@implementation WKWebView (cookieMgr)
- (void)syncCookies {
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
if (@available(iOS 11.0, *)) {
WKHTTPCookieStore *cookieStroe = self.configuration.websiteDataStore.httpCookieStore;
if (cookies.count == 0) {
return;
}
for (NSHTTPCookie *cookie in cookies) {
[cookieStroe setCookie:cookie completionHandler:^{
if ([[cookies lastObject] isEqual:cookie]) {
return;
}
}];
}
}else {
for (NSHTTPCookie *ck in cookies) {
NSString *script = [NSString stringWithFormat:@"document.cookie='%@=%@;domain=%@;path=%@; expiresDate=\"%@\";isSecure=%@;sessionOnly=%@'",ck.name,ck.value,ck.domain,(ck.path?:@"/"),ck.expiresDate,(ck.isSecure ? @"TRUE":@"FALSE"),(ck.sessionOnly?@"TRUE":@"FALSE")];
[self evaluateJavaScript:script completionHandler:nil];
}
}
}
@end
3、退出登录时,再次登录,部分页面cookie错误
在退出登录时,如果将WKProcessPool单例对象重置,会造成以前打开页面和新打开cookie错误现象
4、关于原生方法调用的问题
webview采用jsexport为原生导出相应方法
wk通过js注入,创建原生对应对象并调用
JS代码示例:
window.uz$q = {
c:[],
flag:true,
};
window.uz$cb = {
fn:{},
id:1,
on:function(cbId, ret, err, del) {
if (this.fn[cbId]) {
this.fn[cbId](ret, err);
if (del) {
delete this.fn[cbId];
}
}
}
};
function _onResultCallback(cbId, ret, err, del) {
return function(){
uz$cb.on(cbId, ret, err, del);
}
}
function onResultCallback(cbId, ret, err, del) {
setTimeout(_onResultCallback(cbId, ret, err, del), 0);
};
window.uz$md = {};
function uz$e(c, m, p, isSync, module){
param = {};
param.cbId = -1; //-1 表示没有回调函数
newP = [];
var str = Object.prototype.toString.call(p);
if (str == "[object Arguments]" && p.length > 0) {
p = Array.from(p);
for (var index = 0; index < p.length;index ++) {
node = p[index];
str = Object.prototype.toString.call(node);
if (str == "[object Function]") {
param.cbId = uz$cb.id ++;
uz$cb.fn[param.cbId] = node;
newP.push(str);
}else {
newP.push(node);
}
}
}
param.param = newP;
/*
if (p.length === 1) {
var p0 = p[0];
if (Object.prototype.toString.call(p0) === "[object Object]") {
param = p0;
} else if (typeof p0 === "function") {
param.cbId = uz$cb.id++;
uz$cb.fn[param.cbId] = p0;
}
} else if (p.length === 2) {
var p0 = p[0];
var p1 = p[1];
if (Object.prototype.toString.call(p0) === "[object Object]") {
param = p0;
}
if (typeof p1 === "function") {
param.cbId = uz$cb.id++;
uz$cb.fn[param.cbId] = p1;
}
}
*/
var message = {};
message.class = c;
message.method = m;
message.param = param;
message.isSync = false;
message.module = module;
window.webkit.messageHandlers.nativeAndroidOrIos.postMessage(message);
};
function uz$shift() {
if (uz$q.c.length > 0) {
uz$q.c.shift();
}
uz$q.flag = true;
};
$object_define_placeholder$; //对象定义点位符
nativeAndroidOrIos.require = function(name, cb) {
var module = uz$md[name];
if (!module && nativeAndroidOrIos.useJavaScriptCore) {
var moduleInfo = nativeAndroidOrIos.getModule({name:name, sync:true});
if (moduleInfo) {
var name = moduleInfo.name;
var className = moduleInfo.class;
var methods = moduleInfo.methods;
var syncMethods = moduleInfo.syncMethods;
uz$md[name] = {};
if (methods && Object.prototype.toString.call(methods) == '[object Array]') {
for (var i=0;i (function() { var method = methods[i]; uz$md[name][method] = function() { return uz$e(className, method, arguments, false, name); } })(); } } if (syncMethods && Object.prototype.toString.call(syncMethods) == '[object Array]') { for (var i=0;i (function() { var method = syncMethods[i]; uz$md[name][method] = function() { return uz$e(className, method, arguments, true, name); } })(); } } module = uz$md[name]; } } if (module) { return module; } else { if (cb) { cb('undefined', {code:1,msg:name+' module not found'}); } else { return null; } } } $method_define_placeholder$; //函数定义占位符 //var uzmeta = window.document.getElementsByTagName("meta"); //for(var i=0;i // var name = uzmeta[i].getAttribute('name'); // var content = uzmeta[i].getAttribute('content'); // if (name && name=='viewport' && content){ // content = content.replace(/width=\d{1,}/,'width=device-width'); // content = content.replace('width=device-width','width=device-width'); // uzmeta[i].setAttribute('content',content); // } //} //var uzmeta = window.document.getElementsByTagName("meta"); //for(var i=0;i // var name = uzmeta[i].getAttribute('name'); // var content = uzmeta[i].getAttribute('content'); // if (name && name=='viewport' && content){ // content = content+',user-scalable=0'; // uzmeta[i].setAttribute('content',content); // } //} document.documentElement.style.webkitTouchCallout = 'none'; document.documentElement.style.webkitUserSelect = 'none'; /* window.alert = function(arg) { var msg; if (arg === null) { msg = 'null'; } else if (arg === undefined) { msg = 'undefined'; } else { msg = arg.toString(); } nativeAndroidOrIos.alert({ title:'', msg:msg, buttons:['好'] }); } */ var originalFunc_onerror = window.onerror; window.onerror = function(message, url, line) { var param = {}; param.message = message; param.url = url; param.line = line; originalFunc_onerror.apply(window, arguments); } function getContentFromArg(arg){ var content; var args = Array.prototype.slice.call(arg); if (args.length >= 1) { if (args[0] === null) { args[0] = 'null'; } if (args[0] === undefined) { args[0] = 'undefined'; } args[0] = args[0].toString(); } if (args.length > 1) { var i = 1; if (args[0].indexOf('%c') == 0) { args[0] = args[0].replace(/%c/,''); i = 2; } for (; i if (/%s|%d|%i|%o/.test(args[0])) { args[0] = args[0].replace(/%s|%d|%i|%o/, args[i]); } else { break; } } if (i < args.length) { args[0] = args[0]+' '+args.slice(i).join(' '); } content = args[0]; } else if (args.length == 1) { content = args[0]; } else { content = ''; } return content; } var originalFunc_log = console.log; console.log = function() { var content = getContentFromArg(arguments); var param = {}; param.method = 'log'; param.content = content; originalFunc_log.apply(console, arguments); } var originalFunc_info = console.info; console.info = function() { var content = getContentFromArg(arguments); var param = {}; param.method = 'info'; param.content = content; originalFunc_info.apply(console, arguments); } var originalFunc_debug = console.debug; console.debug = function() { var content = getContentFromArg(arguments); var param = {}; param.method = 'debug'; param.content = content; originalFunc_debug.apply(console, arguments); } var originalFunc_warn = console.warn; console.warn = function() { var content = getContentFromArg(arguments); var param = {}; param.method = 'warn'; param.content = content; originalFunc_warn.apply(console, arguments); } var originalFunc_error = console.error; console.error = function() { var content = getContentFromArg(arguments); var param = {}; param.method = 'error'; param.content = content; originalFunc_error.apply(console, arguments); } var uzAttempCheckTimes = 0; function uzCheckApiready() { if (uzAttempCheckTimes < 50) { if (typeof(apiready) === 'function') { apiready(); } else { uzAttempCheckTimes++; setTimeout('uzCheckApiready()', 100); } } } uzCheckApiready(); (function(){ var MAX_MOVE = 5; var SELECTOR = "tapmode"; function uz_addTouchedClass(node, clas) { if (node && clas) { var list = clas.split(' '); for (var i=0;i var classItem = list[i]; if (uz_isString(classItem) && classItem.length>0) { node.classList.add(classItem.trim()); } } } }; function uz_removeTouchedClass(node) { if (node && node.clicker) { var clas = node.clicker.clas; if (clas) { var list = clas.split(' '); for (var i=0;i var classItem = list[i]; if (uz_isString(classItem) && classItem.length>0) { node.classList.remove(classItem.trim()); } } } }; }; var Clicker = function(){}; function parseTapmode(){ var nodes = document.querySelectorAll('[' + SELECTOR + ']'); if (nodes) { for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; if (!node.uzonclick) { if (node.onclick) { node.uzonclick = node.onclick; node.onclick = null; node.addEventListener('touchstart', uz_handStart, false); node.addEventListener('touchmove', uz_handMove, false); node.addEventListener('touchend', uz_handEnd, false); node.addEventListener('touchcancel', uz_handCancel, false); } } } } }; function uz_isDisabled(e) { var node = e.currentTarget; return node.disabled; }; function uz_isString(str) { return (typeof str == 'string'); }; function uz_handStart(e) { if (nativeAndroidOrIos.isScrolling) { return; } if (uz_isDisabled(e)) { return; } var node = e.currentTarget; var clicker = new Clicker(); clicker.X = e.touches[0].clientX; clicker.Y = e.touches[0].clientY; clicker.downTime = e.timeStamp; var clas = node.getAttribute(SELECTOR); if (!uz_isString(clas)) { clas = ''; } clas = clas.trim(); clicker.clas = clas; node.clicker = clicker; uz_addTouchedClass(node, clas); }; function uz_handMove(e) { if (uz_isDisabled(e)) { return; } var node = e.currentTarget; var clicker = node.clicker; if (!clicker) { return; } var x = e.touches[0].clientX, y = e.touches[0].clientY; if (Math.abs(x - clicker.X) > MAX_MOVE || Math.abs(y - clicker.Y) > MAX_MOVE) { uz_reset(node, true); } }; function uz_handEnd(e) { if (uz_isDisabled(e)) { return; } var node = e.currentTarget; uz_reset(node); if (!nativeAndroidOrIos.didShowExitAction) { uz_fire(e, node); } }; function uz_handCancel(e) { var node = e.currentTarget; uz_reset(node, true); }; function uz_fire(e, node) { if (node.uzonclick) { var clicker = node.clicker; if (clicker) { e.preventDefault(); node.uzonclick.call(node, e); node.clicker = null; } } }; function uz_reset(node, del) { uz_removeTouchedClass(node); if (del) { node.clicker = null; } }; parseTapmode(); })(); // // // STWKUserContentController.m @interface STWKUserContentController() @property (nonatomic,strong) NSMutableDictionary *delegateInstanceMap; //保存代理实例的字典 @end @implementation STWKUserContentController - (instancetype)init { if (self = [super init]) { _delegateInstanceMap = [NSMutableDictionary dictionary]; [self injectJS]; } return self; } /** 获取一个类的方法列表,并生成js中方法定义 @param classes 原生类名 @param level js中类层次定义, @return 返回一个类在js中的定义 */ - (NSString *)methodByClass:(Class)classes level:(NSString *)level { NSMutableString *result = [NSMutableString string]; unsigned int count; __unsafe_unretained Protocol **protocolList = class_copyProtocolList(classes, &count); for (int i = 0;i < count;i++) { Protocol *protocol = protocolList[i]; unsigned int methodCount = 0; struct objc_method_description *method_description_list = protocol_copyMethodDescriptionList(protocol, YES, YES, &methodCount); for (int j = 0; j < methodCount ; j ++) { struct objc_method_description description = method_description_list[j]; NSString *name = NSStringFromSelector(description.name); NSRange range = [name rangeOfString:@":"]; if (NSNotFound != range.location) { name = [name substringToIndex:range.location ]; } NSString *str = [NSString stringWithFormat: @"%@.%@ = function() {\n \ return uz$e('%@', '%@', arguments, false, 'api');\n \ }\n\n",level,name,classes,name]; [result appendString:str]; } free(method_description_list); } free(protocolList); /* Method *methods = class_copyMethodList(classes, &count); for (int i = 0; i < count; i++) { Method method = methods[i]; SEL selector = method_getName(method); NSString *name = NSStringFromSelector(selector); NSRange range = [name rangeOfString:@":"]; if (NSNotFound != range.location) { name = [name substringToIndex:range.location ]; } NSString *str = [NSString stringWithFormat: @"%@.%@ = function() { \ return uz$e('%@', '%@', arguments, false, 'api'); \ }",level,name,classes,name]; [result appendString:str]; } free(methods); */ return result; } - (id)nativeInstanceWithLevel:(NSString *)level { return nil; } - (NSArray *)arrayOfInjectClass { return @[@{@"level":@"window.nativeAndroidOrIos", @"classes":@"nativeAndroidOrIosContext"}, ]; } //根据类名返回实例对象 - (instancetype)instanceWithClassName:(NSString *)strClass { id instance = [_delegateInstanceMap objectForKey:strClass]; if ( !instance ) { Class classes = NSClassFromString(strClass); instance = [[classes alloc] init]; } return instance; } #pragma mark - js注入 - - (void)injectJS { [self addScriptMessageHandler:self name:@"nativeAndroidOrIos"]; NSString *ajs = [[NSBundle bundleForClass:[self class]] pathForResource:@"a" ofType:@"js"]; NSString *jsStr = [NSString stringWithContentsOfFile:ajs encoding:NSUTF8StringEncoding error:nil]; NSMutableString *jsContent = [NSMutableString stringWithString:jsStr]; NSArray *arrays = [self arrayOfInjectClass]; //组合类声明,及函数声明字符串 NSMutableString *strLevel = [NSMutableString string]; NSMutableString *strFuncDef = [NSMutableString string]; for (int i = 0; i < [arrays count];i ++) { NSString *level = arrays[i][@"level"]; NSString *strCls = arrays[i][@"classes"]; Class classes = NSClassFromString(strCls); id instance = [[classes alloc] init]; [_delegateInstanceMap setObject:instance forKey:strCls]; [strLevel appendFormat:@"%@={};\n",level]; [strFuncDef appendString:[self methodByClass:classes level:level]]; } //替换a.js中,对象声明及函数定义 NSString *stringJavaScript = [jsContent stringByReplacingOccurrencesOfString:@"$object_define_placeholder$;" withString:strLevel]; stringJavaScript = [stringJavaScript stringByReplacingOccurrencesOfString:@"$method_define_placeholder$;" withString:strFuncDef]; // WKUserScript *script = [[WKUserScript alloc] initWithSource:stringJavaScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; [self addUserScript:script]; // WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource: [self readCurrentCookie] injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]; [self addUserScript:cookieScript]; } //读取cookie信息 - (NSString *)readCurrentCookie { return @""; NSMutableString *cookieValue = [NSMutableString stringWithFormat:@""]; NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage]; for (NSHTTPCookie *cookie in [cookieJar cookies]) { [cookieValue appendString:[NSString stringWithFormat:@"document.cookie='%@';",cookie.getCookieString]]; } return cookieValue; } // - (void)onResultCallBack:(AndroidIosNativeBase *)target sign:(NSMethodSignature *)sign inv:(NSInvocation *)inv { //获取返回值类型 const char * returnValueType = sign.methodReturnType; //声明一个返回值变量 __autoreleasing id returnValue; //此处一定要为__autoreleasing否则会crash BOOL bVoidReture = NO; NSString *strReturnValue; //如果没有返回值,也就是消息声明为void,那么returnValue = nil if (!strcmp(returnValueType, @encode(void))) { NSLog(@"没有返回值,即返回值类型为void"); returnValue = nil; bVoidReture = YES; }else if (!strcmp(returnValueType, @encode(id))){ //如果返回值为对象,那么为变量赋值 NSLog(@"返回值类型为对象"); [inv getReturnValue:&returnValue]; if (!returnValue) { strReturnValue = @"null"; } else { if ([returnValue isKindOfClass:[NSString class]]) { strReturnValue = returnValue; }else { @throw [NSException exceptionWithName:@"返回值异常" reason:@"未转换的返回值类型" userInfo:returnValue]; } } }else { //如果返回值为普通类型,如NSInteger, NSUInteger ,BOOL等 NSLog(@"返回类型为普通类型"); //首先获取返回值长度 NSUInteger returnValueLenth = sign.methodReturnLength; //根据长度申请内存 void * retValue = (void *)malloc(returnValueLenth); //为retValue赋值 [inv getReturnValue:retValue]; if (!strcmp(returnValueType, @encode(BOOL))) { returnValue = [NSNumber numberWithBool:*((BOOL *)retValue)]; BOOL bRet = returnValue; strReturnValue = [NSString stringWithFormat:@"%@",(bRet ? @"true":@"false")]; }else if (!strcmp(returnValueType, @encode(NSInteger))){ returnValue = [NSNumber numberWithInteger:*((NSInteger *) retValue)]; strReturnValue = [NSString stringWithFormat:@"%ld",returnValue]; } } //函数有返回值 if (!bVoidReture && [target isKindOfClass:[AndroidIosNativeBase class]]) { AndroidIosNativeBase *native = (AndroidIosNativeBase *)target; [native onResultCallBack:@"[Function]" value:strReturnValue]; } } //根据类名,方法名,参数个数查找合适的方法并执行 - (void)nativeOcMethod:(WKWebView *)webView classes:(NSString *)classes method:(NSString *)strMethod args:(id)param { unsigned int count; Class classz = NSClassFromString(classes); Method *methods = class_copyMethodList(classz, &count); for (int i = 0; i < count; i++) { Method method = methods[i]; SEL selector = method_getName(method); NSString *name = NSStringFromSelector(selector); NSRange range = [name rangeOfString:@":"]; if (NSNotFound != range.location) { name = [name substringToIndex:range.location ]; } if ([name isEqualToString:strMethod]) { //找到对应方法名 unsigned int argN = method_getNumberOfArguments(method); if ([param isKindOfClass:[NSDictionary class]]) { NSArray *args = [param objectForKey:@"param"]; if (argN - 2 == [args count] ) { //简单判断参数个数相等,则认为是同一方法 id target = [self instanceWithClassName:classes]; if ([target isKindOfClass:[AndroidIosNativeBase class]]) { ((AndroidIosNativeBase *)target).cbId = [[param objectForKey:@"cbId"] integerValue]; ((AndroidIosNativeBase *)target).webView = webView; } NSMethodSignature *singture = [classz instanceMethodSignatureForSelector:selector]; NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:singture]; [invocation setTarget:target]; [invocation setSelector:selector]; //index从2开始 for (int i = 0;i <[args count];i++) { id value = args[i]; [invocation setArgument:&value atIndex:i + 2]; } [invocation retainArguments]; //retain参数 防止被dealloc [invocation invoke]; //取消返回值 //[self onResultCallBack:target sign:singture inv:invocation]; return; } } } NSLog(@"method_getName:%@",name); } } // #pragma mark - WKScriptMessageHandler - - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { if ([message.name isEqualToString:@"nativeAndroidOrIos"]) { if ([message.body isKindOfClass:[NSDictionary class]]) { NSDictionary *body = message.body; NSString *method = [body objectForKey:@"method"]; NSString *class = [body objectForKey:@"class"]; NSDictionary *params = [body objectForKey:@"param"];//参数 if ([params isKindOfClass:[NSDictionary class]]) { [self nativeOcMethod:message.webView classes:class method:method args:params]; } } } } @end