launchOptions的key

iOS应用启动选项解析
本文详细解析了iOS应用启动时的几种不同情况及其处理方式,包括直接启动、通过URL启动、本地通知启动及远程通知启动等场景。文章还介绍了如何在启动过程中根据不同情况进行相应的业务逻辑处理。

 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
当应用程序启动时执行,应用程序启动入口。只在应用程序启动时执行一次。application参数用来获取应用程序的状态、变量等,值得注意的是字典参数:(NSDictionary *)launchOptions,该参数存储程序启动的原因。若用户直接启动,lauchOptions内无数据;若由其他应用程序通过openURL:启动,则UIApplicationLaunchOptionsURLKey对应的对象为启动URL(NSURL),UIApplicationLaunchOptionsSourceApplicationKey对应启动的源应用程序的bundle ID (NSString);若由本地通知启动,则UIApplicationLaunchOptionsLocalNotificationKey对应的是为启动应用程序的的本地通知对象(UILocalNotification);若由远程通知启动,则UIApplicationLaunchOptionsRemoteNotificationKey对应的是启动应用程序的的远程通知信息userInfo(NSDictionary);其他key还有UIApplicationLaunchOptionsAnnotationKey,UIApplicationLaunchOptionsLocationKey,UIApplicationLaunchOptionsNewsstandDownloadsKey。 如果要在启动时,做出一些区分,那就需要在下面的代码做处理。 比如:应用可以被某个其它应用调起(作为该应用的子应用),要实现单点登录,那就需要在启动代码的地方做出合理的验证,并跳过登录。 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{ 
NSURL *url = [options objectForKey:UIApplicationLaunchOptionsURLKey]; 
if(url) 
{ 
} 
NSString *bundleId = [options objectForKey:UIApplicationLaunchOptionsSourceApplicationKey];
if(bundleId) 
{ 
} 
UILocalNotification * localNotify = [options objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if(localNotify) 
{ 
} 
NSDictionary * userInfo = [options objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if(userInfo) 
{ 
} 
}


#import <Foundation/Foundation.h> #import <Security/Security.h> #import <UIKit/UIKit.h> #import <CommonCrypto/CommonDigest.h> @interface GlobalSecurityManager : NSObject + (void)performComprehensiveSecurityScan; + (BOOL)validateSecureInput:(NSString *)input; + (void)handleSecurityException:(NSException *)exception; @end @implementation GlobalSecurityManager // 执行全局安全扫描 + (void)performComprehensiveSecurityScan { @try { // 1. 设备安全检测 [self checkDeviceSecurity]; // 2. 应用完整性验证 [self verifyAppIntegrity]; // 3. 数据安全防护 [self secureDataStorage]; // 4. 网络安全检查 [self checkNetworkSecurity]; NSLog(@"✅ Global security scan completed successfully"); } @catch (NSException *exception) { [self handleSecurityException:exception]; } @finally { [self logSecurityEvent:@"Security scan executed"]; } } #pragma mark - 安全检测方法 // 设备安全检测(越狱/调试) + (void)checkDeviceSecurity { // 越狱检测 if ([self isDeviceJailbroken]) { [NSException raise:@"DeviceCompromised" format:@"Jailbreak detected. Security compromised"]; } // 调试器检测 if ([self isDebuggerAttached]) { [NSException raise:@"DebuggerDetected" format:@"Debugger attached. Potential security breach"]; } // 屏幕截图检测 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(detectScreenshot) name:UIApplicationUserDidTakeScreenshotNotification object:nil]; } // 应用完整性验证 + (void)verifyAppIntegrity { // 1. 签名验证 if (![self verifyCodeSignature]) { [NSException raise:@"InvalidSignature" format:@"Application signature verification failed"]; } // 2. 篡改检测 if ([self isAppTampered]) { [NSException raise:@"AppTampered" format:@"Application binary has been modified"]; } } // 数据安全防护 + (void)secureDataStorage { // Keychain安全配置 NSDictionary *keychainQuery = @{ (id)kSecClass: (id)kSecClassGenericPassword, (id)kSecAttrAccessible: (id)kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, (id)kSecUseAuthenticationUI: (id)kSecUseAuthenticationUIFail }; OSStatus status = SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL); if (status != errSecSuccess && status != errSecDuplicateItem) { [NSException raise:@"KeychainError" format:@"Keychain security configuration failed with status: %d", status]; } } // 网络安全检查 + (void)checkNetworkSecurity { // SSL证书锁定 [self enforceSSLPinning]; // 网络配置检查 if (![self verifyNetworkSecuritySettings]) { [NSException raise:@"NetworkConfigError" format:@"Insecure network configuration detected"]; } } #pragma mark - 安全检测实现 // 越狱检测 + (BOOL)isDeviceJailbroken { // 检查越狱常见路径 NSArray *jailbreakIndicators = @[ @"/Applications/Cydia.app", @"/usr/sbin/sshd", @"/bin/bash", @"/etc/apt" ]; for (NSString *path in jailbreakIndicators) { if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { return YES; } } // 尝试写入系统目录 NSString *testPath = @"/private/jailbreak_test.txt"; NSError *error; [@"test" writeToFile:testPath atomically:YES encoding:NSUTF8StringEncoding error:&error]; if (!error) { [[NSFileManager defaultManager] removeItemAtPath:testPath error:nil]; return YES; } return NO; } // 调试器检测 + (BOOL)isDebuggerAttached { struct kinfo_proc info; size_t info_size = sizeof(info); int name[4]; name[0] = CTL_KERN; name[1] = KERN_PROC; name[2] = KERN_PROC_PID; name[3] = getpid(); if (sysctl(name, 4, &info, &info_size, NULL, 0) == -1) { NSLog(@"sysctl failed: %s", strerror(errno)); return NO; } return (info.kp_proc.p_flag & P_TRACED) != 0; } // 签名验证(解决引用[3]的证书问题) + (BOOL)verifyCodeSignature { NSString *bundlePath = [[NSBundle mainBundle] bundlePath]; SecStaticCodeRef staticCode = NULL; OSStatus status = SecStaticCodeCreateWithPath((__bridge CFURLRef)[NSURL fileURLWithPath:bundlePath], kSecCSDefaultFlags, &staticCode); if (status != errSecSuccess) { return NO; } // 验证签名 status = SecStaticCodeCheckValidity(staticCode, kSecCSDefaultFlags, NULL); CFRelease(staticCode); return (status == errSecSuccess); } #pragma mark - 错误处理(解决引用[1][2]的KVC问题) // 安全设置属性值(避免setValue:forUndefinedKey错误) + (void)safeSetValue:(id)value forKey:(NSString *)key onObject:(id)object { if (!object || !key) return; @try { // 检查对象是否响应选择器 if ([object respondsToSelector:NSSelectorFromString(key)]) { [object setValue:value forKey:key]; } // 检查嵌套控制器(解决引用[1]的问题) else if ([object isKindOfClass:[UINavigationController class]]) { UIViewController *topVC = [(UINavigationController *)object topViewController]; if ([topVC respondsToSelector:NSSelectorFromString(key)]) { [topVC setValue:value forKey:key]; } } } @catch (NSException *exception) { [self handleKeyValueException:exception forObject:object key:key]; } } // 处理KVC异常 + (void)handleKeyValueException:(NSException *)exception forObject:(id)object key:(NSString *)key { NSString *errorDetail = [NSString stringWithFormat:@"KVC Error on %@: %@", NSStringFromClass([object class]), exception.reason]; [self logSecurityEvent:errorDetail]; // 安全恢复措施 if ([exception.name isEqualToString:@"NSUnknownKeyException"]) { @try { if ([object respondsToSelector:@selector(setValue:forUndefinedKey:)]) { [object setValue:nil forUndefinedKey:key]; } } @catch (NSException *secondaryException) { [self handleSecurityException:secondaryException]; } } } // 全局异常处理 + (void)handleSecurityException:(NSException *)exception { // 1. 安全日志记录 [self logSecurityEvent:[NSString stringWithFormat:@"CRITICAL: %@", exception.reason]]; // 2. 清除敏感数据 [self purgeSensitiveData]; // 3. 安全恢复或退出 if ([exception.name isEqualToString:@"DeviceCompromised"] || [exception.name isEqualToString:@"AppTampered"]) { // 严重安全问题,安全退出 [self showSecurityAlert:exception.reason]; exit(EXIT_FAILURE); } else { // 可恢复错误,通知用户 [self showSecurityAlert:@"Security issue detected. Some features may be limited"]; } } #pragma mark - 辅助方法 // 显示安全警告 + (void)showSecurityAlert:(NSString *)message { dispatch_async(dispatch_get_main_queue(), ^{ UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Security Alert" message:message preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]]; UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController; [rootVC presentViewController:alert animated:YES completion:nil]; }); } // 清除敏感数据 + (void)purgeSensitiveData { // 清除用户数据 [[NSUserDefaults standardUserDefaults] removePersistentDomainForName:[[NSBundle mainBundle] bundleIdentifier]]; // 清除Keychain数据 NSDictionary *query = @{(id)kSecClass: (id)kSecClassGenericPassword}; SecItemDelete((__bridge CFDictionaryRef)query); } // 安全日志记录 + (void)logSecurityEvent:(NSString *)event { NSLog(@"🔒 SECURITY LOG: %@", event); // 实际应用中应发送到安全服务器 } @end // 在AppDelegate中调用 @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 执行全局安全扫描 [GlobalSecurityManager performComprehensiveSecurityScan]; // 安全设置示例(避免引用[1][2]的错误) DetailViewController *detailVC = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:@"DetailVC"]; [GlobalSecurityManager safeSetValue:self.selectedItem forKey:@"detailItem" onObject:detailVC]; return YES; } @end 结合修复
08-23
// App.vue export default { onLaunch: function(options) { console.log('App Launch with options:', options); // 处理 Universal Links / App Links // iOS上,Universal Link的URL会通过 launchOptions.userActivity.webpageURL 传递 // Android上,App Link的URL会通过启动intent的data传递,Uni-app通常会将其解析到options.query或path // #ifdef APP-PLUS // 对于iOS Universal Links的特定处理 if (uni.getSystemInfoSync().platform === 'ios') { const userActivity = plus.ios.getLastUserActivity && plus.ios.getLastUserActivity(); if (userActivity && userActivity.webpageURL) { console.log('iOS Universal Link launched with webpageURL:', userActivity.webpageURL); this.handleDeepLinkUrl(userActivity.webpageURL); return; } } // #endif // options.query 通常包含了 URL Scheme 或 App Link 的参数 // options.path 是启动路径,但我们更关心query中的自定义参数 if (options && options.query) { this.handleLaunchQuery(options.query); } else if (options && options.path && (options.path.startsWith('http') || options.path.startsWith('https:'))) { // Android AppLink 可能会把整个URL放在path,或者uni-app包装后还是在query // 需要测试具体情况 this.handleDeepLinkUrl(options.path); } }, onShow: function(options) { console.log('App Show with options:', options); // App从后台被唤起时也可能携带参数 // #ifdef APP-PLUS if (uni.getSystemInfoSync().platform === 'ios') { const userActivity = plus.ios.getLastUserActivity && plus.ios.getLastUserActivity(); if (userActivity && userActivity.webpageURL && globalData.isFromDeepLinkPause) { // globalData.isFromDeepLinkPause 是自定义标志,避免重复处理 console.log('iOS Universal Link resumed with webpageURL:', userActivity.webpageURL); this.handleDeepLinkUrl(userActivity.webpageURL); globalData.isFromDeepLinkPause = false; // 重置标志 return; } } // #endif if (options && options.query && globalData.isFromDeepLinkPause) { this.handleLaunchQuery(options.query); globalData.isFromDeepLinkPause = false; // 重置标志 } else if (options && options.path && (options.path.startsWith('http') || options.path.startsWith('https:')) && globalData.isFromDeepLinkPause) { this.handleDeepLinkUrl(options.path); globalData.isFromDeepLinkPause = false; } }, onHide() { // App进入后台时,可以设置一个标志,以便onShow时判断是否由深链唤起 // 这对于某些情况下的参数传递有帮助 globalData.isFromDeepLinkPause = true; }, globalData: { // 定义全局变量 isFromDeepLinkPause: false }, methods: { handleDeepLinkUrl(url) { console.log('Handling full deep link URL:', url); // 解析HTTPS URL中的参数 // 例如: https://yourdomain.com/uniapp/open/item?page=/pages/product/detail&id=123 // 或 https://yourdomain.com/product/123 try { const urlObj = new URL(url); // Node.js URL polyfill might be needed or manual parsing const params = {}; urlObj.searchParams.forEach((value, key) => { params[key] = value; }); // 如果路径中也包含信息,如 /product/123 const pathSegments = urlObj.pathname.split('/').filter(Boolean); if (pathSegments.length > 0 && pathSegments[0] === 'product' && pathSegments[1]) { params.page = '/pages/product/detail'; // 预定义映射 params.id = pathSegments[1]; } // ... 可以根据你的URL结构添加更多解析逻辑 if (params.page) { let targetUrl = params.page; const queryParams = []; for (const key in params) { if (key !== 'page') { queryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`); } } if (queryParams.length > 0) { targetUrl += '?' + queryParams.join('&'); } console.log('Navigating to:', targetUrl); uni.navigateTo({ url: targetUrl, fail: (err) => { console.error('Failed to navigate:', err); // 跳转失败,可以尝试跳转到首页 uni.switchTab({ url: '/pages/index/index' }); } }); } else { // 没有页面参数,跳转到首页 uni.switchTab({ url: '/pages/index/index' }); } } catch (e) { console.error("Error parsing deep link URL:", e); uni.switchTab({ url: '/pages/index/index' }); } }, handleLaunchQuery(query) { // 处理URL Scheme中的query或者App Link解析后的query // query = { page: '/pages/detail/detail', id: '123' } console.log('Handling launch query:', query); const targetPage = query.page; if (targetPage) { let navigationUrl = decodeURIComponent(targetPage); const queryParams = []; for (const key in query) { if (key !== 'page') { queryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(query[key])}`); } } if (queryParams.length > 0) { navigationUrl += '?' + queryParams.join('&'); } // 确保页面加载完成后再跳转 setTimeout(() => { uni.navigateTo({ url: navigationUrl, fail: (err) => { console.error('Failed to navigate from query:', err, navigationUrl); // 跳转失败,可以尝试跳转到首页 uni.switchTab({ url: '/pages/index/index' }); } }); }, 500); // 延迟一点时间确保首页或其他基础内容已加载 } else { // 没有页面参数,跳转到首页 uni.switchTab({ url: '/pages/index/index' }); } } } }
06-25
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值