1. 隐私权限适配
1.1 相册权限
相册新增选择权限类型PHAuthorizationStatusLimited:当页面弹出请求权限Alert时,会有Select Photos...选项,用户选择该选项时,会弹出页面供用户选择允许App访问的图片/照片。
适配要点:权限提示框会在每次冷启动后打开相册时重新弹出,可以在 info.plist 中设置PHPhotoLibraryPreventAutomaticLimitedAccessAlert选项为YES,关闭提示,调用下述方法手动选择:
// Custom subviews should be added to the content view.
@property (nonatomic, readonly, strong) UIView *contentView;
iOS14 新增了“Limited Photo Library Access” 模式,在授权弹窗中增加了 Select Photo 选项。用户可以在 App 请求调用相册时选择部分照片让 App 读取。从 App 的视⻆来看,你的相册里就只有这几张照片,App 无法得知其它照片的存在。
iOS14 中当用户选择
“PHAuthorizationStatusLimited” 时,如果未进行适配,有可能会在每次触发相册功能时都进行弹窗询问用户是否需要修改照片权限。
对于这种情况可通过在 Info.plist 中设置
“PHPhotoLibraryPreventAutomaticLimitedAccessAlert”的值为 YES 来阻止该弹窗反复弹出,并且可通过下面这个 API 来主动控制何时弹出PHPickerViewController 进行照片选择
[[PHPhotoLibrary sharedPhotoLibrary]presentLimitedLibraryPickerFromViewController:self];
在 iOS14 中官方推荐使用 PHPicker 来替代原 API 进行图片选择。PHPicker 为独立进程,会在视图最顶层进行展示,应用内无法对其进行截图也无法直接访问到其内的数据。
UIImagePickerController -> PHPickerViewController, UIImagePickerViewController 功能受限,每次只能选择一张图片,将逐渐被废弃。
PHPicker 支持多选,支持搜索,支持按 image,video,livePhotos 等进行选择。
新API及迁移demo:
需要注意的是,在 limit Photo 模式下,AssetsLibrary 访问相册会失败;在 writeOnly 模式下,AssetLibrary 也会有显示问题。建议还在使用 AssetsLibrary 的同学尽快迁移到新 API。
授权相关:旧 API 废弃,增加 PHAccessLevel 参数。如果再使用以前的API来获取权限状态,
PHAuthorizationStatusLimited 状态下也会返回
PHAuthorizationStatusAuthorized
1.2 地理位置
新增了精确定位和模糊定位的概念,用户可以手动选择,模糊定位的误差约 500m 。可以根据实际功能判断是否可以接受用户选择模糊定位。
如果功能强依赖精确定位,可以在需要的时候调用[CALocationMnanger requestTemporaryFullAccuracyAuthorizationWithPurposeKey:]单独请求一次精确定位,用户可以选择拒绝授权。所需参数purposeKey需要在 info.plist 中设置NSLocationTemporaryUsageDescriptionDictionary字典,key 为purposeKey, value 为对应的话述。
在 iOS13 及以前,App 请求用户定位授权时为如下形态:一旦用户同意应用获取定位信息,当前应用就可以获取到用户的精确定位。
iOS14 新增用户大致位置选项可供用户选择,原因是大多数 App 实际上并不需要获取用户到用户最准确的定位信息。iOS14 授权弹窗新增的 Precise的开关默认会选中精确位置。用户通过这个开关可以进行更改,当把这个值设为 On 时,地图上会显示精确位置;切换为Off时,将显示用户的大致位置。
对于对用户位置敏感度不高的 App 来说,这个似乎无影响,但是对于强依赖精确位置的 App 适配工作就显得非常重要了。可以通过用户在 “隐私设置” 中设置来开启精确定位,但是可能用户宁可放弃使用这个应用也不愿意开启。这个时候,iOS14 在 CLLocationManager 新增两个方法可用于向用户申请临时开启一次精确位置权限。
使用方式也很简单,需要首先在 Info.plist 中配置“NSLocationTemporaryUsageDescriptionDictionary”字典中需要配置 key 和 value 表明使用位置的原因,以及具体的描述。
在本例中,key 即为获取用户权限时传的 "purposeKey",最终呈现给用户的就是左图,右图为当App主动关闭精确定位权限申请。
对于地理位置不敏感的App 来说,iOS14 也可以通过直接在 info.plist 中添加 NSLocationDefaultAccuracyReduced 为 true 默认请求大概位置。
这样设置之后,即使用户想要为该 App 开启精确定位权限,也无法开启。
也可以直接通过API来根据不同的需求设置不同的定位精确度。
需要注意的是,当 App 在 Background 模式下,如果并未获得精确位置授权,那么 Beacon 及其他位置敏感功能都将受到限制。
1.3 Local Network
iOS14 当 App 要使用 Bonjour 服务时或者访问本地局域网,使用 mDNS 服务等,都需要授权,开发者需要在 Info.plist 中详细描述使用的为哪种服务以及用途。下图为需要无需申请权限与需要授权的服务:
在 "隐私设置" 中也可以查看和修改具体有哪些 App 正在使用 LocalNetwork
如果应用中需要使用 LocalNetwork 需要在 Info.plist 中配置两个选项,详细描述为什么需要使用该权限,以及需要列出具体使用 LocalNetwork 的服务列表。
对于使用了下列包含 Bonjour 的 framework,都需要更新描述.
1.4 Wi-Fi Address
iOS8 - iOS13 ,用户在不同的网络间切换和接入时,mac 地址都不会改变,这也就使得网络运营商还是可以通过 mac 地址对用户进行匹配和用户信息收集,生成完整的用户信息。iOS14 提供 Wifi 加密服务,每次接入不同的 WiFi 使用的 mac 地址都不同。每过 24 小时,mac 地址还会更新一次。需要关注是否有使用用户网络 mac 地址的服务。
下图为 iOS13 及之前用户接入网络时 mac 地址并不会进行改变
下图为 iOS14 用户接入 Wi-Fi 时 mac 地址的变化情况
并且用户也可以自行选择是否开启 private Wi-Fi address
1.5 剪切板
在 iOS14 中,读取用户剪切板的数据会弹出提示。
弹出提示的原因是使用 UIPasteboard 访问用户数据,访问以下数据都会弹出 toast 提示。
兼容方案:如果应用访问剪切板仅仅用于判断是否为URL格式,则 iOS14 新增了两个 API 可以用于规避该提示。如果应用想直接访问剪切板的数据,暂时可能无法做到规避该提示。iOS14 新增两种
UIPasteboardDetectionPattern。
上面的两个 API 可用于规避提示,但只能用于判断剪切板中是否有 URL,并不是真正的访问剪贴板数据,也拿不到剪切板的真实数据。下面两个 API 可以获得具体的 URL 信息,但是会触发剪切板提示。并且实测当用户剪切板中包含多个 URL 时只会返回第一个。
使用示例
NSSetpatterns=[[NSSet alloc]initWithObjects:UIPasteboardDetectionPatternProbableWebURL,nil];[[UIPasteboard generalPasteboard]detectPatternsForPatterns:patterns completionHandler:^(NSSet
1.6 相机和麦克风
iOS14 中 App 使用相机和麦克风时会有图标提示以及绿点和黄点提示,并且会显示当前是哪个 App 在使用此功能。我们无法控制是否显示该提示。
会触发录音小黄点的代码示例:
AVAudioRecorder*recorder=[[AVAudioRecorder alloc]initWithURL:recorderPath settings:nil error:nil];[recorder record];
触发相机小绿点的代码示例:
AVCaptureDeviceInputvideoInput=[[AVCaptureDeviceInput alloc]initWithDevice:videoCaptureDevice error:nil];AVCaptureSessionsession=[[AVCaptureSession alloc]init];if([session canAddInput:videoInput]){[session addInput:videoInput];}[session startRunning];
1.7 IDFA
IDFA 全称为 Identity for Advertisers ,即广告标识符。用来标记用户,目前最广泛的用途是用于投放广告、个性化推荐等。
在 iOS13 及以前,系统会默认为用户开启允许追踪设置,我们可以简单的通过代码来获取到用户的 IDFA 标识符。
AVCaptureDeviceInputvideoInput=[[AVCaptureDeviceInput alloc]initWithDevice:videoCaptureDevice error:nil];AVCaptureSessionsession=[[AVCaptureSession alloc]init];if([session canAddInput:videoInput]){[session addInput:videoInput];}[session startRunning];
但是在 iOS14 中,这个判断用户是否允许被追踪的方法已经废弃。
iOS14 中,系统会默认为用户关闭广告追踪权限。
对于这种情况,我们需要去请求用户权限。首先需要在 Info.plist 中配置" NSUserTrackingUsageDescription " 及描述文案,接着使用 AppTrackingTransparency 框架中的 ATTrackingManager 中的 requestTrackingAuthorizationWithCompletionHandler 请求用户权限,在用户授权后再去访问 IDFA 才能够获取到正确信息。
1.8 上传 AppStore
更加严格的隐私审核,可以让用户在下载 App 之前就知道此 App 将会需要哪些权限。目前苹果商店要求所有应用在上架时都必须提供一份隐私政策。如果引入了第三方收集用户信息等SDK,都需要向苹果说明是这些信息的用途。
2. UITableViewCell 的 contentView 会置于自定义控件的上层。
在 iOS14 bate 中,UITableViewCell 中如果有直接添加在 cell 上的控件,也就是使用[self addSubview:]方式添加的控件,会显示在 contentView 的下层。
contentView 会阻挡事件交互,使所有事件都响应tableView:didSelectRowAtIndexPath:方法,如果 customView 存在交互事件将无法响应。如果 contentView 设置了背景色,还会影响界面显示。
此改动在官方文档中并未说明,存在正式版发布时作为 bug 修复的可能性。但是在此前关于 contentView 的声明注释中,官方已经明确建议开发者将 customView 放在 contentView 上,使 contentView 作为 UITableViewCell 默认的 fatherView。
// Custom subviews should be added to the content view.@property (nonatomic, readonly, strong) UIView *contentView;
如果正式版系统更新了此改动, App 中可能会出现页面交互和显示异常的情况。
解决方案就是将添加在cell本体上的 ``customView添加在contentView` 上
3. KVC 不允许访问 UIPageControl的pageImage
同 iOS 13 更新时的 textField 的 placeHolder 属性一样,iOS 14 更新后,禁止开发者用 KVC 的方式访问 UIPageControl 的 私有属性 pageImage。
4. UIDatePicker 更新 UI 样式
iOS 14 中,UIDatePicker UI样式更新了
UIDatePickerStyleInline API_AVAILABLE(ios(14.0)) API_UNAVAILABLE(tvos, watchos),
aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80MTA4NS05NDZlMDA4NGJmZWFkYTI5LnBuZw.jpeg
并且为默认样式。如果想使用原来的播轮样式,需要设置
_pickerView.preferredDatePickerStyle = UIDatePickerStyleWheels;
preferredDatePickerStyle 为 iOS 13.4 新增属性
5.在iOS14中原获取UDID方法失效,获取值为空. 需使用AppTrackingTransparency 新框架获取,请请求用户授权.
具体plist配置参数如下:
具体api适配方法:
+ (NSString*)jxt_IDFA {
__block NSString *IDFAString = nil;
dispatch_semaphore_t signal = dispatch_semaphore_create(0);
if (@available(iOS 14, *)) {
// iOS14使用 AppTrackingTransparency 新框架
[ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
if (status == ATTrackingManagerAuthorizationStatusAuthorized) {
IDFAString = [[ASIdentifierManager sharedManager] advertisingIdentifier].UUIDString;
}else{
IDFAString = @"";
}
dispatch_semaphore_signal(signal);
}];
} else {
// 使用原方式访问 IDFA
NSString * systemVersion = [[UIDevice currentDevice] systemVersion];
if ([systemVersion floatValue] >= 6.0f){
IDFAString = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
}else{
IDFAString = @"";
}
dispatch_semaphore_signal(signal);
}
dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
return IDFAString;
}
6.适配canopenurl的问题
部分升级到iOS 14的用户,在点击链接跳转的时候,[[UIApplication sharedApplication]canOpenURL:url]返回false 经定位,是因为iOS 14新增默认浏览器设置,用户设置其他浏览器(例如chrome)为默认浏览器后,[[UIApplication sharedApplication]canOpenURL:url]就会报以下错误
-canOpenURL: failed for URL: "https://www.163.com" - error: "This app is not allowed to query for scheme https"
解决方案
应急方法:让用户在设置-其他浏览器中,把【默认浏览器】改为Safari浏览器
info.plist添加以下字段
LSApplicationQueriesSchemes
https
7.NSTimer不走的问题
背景
这个版本上线后,突然发现埋点数据直线下降,调试后发现是定时器上传的方法没有走,但是定时器的方法本期并没有修改过。代码如下
- (BOOL)initTimer() {
self.uploadTimer = [NSTimer scheduledTimerWithTimeInterval:timerInterval target:self selector:@selector(handleUpload) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.uploadTimer forMode:NSRunLoopCommonModes];
}
排查
这个handleUpload方法,怎么都不会走,但是在之前的版本中就是好的,排查了之后发现,是外层调用的地方加了一层异步。即调用的地方变成了
dispatch_async(dispatch_get_global_queue(0, 0), ^{
initTimer()
});
然后就导致了定时器没有启动。
原因
iOS是通过runloop作为消息循环机制,主线程默认启动了runloop,可是自线程没有默认的runloop,因此,我们在子线程启动定时器是不生效的。
解决方法:在子线程启动一下runloop即可
- (BOOL)initTimer() {
self.uploadTimer = [NSTimer scheduledTimerWithTimeInterval:timerInterval target:self selector:@selector(handleUpload) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.uploadTimer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
}
思考
通过这个问题,有两点收获,
- timer在iOS开发中经常使用,在很多博客中都看到关于timer要注意的地方也很多,通常是内存管理,timer启动相关,但是在开发中,如果没有真正遇到问题,没有“疼”在自己身上,就自己注意的就不够,经过这次之后,相信以后再要在异步使用timer,肯定会留心很多。
- 这个地方initTimer其实是一个SDK初始化的类,其实SDK内部没有修改,但是外层使用SDK的App调用修改了,就导致了SDK不能正常工作。所以封装SDK时要注意,如果使用timer,要么做线程检查,要么就直接把代码安全保证好;因为不能保证第三方调用者使用时的情况,所以要保证自己代码的正确性。
8.iOS14更新内容
参考链接:
https://blog.csdn.net/taobaojishu/article/details/107398696
https://www.jianshu.com/p/1ff3bac8672e
https://cloud.tencent.com/developer/article/1751551
https://support.apple.com/zh-cn/HT211850
https://cloud.tencent.com/developer/article/1681605