iOS 13 兼容性适配检查

可能出现问题的关键字

复制下面的关键字在 Xcode 中正则搜索

(_UINavigationBarContentView|forKeyPath|_placeholderLabel|boolForKey|stringForKey|deviceToken|CNCopyCurrentNetworkInfo|presentViewController|UISearchDisplayController|UIWebView|MPMoviePlayerController|@available|_cancelButtonText|_searchField)

_cancelButtonText:

crash: [searchBar setValue:@”取消” forKey:@”_cancelButtonText”];

_UINavigationBarContentView

for (UIView *subview in self.subviews) {
if ([NSStringFromClass([subview class]) containsString:@”_UINavigationBarContentView”]) {
subview.layoutMargins = UIEdgeInsetsZero;
break;
}
}

这种做法在 iOS 13 中会导致崩溃,崩溃信息如下 *** Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘Client error attempting to change layout margins of a private view’

解决方案 使用设置 frame 的方式,让 _UINavigationBarContentView 向两边伸展,从而抵消两边的边距。

_placeholderLabel

[_textField setValue:[UIColor redColor] forKeyPath:@”_placeholderLabel.textColor”];///崩溃 [_textField setValue:[UIFont systemFontOfSize:14] forKeyPath:@”_placeholderLabel.font”];///崩溃解决方案 _textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@”姓名” attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:14],NSForegroundColorAttributeName:[UIColor redColor]}];在Xcode10上编译不会有问题,但在Xcode11上编译的会崩溃。并且- (void)setValue:(nullable id)value forKey:(NSString *)key方法没问题,- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath会崩溃

boolForKey

第三方 NSDictionary 扩展重名 打印日志:objc[5337]: REPLACED: -[NSDictionary boolForKey:] by category HMFoundation (IMP was 0x104ae8280 (/var/containers/Bundle/Application/B1486820-C2CC-4339-9D41-DBF1AC77EBF2/iHome4iPhone.app/iHome4iPhone), now 0x186d95a8c (/System/Library/PrivateFrameworks/HMFoundation.framework/HMFoundation))解决方案 Category 换名字

stringForKey

stringForKey 返回的是默认值是@“”并非nil. if([dict stringForKey:@”key”]){} 会发生逻辑错误解决方案 [dict stringForKey:@”key”].length > 0

Category

见 <Category 私有函数>

deviceToken

– (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSString *token = [deviceToken description];
for (NSString *symbol in @[@” “, @”<“, @”>”, @”-“]) {
token = [token stringByReplacingOccurrencesOfString:symbol withString:@””];
}
}

在 iOS 13 中,这种方法已经失效,NSData类型的 deviceToken 转换成的字符串变成了: {length = 32, bytes = 0xd7f9fe34 69be14d1 fa51be22 329ac80d … 5ad13017 b8ad0736 }

解决方案

#include <arpa/inet.h> – (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { if (![deviceToken isKindOfClass:[NSData class]]) return; const unsigned *tokenBytes = [deviceToken bytes]; NSString *hexToken = [NSString stringWithFormat:@”%08x%08x%08x%08x%08x%08x%08x%08x”, ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]), ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]), ntohl(tokenBytes[6]), ntohl(tokenBytes[7])]; NSLog(@”deviceToken:%@”, hexToken); }

presentViewController

苹果将 UIViewController 的 modalPresentationStyle 属性的默认值改成了新加的一个枚举值 UIModalPresentationAutomatic,对于多数 UIViewController,此值会映射成 UIModalPresentationPageSheet。 这种效果弹出来的页面导航栏部分是会被砍掉的,在 storyboard 中也可以看到,页面布局时需要注意导航栏的内容不要被遮挡。注意,我们原来以全屏的样式弹出一个页面,那么将这个页面弹出的那个 ViewController 会依次调用 viewWillDisappear 和 viewDidDisappear。然后在这个页面被 dismiss 的时候,将他弹出的那个 ViewController 的 viewWillAppear 和 viewDidAppear 会被依次调用。然而使用默认的视差效果弹出页面,将他弹出的那个 ViewController 并不会调用这些方法,原先写在这四个函数中的代码以后都有可能会存在问题。解决方案:如果视差效果的样式可以接受的话,就不需要修改;如果需要改回全屏显示的界面,需要手动设置弹出样式:

– (UIModalPresentationStyle)modalPresentationStyle {
return UIModalPresentationFullScreen;
}

self.modalPresentationStyle = .fullScreen

CNCopyCurrentNetworkInfo

CNCopyCurrentNetworkInfo 文档说明,应用还需要符合下列三项条件中的至少一项才能得到正确的值:使用 Core Location 的应用, 并获得定位服务权限。 使用 NEHotspotConfiguration 来配置 WiFi 网络的应用。 目前正处于启用状态的 VPN 应用。解决方案: 获取 wift信息还要先获得位置授权

UISearchDisplayController

在 iOS 8 之前,我们在 UITableView 上添加搜索框需要使用 UISearchBar + UISearchDisplayController 的组合方式,而在 iOS 8 之后,苹果就已经推出了 UISearchController 来代替这个组合方式。在 iOS 13 中,如果还继续使用 UISearchDisplayController 会直接导致崩溃,崩溃信息如下: *** Terminating app due to uncaught exception ‘NSGenericException’, reason: ‘UISearchDisplayController is no longer supported when linking against this version of iOS. Please migrate your application to UISearchController.’

解决方案: 使用 UISearchController 替换 UISearchBar + UISearchDisplayController 的组合方案。

UIWebView

UIWebView 将被禁止提交审核 如果开发者将包含 UIWebView api 的应用更新上传到 App Store 审核后,其将会收到包含 ITMS-90809 信息的回复邮件解决方案 用 WKWebView 替代 UIWebView,确保所有 UIWebView 的 api 都要移除,如果需要适配 iOS 7 的可以通过 openURL 的方式在 Safari 打开。

MPMoviePlayerController

在 iOS 9 之前播放视频可以使用 MediaPlayer.framework 中的MPMoviePlayerController类来完成,它支持本地视频和网络视频播放。但是在 iOS 9 开始被弃用,如果在 iOS 13 中继续使用的话会直接抛出异常:*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘MPMoviePlayerController is no longer available. Use AVPlayerViewController in AVKit.’

解决方案

使用 AVFoundation 里的 AVPlayer 作为视频播放控件。

@available

使用 @available 导致旧版本 Xcode 编译出错 在 Xcode 11 的 SDK 工程的代码里面使用了 @available 判断当前系统版本,打出来的包放在 Xcode 10 中编译,会出现一下错误:Undefine symbols for architecture i386: “__isPlatformVersionAtLeast”, referenced from: … ld: symbol(s) not found for architecture i386复制代码从错误信息来看,是 __isPlatformVersionAtLeast 方法没有具体的实现,但是工程里根本没有这个方法。实际测试无论在哪里使用@available ,并使用 Xcode 11 打包成动态库或静态库,把打包的库添加到 Xcode 10 中编译都会出现这个错误,因此可以判断是 iOS 13 的 @available 的实现中使用了新的 api。 解决方案 如果你的 SDK 需要适配旧版本的 Xcode,那么需要避开此方法,通过获取系统版本来进行判断: if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) { … }另外,在 Xcode 10 上打开 SDK 工程也应该可以正常编译,这就需要加上编译宏进行处理:

#ifndef __IPHONE_13_0 #define __IPHONE_13_0 130000 #endif

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0 … #endif

_searchField

UITextField *textField = [searchBar valueForKey:@”_searchField”]; // Crash

解决方案

// 替代方案 1,使用 iOS 13 的新属性 searchTextField searchBar.searchTextField.placeholder = @”search”;

_cancelButtonText

[searchBar setValue:@”取消” forKey:@”_cancelButtonText”]; // Crash

解决方案 // 替代方案,用同上的方法找到子类中 UIButton 类型的属性,然后设置其标题 UIButton *cancelButton = [self findViewWithClassName:NSStringFromClass([UIButton class]) inView:searchBar]; [cancelButton setTitle:@”取消” forState:UIControlStateNormal];

Category 私有函数

Category函数

系统Category仓库

NSObject

safeValueForKey

AccessibilityUtilities

NSDictionary

boolForKey

HMFoundation

NSDictionary

stringForKey

HMFoundation

NSDictionary

arrayForKey

HMFoundation

NSDictionary

dictionaryForKey

HMFoundation

NSDictionary

dataForKey

HMFoundation

NSDictionary

numberForKey

HMFoundation

NSDictionary

mutableDictionaryForKey

HMFoundation

NSDictionary

mutableArrayForKey

HMFoundation

NSString

unsignedLongLongValue

HearingUtilities

NSString

unsignedLongLongValue

HearingUtilities

NSString

stdStringForString

libwebrtc

NSString

stringForStdString

libwebrtc

NSString

unsignedIntValue

IMFoundation

NSString

stdString

GeoServices

NSString

containsString

Foundation

NSString

unsignedIntValue

IMFoundation

NSString

hexValue

IMFoundation

NSString

hasPrefixCaseInsensitive

CalendarFoundation

NSString

urlEncodedString

Social

NSMutableArray

nonRetainingArray

IMFoundation

关键字搜索

(safeValueForKey|boolForKey|stringForKey|arrayForKey|dictionaryForKey|dataForKey|numberForKey|mutableDictionaryForKey|mutableArrayForKey|unsignedLongLongValue|stdStringForString|stringForStdString|unsignedIntValue|stdString|hexValue|hasPrefixCaseInsensitive|urlEncodedString|nonRetainingArray)

Last updated