// // KBExtensionAppLauncher.m // CustomKeyboard // #import "KBExtensionAppLauncher.h" #import @implementation KBExtensionAppLauncher + (void)openPrimaryURL:(NSURL * _Nullable)primaryURL fallbackURL:(NSURL * _Nullable)fallbackURL usingInputController:(UIInputViewController *)ivc source:(UIResponder *)source completion:(void (^ _Nullable)(BOOL success))completion { if (!ivc || (!primaryURL && !fallbackURL)) { if (completion) { completion(NO); } return; } // 保证在主线程回调,避免调用方再做一次 dispatch。 void (^finish)(BOOL) = ^(BOOL ok){ if (!completion) return; if ([NSThread isMainThread]) { completion(ok); } else { dispatch_async(dispatch_get_main_queue(), ^{ completion(ok); }); } }; NSURL *first = primaryURL ?: fallbackURL; NSURL *second = (first == primaryURL) ? fallbackURL : nil; if (!first) { finish(NO); return; } [ivc.extensionContext openURL:first completionHandler:^(BOOL ok) { if (ok) { finish(YES); return; } if (second) { [ivc.extensionContext openURL:second completionHandler:^(BOOL ok2) { if (ok2) { finish(YES); return; } BOOL bridged = [self p_bridgeFirst:first second:second from:source]; finish(bridged); }]; } else { BOOL bridged = [self p_bridgeFirst:first second:nil from:source]; finish(bridged); } }]; } + (void)openScheme:(NSURL *)scheme usingInputController:(UIInputViewController *)ivc source:(UIResponder *)source completion:(void (^ _Nullable)(BOOL success))completion { [self openPrimaryURL:scheme fallbackURL:nil usingInputController:ivc source:source completion:completion]; } #pragma mark - Private // 通过响应链尝试调用 openURL:(等价于原 KBURLOpenBridge 实现) + (BOOL)p_openURLViaResponder:(NSURL *)url from:(UIResponder *)start { #if KB_URL_BRIDGE_ENABLE if (!url || !start) return NO; SEL sel = NSSelectorFromString(@"openURL:"); UIResponder *responder = start; while (responder) { @try { if ([responder respondsToSelector:sel]) { BOOL handled = NO; BOOL (*funcBool)(id, SEL, NSURL *) = (BOOL (*)(id, SEL, NSURL *))objc_msgSend; if (funcBool) { handled = funcBool(responder, sel, url); } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [responder performSelector:sel withObject:url]; handled = YES; #pragma clang diagnostic pop } return handled; } } @catch (__unused NSException *e) { // ignore and continue } responder = responder.nextResponder; } return NO; #else (void)url; (void)start; return NO; #endif } + (BOOL)p_bridgeFirst:(NSURL * _Nullable)first second:(NSURL * _Nullable)second from:(UIResponder *)source { BOOL bridged = NO; if (first) { bridged = [self p_openURLViaResponder:first from:source]; } if (!bridged && second) { bridged = [self p_openURLViaResponder:second from:source]; } return bridged; } @end