美文网首页
iOS开发解决定位权限卡顿

iOS开发解决定位权限卡顿

作者: Sweet丶 | 来源:发表于2023-02-17 13:07 被阅读0次
    一、简介

    在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 ... // 以下略
    
    1. 首先从第14行找到是MKLocationRecorder类的locationAuthorised方法调用后,执行到了系统库函数,最终导致了卡死、卡顿。
    2. 对堆栈中第0-13行中的方法做一番了解,初步发现xpc_connection_send_message_with_reply_sync函数涉及进程间同步通信,可能会阻塞当前线程点击查看官方方法说明

    该函数说明:Sends a message over the connection and blocks the caller until it receives a reply.

    1. 接下来添加符号断点xpc_connection_send_message_with_reply_sync, 注意如果是系统库中的带下划线的函数,我们添加符号断点的时候一般需要少一个下划线_.
      执行后,从Xcode的方法调用栈视图中查看,可以发现MKLocationRecorder类的locationAuthorised方法内部中调用CLLocationManager类的locationServicesEnabledauthorizationStatus方法都会来到这个符号断点.所以确定了是这两个方法导致的卡顿。(调试时并未发现卡顿,只是线上用户的使用环境更加复杂,卡顿时间长一点就被监控到了,我们目前卡顿监控是3秒,卡死监控是10s+)。
    2. 然后通过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用户端卡顿治理实践

    相关文章

      网友评论

          本文标题:iOS开发解决定位权限卡顿

          本文链接:https://www.haomeiwen.com/subject/njbmkdtx.html