处理上架的问题
1:处理了openurl 拉起问题 2:去掉了http 3 隐私等等
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
// KBExtensionAppLauncher.h
|
||||
// CustomKeyboard
|
||||
//
|
||||
// 封装:在键盘扩展中拉起主 App(Scheme / Universal Link + 响应链兜底)。
|
||||
// 封装:在键盘扩展中拉起主 App(Scheme / Universal Link)。
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
@@ -12,23 +12,25 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@interface KBExtensionAppLauncher : NSObject
|
||||
|
||||
/// 通用入口:优先尝试 primaryURL,失败后尝试 fallbackURL,
|
||||
/// 两者都失败时再通过响应链(openURL:)做兜底。
|
||||
/// 均通过 `extensionContext openURL` 发起跳转(避免使用扩展禁用 API/响应链绕行)。
|
||||
/// 若开启 `KB_URL_BRIDGE_ENABLE=1`,会在两次 `extensionContext openURL` 均失败时,
|
||||
/// 再尝试一次“响应链 openURL”兜底(上架有风险,请谨慎开启)。
|
||||
/// - Parameters:
|
||||
/// - primaryURL: 第一优先尝试的 URL(可为 Scheme 或 UL)
|
||||
/// - fallbackURL: 失败时的备用 URL(可为 nil)
|
||||
/// - ivc: 当前的 UIInputViewController(用于 extensionContext openURL)
|
||||
/// - source: 兜底时用作起点的 responder(通常传 self 或 self.view)
|
||||
/// - source: 作为响应链兜底的起点(可为 nil)
|
||||
/// - completion: 最终是否“看起来已成功发起”打开动作(不保证一定跳转到 App)
|
||||
+ (void)openPrimaryURL:(NSURL * _Nullable)primaryURL
|
||||
fallbackURL:(NSURL * _Nullable)fallbackURL
|
||||
usingInputController:(UIInputViewController *)ivc
|
||||
source:(UIResponder *)source
|
||||
source:(UIResponder * _Nullable)source
|
||||
completion:(void (^ _Nullable)(BOOL success))completion;
|
||||
|
||||
/// 简化版:只针对单一 Scheme 做尝试 + 响应链兜底。
|
||||
/// 简化版:只针对单一 Scheme 做尝试。
|
||||
+ (void)openScheme:(NSURL *)scheme
|
||||
usingInputController:(UIInputViewController *)ivc
|
||||
source:(UIResponder *)source
|
||||
source:(UIResponder * _Nullable)source
|
||||
completion:(void (^ _Nullable)(BOOL success))completion;
|
||||
|
||||
@end
|
||||
|
||||
@@ -4,15 +4,86 @@
|
||||
//
|
||||
|
||||
#import "KBExtensionAppLauncher.h"
|
||||
|
||||
#if KB_URL_BRIDGE_ENABLE
|
||||
#import <objc/message.h>
|
||||
#endif
|
||||
|
||||
@implementation KBExtensionAppLauncher
|
||||
|
||||
#if KB_URL_BRIDGE_ENABLE
|
||||
+ (BOOL)kb_openURLViaResponderChain:(NSURL *)url
|
||||
source:(nullable UIResponder *)source
|
||||
completion:(void (^ _Nullable)(BOOL success))completion {
|
||||
if (!url) {
|
||||
if (completion) { completion(NO); }
|
||||
return NO;
|
||||
}
|
||||
|
||||
UIResponder *responder = source;
|
||||
|
||||
// 优先尝试 openURL:options:completionHandler:
|
||||
// 注意:在键盘扩展里走“响应链兜底”本身就存在不确定性;不同系统/宿主 App 的实现
|
||||
// 可能对 options 参数的类型有不同假设。为避免类型不匹配导致崩溃,options 统一传 nil。
|
||||
SEL openURLOptionsSel = NSSelectorFromString(@"openURL:options:completionHandler:");
|
||||
while (responder) {
|
||||
if ([responder respondsToSelector:openURLOptionsSel]) {
|
||||
void (*msgSend)(id, SEL, NSURL *, id, void (^)(BOOL)) = (void *)objc_msgSend;
|
||||
msgSend(responder, openURLOptionsSel, url, nil, ^(BOOL ok) {
|
||||
if (completion) { completion(ok); }
|
||||
});
|
||||
return YES;
|
||||
}
|
||||
responder = responder.nextResponder;
|
||||
}
|
||||
|
||||
// 尝试 openURL:completionHandler:
|
||||
responder = source;
|
||||
SEL openURLCompletionSel = NSSelectorFromString(@"openURL:completionHandler:");
|
||||
while (responder) {
|
||||
if ([responder respondsToSelector:openURLCompletionSel]) {
|
||||
void (*msgSend)(id, SEL, NSURL *, void (^)(BOOL)) = (void *)objc_msgSend;
|
||||
msgSend(responder, openURLCompletionSel, url, ^(BOOL ok) {
|
||||
if (completion) { completion(ok); }
|
||||
});
|
||||
return YES;
|
||||
}
|
||||
responder = responder.nextResponder;
|
||||
}
|
||||
|
||||
// 兜底:openURL:
|
||||
responder = source;
|
||||
SEL openURLSel = NSSelectorFromString(@"openURL:");
|
||||
while (responder) {
|
||||
if ([responder respondsToSelector:openURLSel]) {
|
||||
BOOL (*msgSend)(id, SEL, NSURL *) = (void *)objc_msgSend;
|
||||
BOOL ok = msgSend(responder, openURLSel, url);
|
||||
if (completion) { completion(ok); }
|
||||
return YES;
|
||||
}
|
||||
responder = responder.nextResponder;
|
||||
}
|
||||
|
||||
if (completion) { completion(NO); }
|
||||
return NO;
|
||||
}
|
||||
#endif
|
||||
|
||||
+ (void)openPrimaryURL:(NSURL * _Nullable)primaryURL
|
||||
fallbackURL:(NSURL * _Nullable)fallbackURL
|
||||
usingInputController:(UIInputViewController *)ivc
|
||||
source:(UIResponder *)source
|
||||
source:(UIResponder * _Nullable)source
|
||||
completion:(void (^ _Nullable)(BOOL success))completion {
|
||||
if (![NSThread isMainThread]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self openPrimaryURL:primaryURL
|
||||
fallbackURL:fallbackURL
|
||||
usingInputController:ivc
|
||||
source:source
|
||||
completion:completion];
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!ivc || (!primaryURL && !fallbackURL)) {
|
||||
if (completion) { completion(NO); }
|
||||
return;
|
||||
@@ -48,19 +119,38 @@
|
||||
finish(YES);
|
||||
return;
|
||||
}
|
||||
BOOL bridged = [self p_bridgeFirst:first second:second from:source];
|
||||
finish(bridged);
|
||||
|
||||
#if KB_URL_BRIDGE_ENABLE
|
||||
// 注意:上架有风险。仅当确实存在“宿主 App 拦截 extensionContext openURL”
|
||||
// 的场景且业务强依赖时才开启此兜底。
|
||||
UIResponder *start = (source ?: (UIResponder *)ivc.view ?: (UIResponder *)ivc);
|
||||
[self kb_openURLViaResponderChain:second
|
||||
source:start
|
||||
completion:^(BOOL ok3) {
|
||||
finish(ok3);
|
||||
}];
|
||||
#else
|
||||
finish(NO);
|
||||
#endif
|
||||
}];
|
||||
} else {
|
||||
BOOL bridged = [self p_bridgeFirst:first second:nil from:source];
|
||||
finish(bridged);
|
||||
#if KB_URL_BRIDGE_ENABLE
|
||||
UIResponder *start = (source ?: (UIResponder *)ivc.view ?: (UIResponder *)ivc);
|
||||
[self kb_openURLViaResponderChain:first
|
||||
source:start
|
||||
completion:^(BOOL ok3) {
|
||||
finish(ok3);
|
||||
}];
|
||||
#else
|
||||
finish(NO);
|
||||
#endif
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)openScheme:(NSURL *)scheme
|
||||
usingInputController:(UIInputViewController *)ivc
|
||||
source:(UIResponder *)source
|
||||
source:(UIResponder * _Nullable)source
|
||||
completion:(void (^ _Nullable)(BOOL success))completion {
|
||||
[self openPrimaryURL:scheme
|
||||
fallbackURL:nil
|
||||
@@ -69,53 +159,4 @@
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user