一、简介
在iOS系统中,定位权限获取是一个涉及进程间同步通信的方法,如果频繁访问可能会导致卡顿或者卡死。在一些打车或者地图类的APP中,定位权限的卡顿报错可能是大头,亟需解决!
下面是系统类提供的访问定位权限的方法:
// CLLocationManager是系统的定位服务管理类
open class CLLocationManager : NSObject {
// 1.下面方法是访问系统设置中定位是否打开
@available(iOS 4.0, *)
open class func locationServicesEnabled() -> Bool
// 2.1 iOS 14.0之后,访问定位的授权状态
@available(iOS 14.0, *)
open var authorizationStatus: CLAuthorizationStatus { get }
// 2.2 iOS 14.0之后,访问定位的授权状态
@available(iOS, introduced: 4.2, deprecated: 14.0)
open class func authorizationStatus() -> CLAuthorizationStatus
}
二、从卡顿堆栈例子中分析问题
为了解决这个卡顿,首先要分析卡顿报错堆栈。接下来举一个定位权限频繁获取导致的卡顿的堆栈:
0 libsystem_kernel.dylib _mach_msg2_trap + 8
1 libsystem_kernel.dylib _mach_msg2_internal + 80
2 libsystem_kernel.dylib _mach_msg_overwrite + 388
3 libsystem_kernel.dylib _mach_msg + 24
4 libdispatch.dylib __dispatch_mach_send_and_wait_for_reply + 540
5 libdispatch.dylib _dispatch_mach_send_with_result_and_wait_for_reply + 60
6 libxpc.dylib _xpc_connection_send_message_with_reply_sync + 240
7 Foundation ___NSXPCCONNECTION_IS_WAITING_FOR_A_SYNCHRONOUS_REPLY__ + 16
8 Foundation -[NSXPCConnection _sendInvocation:orArguments:count:methodSignature:selector:withProxy:] + 2236
9 Foundation -[NSXPCConnection _sendSelector:withProxy:arg1:arg2:arg3:] + 136
10 Foundation __NSXPCDistantObjectSimpleMessageSend3 + 76
11 CoreLocation _CLCopyTechnologiesInUse + 30852
12 CoreLocation _CLCopyTechnologiesInUse + 25724
13 CoreLocation _CLClientStopVehicleHeadingUpdates + 104440
14 MyAPPName +[MKLocationRecorder locationAuthorised] + 40
15 ... // 以下略
- 首先从第14行找到是
MKLocationRecorder
类的locationAuthorised
方法调用后,执行到了系统库函数,最终导致了卡死、卡顿。 - 对堆栈中第0-13行中的方法做一番了解,初步发现
xpc_connection_send_message_with_reply_sync
函数涉及进程间同步通信,可能会阻塞当前线程点击查看官方方法说明。
该函数说明:Sends a message over the connection and blocks the caller until it receives a reply.
- 接下来添加符号断点
xpc_connection_send_message_with_reply_sync
, 注意如果是系统库中的带下划线的函数,我们添加符号断点
的时候一般需要少一个下划线_.
执行后,从Xcode的方法调用栈视图中查看,可以发现MKLocationRecorder
类的locationAuthorised
方法内部中调用CLLocationManager
类的locationServicesEnabled
和authorizationStatus
方法都会来到这个符号断点
.所以确定了是这两个方法导致的卡顿。(调试时并未发现卡顿,只是线上用户的使用环境更加复杂,卡顿时间长一点就被监控到了,我们目前卡顿监控是3秒,卡死监控是10s+)。 - 然后通过
CLLocationManager
类的authorizationStatus
方法说明,发现也是说在权限发生改变后,系统会保证调用代理方法locationManagerDidChangeAuthorization(_:)
,所以就产生了我们的解决方案,最终上线后也是直接解决了这个卡顿,并且APP启动耗时监控数据也因此上升了一些。
三、具体的解决方案
实现思路.png注意点:设置代理必须在有runloop的线程,如果业务量不多的话,就在主线程设置就可以。
四、Demo类,可以直接用
import CoreLocation
public class XLLocationAuthMonitor: NSObject, CLLocationManagerDelegate {
// 单例类
@objc public static let shared = XLLocationAuthMonitor()
/// 定位服务是否可用, 这里设置成变量避免过于频繁调用系统方法时产生卡顿,系统方法涉及进程间通信
@objc public private(set) var serviceEnabled: Bool {
set {
threadSafe { _serviceEnabled = newValue }
}
get {
threadSafe { _serviceEnabled ?? CLLocationManager.locationServicesEnabled() }
}
}
/// 定位服务授权状态
@objc public private(set) var authStatus: CLAuthorizationStatus {
set {
threadSafe { _authStatus = newValue }
}
get {
threadSafe {
if let auth = _authStatus {
return auth
}
if #available(iOS 14.0, *) {
return locationManager.authorizationStatus
} else {
return CLLocationManager.authorizationStatus()
}
}
}
}
/// 计算属性,这里返回当前定位是否可用
@objc public var isLocationEnable: Bool {
guard serviceEnabled else {
return false
}
switch authStatus {
case .authorizedAlways, .authorizedWhenInUse:
return true
case .denied, .notDetermined, .restricted:
return false
default: return false
}
}
// MARK: - 内部使用的私有属性
private lazy var locationManager: CLLocationManager = CLLocationManager()
private let _lock = NSLock()
private var _serviceEnabled: Bool?
private var _authStatus: CLAuthorizationStatus?
private override init() {
super.init()
// 如果是主线程则直接设置,不是则在mainQueue中设置
DispatchQueue.main.safeAsync {
self.locationManager.delegate = self
}
}
private func threadSafe<T>(task: () -> T) -> T {
_lock.lock()
defer { _lock.unlock() }
return task()
}
// MARK: - CLLocationManagerDelegate
/// iOS 14以上调用
public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
if #available(iOS 14.0, *) {
authStatus = locationManager.authorizationStatus
serviceEnabled = CLLocationManager.locationServicesEnabled()
}
}
/// iOS 14以下调用
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
authStatus = status
serviceEnabled = CLLocationManager.locationServicesEnabled()
}
}
参考文章:
出行iOS用户端卡顿治理实践
网友评论