webview转wkwebview遇到的问题
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<methods.length;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<syncMethods.length;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<uzmeta.length;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<uzmeta.length;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<args.length; 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<list.length;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<list.length;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()<WKScriptMessageHandler>
@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