1.打断点查看setTitleViewTitle调用堆栈
发现是调用的__NSThreadPerformPerform
2.上hopper查找调用setTitleViewTitle所在类
0000000102aad358 dq 0x1020a9341 ; @selector(setTitleViewTitle:), "setTitleViewTitle:", DATA XREF=
-[Plugin4HandleProxyRequest handleEvent:]+2452,
-[Plugin4HandleProxyRequest handleEvent:]+3108, ___60
-[Plugin4HandleProxyRequest setNavigateTitleWithHtml:event:]_block_invoke+288,
-[TAComponentWebview observeValueForKeyPath:ofObject:change:context:]+301,
-[TAComponentWebview observeValueForKeyPath:ofObject:change:context:]+375
3这样好麻烦
直接把这个方法替换成别的吧
[rbx performSelectorOnMainThread:@selector(setTitleViewTitle:) withObject:var_-80 waitUntilDone:0x0];
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSThread (YYY)
@end
NS_ASSUME_NONNULL_END
#import "NSThread+YYY.h"
#import <objc/runtime.h>
@implementation NSThread (YYY)
+(void)load{
{
Method originalMethod = class_getInstanceMethod([NSThread class], @selector( performSelectorOnMainThread:withObject:waitUntilDone:));
Method swizzledMethod = class_getInstanceMethod([NSThread class], @selector( performSelectorOnMainThread:withObject:waitUntilDoneS:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDoneS:(BOOL)wait{
if ([self isKindOfClass:[NSClassFromString(@"UIKeyboardTaskQueue") class]]) {
[self performSelectorOnMainThread:aSelector withObject:arg waitUntilDoneS:wait];
return;
}
NSLog(@"^^^^^^##### %@ %@ %@",self,NSStringFromSelector(aSelector),arg);
}
@end
打印需要的信息
--[NSThread(YYY) performSelectorOnMainThread:withObject:waitUntilDoneS:]-^^^^^^##### <ViewController: 0x7f968f9dca00> setTitleViewTitle: XXXXXXXX-
看到堆栈为
对比基线升级之前的工程堆栈(每次都会调用)
5.查看升级提示
mPaaS 10.1.68 发布说明
从 10.1.68 基线开始正式废弃 UIWebView,只支持 WKWebView,详情可参考 mPaaS 适配 WKWebView。由于 App Store 从 2020 年 4 月起不再接受使用 UIWebView 的新 APP,从 2020 年 12 月起不再接受使用 UIWebview 的 APP 的更新,详情请参见 苹果官方声明 。请 2020 年 4 月前仍未上架 APP Store 的新应用,尽快升级适配 WKWebView。
原因猜测 换了wk 异步回调导致
6.看看新工程有没有每次调用-[Plugin4HandleProxyRequest setNavigateTitleWithHtml:event:]
经验证新工程每次都会调用
7.看一下新工程-[Plugin4HandleProxyRequest setNavigateTitleWithHtml:event:]伪源码
void -[Plugin4HandleProxyRequest setNavigateTitleWithHtml:event:](void * self, void * _cmd, void * arg2, void * arg3) {
var_-48 = [arg2 retain];
rax = [arg3 retain];
r14 = [[rax request] retain];
var_-56 = rax;
r13 = [[rax response] retain];
rbx = [[r14 mainDocumentURL] retain];
r15 = r14;
rax = [r14 URL];
rax = [rax retain];
r14 = rax;
rax = [rbx isEqual:rax];
var_-80 = r13;
if (rax != 0x0) {
r12 = [r13 statusCode];
[r14 release];
[rbx release];
r13 = r15;
if (r12 == 0xc8) {
rax = [H5Configs sharedConfigs];
rax = [rax retain];
var_-72 = rax;
r12 = [[rax titleReg] retain];
var_-64 = [var_-48 length];
var_-128 = __NSConcreteStackBlock;
*(&var_-128 + 0x8) = 0xc2000000;
*(&var_-128 + 0x10) = ___60-[Plugin4HandleProxyRequest setNavigateTitleWithHtml:event:]_block_invoke;
*(&var_-128 + 0x18) = ___block_descriptor_48_e8_32s40s_e37_v32?0"NSTextCheckingResult"8Q16^B24l;
rax = [var_-48 retain];
*(&var_-128 + 0x20) = rax;
*(&var_-128 + 0x28) = [var_-56 retain];
stack[2048] = &var_-128;
//下面的block var_-64就是上面声明的block ___60
[r12 enumerateMatchesInString:rax options:0x0 range:0x0 usingBlock:var_-64];
[r12 release];
[var_-72 release];
[*(&var_-128 + 0x28) release];
[*(&var_-128 + 0x20) release];
}
}
else {
[r14 release];
[rbx release];
r13 = r15;
}
[var_-80 release];
[r13 release];
[var_-56 release];
[var_-48 release];
return;
}
function ___60-[Plugin4HandleProxyRequest setNavigateTitleWithHtml:event:]_block_invoke {
rbx = rcx;
r14 = rdi;
rax = [rsi retain];
r13 = rax;
if ([rax numberOfRanges] >= 0x2) {
var_-56 = rbx;
rax = [r13 rangeAtIndex:0x1];
rax = [*(r14 + 0x20) substringWithRange:rax];
rax = [rax retain];
rbx = rax;
rax = [rax gtm_stringByUnescapingFromHTML_mp];
rax = [rax retain];
var_-48 = r14;
r14 = rax;
[rbx release];
rax = [NSCharacterSet whitespaceAndNewlineCharacterSet];
rax = [rax retain];
r12 = [[r14 stringByTrimmingCharactersInSet:rax] retain];
[r14 release];
[rax release];
rax = [*(var_-48 + 0x28) context];
rax = [rax retain];
r14 = rax;
rax = [rax currentViewController];
rax = [rax retain];
[rax performSelectorOnMainThread:@selector(setTitleViewTitle:) withObject:r12 waitUntilDone:0x0];
rbx = var_-56;
[rax release];
[r14 release];
[r12 release];
}
*(int8_t *)rbx = 0x1;
rax = [r13 release];
return rax;
}
8再看看旧工程伪源码
void -[Plugin4HandleProxyRequest setNavigateTitleWithHtml:event:](void * self, void * _cmd, void * arg2, void * arg3) {
var_-48 = [arg2 retain];
rbx = [arg3 retain];
r14 = [[rbx request] retain];
var_-56 = rbx;
r13 = [[rbx response] retain];
rbx = [[r14 mainDocumentURL] retain];
r15 = r14;
r14 = [[r14 URL] retain];
rax = [rbx isEqual:r14];
var_-80 = r13;
if (rax != 0x0) {
r12 = [r13 statusCode];
[r14 release];
[rbx release];
r13 = r15;
if (r12 == 0xc8) {
rax = [H5Configs sharedConfigs];
rax = [rax retain];
var_-72 = rax;
r12 = [[rax titleReg] retain];
rax = [var_-48 length];
var_-128 = __NSConcreteStackBlock;
*(&var_-128 + 0x8) = 0xc2000000;
*(&var_-128 + 0x10) = ___60-[Plugin4HandleProxyRequest setNavigateTitleWithHtml:event:]_block_invoke;
*(&var_-128 + 0x18) = ___block_descriptor_tmp.890;
rbx = [var_-48 retain];
*(&var_-128 + 0x20) = rbx;
*(&var_-128 + 0x28) = [var_-56 retain];
stack[2048] = &var_-128;
[r12 enumerateMatchesInString:rbx options:0x0 range:0x0 usingBlock:rax];
[r12 release];
[var_-72 release];
[*(&var_-128 + 0x28) release];
[*(&var_-128 + 0x20) release];
}
}
else {
[r14 release];
[rbx release];
r13 = r15;
}
[var_-80 release];
[r13 release];
[var_-56 release];
[var_-48 release];
return;
}
function ___60-[Plugin4HandleProxyRequest setNavigateTitleWithHtml:event:]_block_invoke {
rbx = rcx;
r14 = rdi;
r13 = [rsi retain];
if ([r13 numberOfRanges] >= 0x2) {
var_-56 = rbx;
var_-48 = r14;
rbx = [[*(r14 + 0x20) substringWithRange:[r13 rangeAtIndex:0x1]] retain];
r14 = [[rbx gtm_stringByUnescapingFromHTML_mp] retain];
[rbx release];
rbx = [[NSCharacterSet whitespaceAndNewlineCharacterSet] retain];
r12 = [[r14 stringByTrimmingCharactersInSet:rbx] retain];
[r14 release];
[rbx release];
r14 = [[*(var_-48 + 0x28) context] retain];
rbx = [[r14 currentViewController] retain];
[rbx performSelectorOnMainThread:@selector(setTitleViewTitle:) withObject:r12 waitUntilDone:0x0];
rdi = rbx;
rbx = var_-56;
[rdi release];
[r14 release];
[r12 release];
}
*(int8_t *)rbx = 0x1;
rax = [r13 release];
return rax;
}
9通过比较代码是一样的。
10.多次比较发现, 没有标题的同时这个block也不会走-[Plugin4HandleProxyRequest setNavigateTitleWithHtml:event:]_block_invoke
no!会走
断点要打在-[NSSimpleRegularExpressionCheckingResult numberOfRanges]
而不是-[NSTextCheckingResult numberOfRanges]
11.基线升级前的工程打印
[NSSimpleRegularExpressionCheckingResult(PC) numberOfRangesS]-!!!!!!!<NSSimpleRegularExpressionCheckingResult: 0x600002ea71c0>{1760, 19}{
<NSRegularExpression: 0x6000035bacd0> <title>([\s\S]*?)</title> 0x1}
2
NSTextCheckingResult
self->_regularExpression->_pattern:
<title>([\s\S]*?)</title>
12.替换这个方法
[r12 enumerateMatchesInString:rbx options:0x0 range:0x0 usingBlock:rax];
并在-[Plugin4HandleProxyRequest setNavigateTitleWithHtml:event:]打断点后再进入enumerateMatchesInString:rbx断点
打印传入的字符串
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport"
是html报文
结合上面的正则 是找title
13.查看numberOfRanges注释 上面打印返回值是2
/* A result must have at least one range, but may optionally have more (for
example, to represent regular expression capture groups). The range at index 0
always matches the range property. Additional ranges, if any, will have indexes
from 1 to numberOfRanges-1. rangeWithName: can be used with named regular
expression capture groups. */
@property (readonly) NSUInteger numberOfRanges
14.1在基线升级后工程打印 进一步 发现-[Plugin4HandleProxyRequest setNavigateTitleWithHtml:event:]这个没走
所以设置title方法也不会走 ,因为前后点的功能不同。
14.2 走了setNavigateTitleWithHtml:event的方法情况,判断的时候没有title正则,所以也不会继续往下走
15.这应该是原有的bug因为不重新指定h5容器也会有这个问题。
最后再看看这个规则伪源码:
void * -[H5Configs titleReg](void * self, void * _cmd) {
r14 = [[*(self + 0x110) stringValueForKey:@"h5_titleRegularExpression"] retain];
if (([r14 isKindOfClass:[NSString class]] != 0x0) && ([r14 length] != 0x0)) {
rax = [NSRegularExpression regularExpressionWithPattern:r14 options:0x1 error:0x0];
rax = [rax retain];
rbx = rax;
if (rax == 0x0) {
rbx = [[NSRegularExpression regularExpressionWithPattern:@"<title>([\s\S]*?)</title>" options:0x1 error:0x0] retain];
}
}
else {
rbx = [[NSRegularExpression regularExpressionWithPattern:@"<title>([\s\S]*?)</title>" options:0x1 error:0x0] retain];
}
[r14 release];
rax = [rbx autorelease];
return rax;
}
另外收获
1.比如替换这个方法会经常崩溃performSelectorOnMainThread:withObject:waitUntilDone:
2.NSTextCheckingResult
3.block伪源码变量分布
4.寄存器是物理设备,是不受大括号的作用域限制的
补充(总结):1.三方h5 setNavigateTitleWithHtml走的情况下:旧版代码有些功能走setTitleViewTitle方法、有些不走,不走原因是加载的html报文里没有<title>标签。
2.三方h5 setNavigateTitleWithHtml不走的情况是因为数据量大didReceiveData需要执行多次
要看-[Plugin4HandleProxyRequest insertJs:]代码逻辑 有点复杂
3.疑惑:蚂蚁的同学为什么要这么写呢?
问题1:如果注释掉了title标签代码也会走<!-- <title>我是标题</title> -->
问题2:有些html页面没有title标签,但会有其他方法设置标题,这个方法就不会走
问题3:为啥数据量大setNavigateTitleWithHtml都不会走
4.解决办法
void * -[H5WebViewController titleLabelValue](void * self, void * _cmd) {
r12 = self;
rax = [self navigationItem];
rax = [rax retain];
rbx = [[rax titleView] retain];
rdx = [H5NavigationTitleView class];
r14 = [rbx isKindOfClass:rdx];
[rbx release];
[rax release];
if (r14 != 0x0) {
rax = [r12 navigationItem];
rax = [rax retain];
r14 = [[rax titleView] retain];
[rax release];
rax = [r14 mainTitleLabel];
rax = [rax retain];
rbx = [[rax text] retain];
[rax release];
[r14 release];
}
else {
rbx = 0x0;
}
rax = [rbx autorelease];
return rax;
}