Tencent / QMUI_iOS

QMUI iOS——致力于提高项目 UI 开发效率的解决方案
http://qmuiteam.com/ios
Other
7.09k stars 1.38k forks source link

[iOS 13] Access to ivar is prohibited #617

Closed MoLice closed 5 years ago

MoLice commented 5 years ago

In iOS 13, we can not use KVC(valueForKey:, setValue:forKey:) to access some private APIs, it causes the crash of UISearchBar (QMUI), UINavigationBar (QMUI) and UITabBar (QMUI).

ziecho commented 5 years ago

以下记录 iOS 13 系统禁止通过 KVC 访问的几种实现方式:

  1. UITextField

    UITextField *textField = [UITextField new];
    [textField valueForKey:@"_placeholderLabel"];

    系统的 UITextField 重写了 valueForKey: 拦截了外部的取值,实现如下:

    
    @implementation UITextField

@end


简单解决:
去掉下划线即可 `[textField valueForKey:@"placeholderLabel"];`

2. **UISearchBar**

```objc
UISearchBar *bar = [UISearchBar new];
[bar setValue:@"test" forKey:@"_cancelButtonText"]
UIView *searchField = [bar valueForKey:@"_searchField"];

根据 KVC 的实现,会先去找名为 set_cancelButtonText 的方法,所以系统内部重写了这个方法,什么事都不干,专门用来拦截 KVC,实现如下:

- (void)set_cancelButtonText:(NSString *)text {
    [NSException raise:NSGenericException format:@"Access to UISearchBar's set_cancelButtonText: ivar is prohibited. This is an application bug"];
    [self _setCancelButtonText];
}

拦截 _searchField:

- (void)_searchField {
    [NSException raise:NSGenericException format:@"Access to UISearchBar's _searchField ivar is prohibited. This is an application bug"];
    [self searchField];
}

简单解决: 直接调用 _setCancelButtonText, searchField

ziecho commented 5 years ago

根据上面提到的原理,这里提供一种全局绕过这个禁止的方法供参考。 请注意:这只是一种临时的参考方案,我们 不推荐 开发者这么做, 因为访问私有属性会带来了不确定和不稳定性,少了苹果的警告会让你无节制去访问使用各种属性,随着系统的升级这私有属性会面临改动和失效的风险。

@implementation NSException (DisableUIKVCAccessProhibited)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getClassMethod(self, @selector(raise:format:));
        Method swizzlingMethod = class_getClassMethod(self, @selector(sw_raise:format:));
        method_exchangeImplementations(originalMethod, swizzlingMethod);

    });
}

+ (void)sw_raise:(NSExceptionName)raise format:(NSString *)format, ... {
    if (raise == NSGenericException && [format isEqualToString:@"Access to %@'s %@ ivar is prohibited. This is an application bug"]) {
        return;
    }

    va_list args;
    va_start(args, format);
    NSString *reason =  [[NSString alloc] initWithFormat:format arguments:args];
    [self sw_raise:raise format:reason];
    va_end(args);
}

@end