iOS 子线程读取剪切板 & iOS14 剪切板API适配

3,837 阅读2分钟

子线程读取剪切板

主线程读取剪切板时,偶现APP主线程卡死,然后程序被看门狗杀死。后来我们把读取剪切板操作放到了子线程。虽然是UIKit下的接口,但经与苹果技术人员确认,以及上线一年没回收到任何与此相关的crash,证实可行。 具体代码如下。另外,如果有多个业务方需要读取剪切板,建议放到同一队列管理。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^() {
        UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];    
        NSString *string = [pasteboard.string copy];  
        NSLog(@"pasteboard:%@",string);});
    });
}

iOS 14下读剪切板

iOS14 系统下,只要使用了pasteboard.string读取剪切板,APP顶部都会出现读取剪切板的提示。 为了保护用户隐私(避免舆论风险),可以使用iOS14 的API来判断剪切板中的内容格式。 使用这两个 API 不会触发系统剪切板提示,但是也拿不到剪切板的具体内容。

- (void)detectPatternsForPatterns:(NSSet<UIPasteboardDetectionPattern> *)patterns
                completionHandler:(void(^)(NSSet<UIPasteboardDetectionPattern> * _Nullable,
                                           NSError * _Nullable))completionHandler NS_REFINED_FOR_SWIFT API_AVAILABLE(ios(14.0));
                                           
- (void)detectPatternsForPatterns:(NSSet<UIPasteboardDetectionPattern> *)patterns
                        inItemSet:(NSIndexSet * _Nullable)itemSet
                completionHandler:(void(^)(NSArray<NSSet<UIPasteboardDetectionPattern> *> * _Nullable,
                                           NSError * _Nullable))completionHandler NS_REFINED_FOR_SWIFT API_AVAILABLE(ios(14.0));                                      

以下是子线程调用新API的代码。注意,不能在detectPatternsForPatterns的回调里直接访问剪切板,必须在下一个runloop读取,因为苹果在检测剪切板内容时,给剪切板加锁了,在回调中直接访问剪切板会造成死锁。表现为只有第一次调detectPatternsForPatterns时能收到回调, 然后子线程死锁,再也收不到detectPatternsForPatterns回调。

//获取全局队列
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(){
    //iOS14及以上,先检测剪切板内容,符合规则再读取剪切板
    if (@available(iOS 14.0, *)) {
        UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
        NSSet *pasteDetectionPatternSet = [NSSet setWithObjects:UIPasteboardDetectionPatternNumber,
                                           UIPasteboardDetectionPatternProbableWebURL,
                                           UIPasteboardDetectionPatternProbableWebSearch,
                                           nil];
        [pasteboard detectPatternsForPatterns: pasteDetectionPatternSet
                            completionHandler:^(NSSet<UIPasteboardDetectionPattern> *foundPattenSet, NSError *e) {
            BOOL isNumber = NO, isUrl = NO, isWebSearch = NO;
            for (UIPasteboardDetectionPattern pattern in foundPattenSet) {
                if ([pattern isEqualToString: UIPasteboardDetectionPatternNumber]) {
                    isNumber = YES;
                }
                if ([pattern isEqualToString: UIPasteboardDetectionPatternProbableWebURL]) {
                    isUrl = YES;
                }
                if ([pattern isEqualToString: UIPasteboardDetectionPatternProbableWebSearch]) {
                    isWebSearch = YES;
                }
            }
            if (isNumber && isUrl) {
                //符合规则,在下一个runloop读取剪切板
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^() {
                    UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
                    NSString *string = [pasteboard.string copy];
                    NSLog(@"pasteboard:%@",string);
                });
            }
        }];
    } else {
        //iOS14以下,直接读剪切板
        UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
        NSString *string = [pasteboard.string copy];
        NSLog(@"pasteboard:%@",string);
    }
}