你可能被openURL给坑了

本文介绍了一个关于 UIApplication openURL 方法导致应用卡顿甚至崩溃的问题,并提供了两种解决方案:一是通过添加延迟来避免同步等待,二是使用新的 API 来处理 URL 打开操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天使用第三方的时候出现了bug,最后发现是UIApplication openURL的问题。

现象描述:当连接xcode运行项目时,app界面会卡住一会,然后才有响应;当断开xcode时,app界面会卡住一会,然后闪退。

崩溃日志:

Exception Type:  EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d
Triggered by Thread:  0

Filtered syslog:
None found

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib        	0x000000018d48b224 mach_msg_trap + 8
1   libsystem_kernel.dylib        	0x000000018d48b09c mach_msg + 72
2   libdispatch.dylib             	0x000000018d37cee4 _dispatch_mach_send_and_wait_for_reply + 540
3   libdispatch.dylib             	0x000000018d37d2e8 dispatch_mach_send_with_result_and_wait_for_reply + 56
4   libxpc.dylib                  	0x000000018d5a4edc xpc_connection_send_message_with_reply_sync + 196
5   Foundation                    	0x000000018f0b09cc __NSXPCCONNECTION_IS_WAITING_FOR_A_SYNCHRONOUS_REPLY__ + 12
6   Foundation                    	0x000000018f0b0070 -[NSXPCConnection _sendInvocation:withProxy:remoteInterface:withErrorHandler:timeout:userInfo:] + 2940
7   CoreFoundation                	0x000000018e4b2d54 ___forwarding___ + 404
8   CoreFoundation                	0x000000018e3aed4c _CF_forwarding_prep_0 + 92
9   MobileCoreServices            	0x000000018fe64fb8 -[LSApplicationWorkspace openURL:withOptions:error:] + 248
10  UIKit                         	0x000000019486acac -[UIApplication _openURL:] + 144
11  CouponPurchase                	0x000000010085d27c 0x100000000 + 8770172
12  CouponPurchase                	0x0000000100100dac 0x100000000 + 1052076
13  CouponPurchase                	0x00000001003bf108 0x100000000 + 3928328
14  CouponPurchase                	0x0000000100381008 0x100000000 + 3674120
15  CouponPurchase                	0x00000001003be370 0x100000000 + 3924848
16  CouponPurchase                	0x00000001003be184 0x100000000 + 3924356
17  CouponPurchase                	0x00000001003be2f0 0x100000000 + 3924720
18  CouponPurchase                	0x0000000100368cac 0x100000000 + 3574956
19  CouponPurchase                	0x0000000100368a60 0x100000000 + 3574368
20  CoreFoundation                	0x000000018e4b2d54 ___forwarding___ + 404
21  CoreFoundation                	0x000000018e3aed4c _CF_forwarding_prep_0 + 92
22  UIKit                         	0x000000019486bc28 __45-[UIApplication _applicationOpenURL:payload:]_block_invoke + 752
23  UIKit                         	0x000000019486b6b0 -[UIApplication _applicationOpenURL:payload:] + 644
24  UIKit                         	0x0000000194874474 -[UIApplication _handleNonLaunchSpecificActions:forScene:withTransitionContext:completion:] + 6260
25  UIKit                         	0x0000000194877bb4 __88-[UIApplication _handleApplicationLifecycleEventWithScene:transitionContext:completion:]_block_invoke + 

注意这里的异常是__NSXPCCONNECTION_IS_WAITING_FOR_A_SYNCHRONOUS_REPLY__ + 12,等待同步回复。

而且,我注意到一个现象,每当走我自己的路由协议,然后在调用第三方的时候就会出现,而如果直接调用第三方方法就不会出现。

所以,定位到了路由相关的地方。而上面信息中有UIApplication _openURL:] + 144信息。所以,猜测是这个方法多次调用,由于方法没有完成导致的同步等待。

如果你在网上搜一下openURL:,就会出现关于 调用openURL:方法响应慢的解决办法 这些信息。具体的解决办法就是加一个延时。

下面列出测试用例:

为了重现bug,先给项目添加一个ms协议,然后添加如下代码:

- (void)test_111
{
    NSLog(@"111 - start");
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"ms://www.baidu.com"]];
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"ms://www.baidu.com"]];
    NSLog(@"222 - end");
    
    // 打印日志
    /*
     2018-09-11 17:34:45.106242+0800 TestOpenUrl[10813:16592945] 111 - start
     2018-09-11 17:34:55.161707+0800 TestOpenUrl[10813:16592945] 222 - end
     */
}

可以看到,这里日志时间相差了10秒钟。


如果使用了延时呢?

- (void)test_222
{
    NSLog(@"222 - start");
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"ms://www.baidu.com"]];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.001 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"xxxx"]];
    });
    NSLog(@"222 - end");
    
    // 打印日志
    /*
     2018-09-11 17:36:24.810128+0800 TestOpenUrl[10896:16598430] 222 - start
     2018-09-11 17:36:24.853829+0800 TestOpenUrl[10896:16598430] 222 - end
     */
}

可以看到,这里时间间隔就变得很短了。确实解决了卡顿的问题。


其实还有一种解决方法:

- (void)test_333
{
    NSLog(@"333 - start");
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"xxx"] options:nil completionHandler:nil];
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"xxx"] options:nil completionHandler:nil];
     NSLog(@"333 - end");
    
    // 打印日志
    /*
     2018-09-11 17:37:17.812804+0800 TestOpenUrl[10945:16601725] 333 - start
     2018-09-11 17:37:17.813552+0800 TestOpenUrl[10945:16601725] 333 - end
     */
}

使用新的api也能够解决这个问题。但是该api必要在iOS10以上使用。所以如果你的项目需要适配iOS10以下的系统,还是乖乖的加上延时吧。


虽然延时可以解决这个问题,但是我一直觉得不靠谱,到底延时多久才好呢?其实该问题的关键就是如何知道openURL:方法执行完毕的时机。如果等到openURL:执行完毕之后再调用openURL:方法,就不会出现问题。而openURL:执行完毕的时机,我费尽心思,还是拿不到。。。。

如果哪位大神知道,烦请告知。

上面完整测试用例在这里

// WM_TIMER处理函数 void CMESRemindDlg::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent != 1) { CDialogEx::OnTimer(nIDEvent); return; } CTime currentTime = CTime::GetCurrentTime(); CTime currentMinute(currentTime.GetYear(), currentTime.GetMonth(),currentTime.GetDay(), currentTime.GetHour(),currentTime.GetMinute(), 0); bool triggered = false; CEdit* mainControls[] = { &m_upTime1, &m_upTime2, &m_dwTime1, &m_dwTime2, &m_niTime1 }; for (int i = 0; i < _countof(mainControls); ++i) { CString strTime; mainControls[i]->GetWindowText(strTime); int hour, minute; if (ParseTime(strTime, hour, minute)) { CTime targetTime( currentTime.GetYear(), currentTime.GetMonth(), currentTime.GetDay(), hour, minute, 0 ); // 检查是否匹配当前时间 if (currentMinute == targetTime && m_lastTrigger != targetTime) { openURL(MES_URL); m_lastTrigger = targetTime; triggered = true; break; // 匹配后停止检查其他控件 } } } if (!triggered) { CString niTime2; m_niTime2.GetWindowText(niTime2); if (!niTime2.IsEmpty()) { int hour, minute; if (ParseTime(niTime2, hour, minute)) { CTime targetTime( currentTime.GetYear(), currentTime.GetMonth(), currentTime.GetDay(), hour, minute, 0 ); if (currentMinute == targetTime && m_lastTrigger != targetTime) { openURL(MES_URL); m_lastTrigger = targetTime; triggered = true; } } } else { if (m_lastEmptyTrigger.GetTime() == 0) { //openURL(MES_URL); m_lastEmptyTrigger = currentTime; } else { CTimeSpan elapsed = currentTime - m_lastEmptyTrigger; if (elapsed.GetTotalMinutes() >= EMPTY_ALERT_INTERVAL) { openURL(MES_URL); m_lastEmptyTrigger = currentTime; } } } } }阅读这个代码
06-26
我需要根据这个调整 不动前面的逻辑 更改固定时间30分钟调用URL 为可自行调整时间1分钟或者2分钟的间隔,不能修改前部分的内容只需要改 当 m_niTime2 为空(未设置)且当前时间超过 m_niTime1 时,每 30 分钟触发一次,这个时间间隔 这是我的函数 void CMESRemindDlg::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent != 1) { CDialogEx::OnTimer(nIDEvent); return; } CTime currentTime = CTime::GetCurrentTime(); CTime currentMinute(currentTime.GetYear(), currentTime.GetMonth(), currentTime.GetDay(), currentTime.GetHour(), currentTime.GetMinute(), 0); bool triggered = false; // 原有主控件检查逻辑保持不变 CEdit* mainControls[] = { &m_upTime1, &m_upTime2, &m_dwTime1, &m_dwTime2, &m_niTime1 }; for (int i = 0; i < _countof(mainControls); ++i) { CString strTime; mainControls[i]->GetWindowText(strTime); int hour, minute; if (ParseTime(strTime, hour, minute)) { CTime targetTime( currentTime.GetYear(), currentTime.GetMonth(), currentTime.GetDay(), hour, minute, 0 ); // 检查是否匹配当前时间 if (currentMinute == targetTime && m_lastTrigger != targetTime) { openURL(MES_URL); m_lastTrigger = targetTime; triggered = true; break; // 匹配后停止检查其他控件 } } } if (!triggered) { CString niTime2; m_niTime2.GetWindowText(niTime2); if (!niTime2.IsEmpty()) { int hour, minute; if (ParseTime(niTime2, hour, minute)) { CTime targetTime( currentTime.GetYear(), currentTime.GetMonth(), currentTime.GetDay(), hour, minute, 0 ); if (currentMinute == targetTime && m_lastTrigger != targetTime) { openURL(MES_URL); m_lastTrigger = targetTime; triggered = true; } } } else { // 解析m_niTime1中的时间 CString strTime1; m_niTime1.GetWindowText(strTime1); int hour1, minute1; if (ParseTime(strTime1, hour1, minute1)) { // 创建m_niTime1的CTime对象 CTime time1( currentTime.GetYear(), currentTime.GetMonth(), currentTime.GetDay(), hour1, minute1, 0 ); // 检查是否满足新增条件 if (currentTime > time1) { // 30分钟触发逻辑 if (m_last30MinTrigger.GetTime() == 0) { openURL(MES_URL); m_last30MinTrigger = currentTime; } else { CTimeSpan elapsed = currentTime - m_last30MinTrigger; if (elapsed.GetTotalMinutes() >= 30) { openURL(MES_URL); m_last30MinTrigger = currentTime; } } } else { // 保留原有空字符串处理逻辑 if (m_lastEmptyTrigger.GetTime() == 0) { m_lastEmptyTrigger = currentTime; } else { CTimeSpan elapsed = currentTime - m_lastEmptyTrigger; if (elapsed.GetTotalMinutes() >= EMPTY_ALERT_INTERVAL) { openURL(MES_URL); m_lastEmptyTrigger = currentTime; } } } } else { // 保留原有空字符串处理逻辑 if (m_lastEmptyTrigger.GetTime() == 0) { m_lastEmptyTrigger = currentTime; } else { CTimeSpan elapsed = currentTime - m_lastEmptyTrigger; if (elapsed.GetTotalMinutes() >= EMPTY_ALERT_INTERVAL) { openURL(MES_URL); m_lastEmptyTrigger = currentTime; } } } } } }
06-26
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值