Swift 苹果权限授权统一管理

1,105 阅读4分钟

权限类目

  • 照片
  • 相机
  • 麦克风
  • 定位服务
  • 通讯录
  • 运动与健身
  • Siri服务
  • 消息推送
  • 健康

苹果一直以来对用户的隐私极其注重,因此权限这块也是非常严谨,几乎每一项权限都需要在项目里的plist文件新增对应的权限key,否则审核会直接打回(二进制文件无效等)。

这里采用对所有权限统一管理等方式,按项目需要在适当的位置向用户申请授权权限。首先需要定义一个具体权限的接口协议类BKPermissionInterface,协议内容为:

// MARK: - 权限协议
protocol BKPermissionInterface {
    /// 是否允许
    var isAuthorized: Bool { get }
    /// 是否拒绝
    var isDenied: Bool { get }
    /// 是否每次询问
    var isNotDetermined: Bool { get }
    /// 请求权限
    func request(completion callback: ((BKAuthorizationStatus) -> Void)?)
}

其中协议里的request方法里的回调枚举为:

enum BKAuthorizationStatus {
    // 未知状态
    case unknown
    // 用户未选择
    case notDetermined
    // 用户没有权限
    case restricted
    // 拒绝
    case denied
    // 允许
    case authorized
    // 临时允许
    case provisional
    // 设备不支持
    case notSupport
    
    /// 是否可以访问
    var isAuthorized: Bool {
        return self == .authorized || self == .provisional
    }
    /// 是否不支持
    var isNotSupport: Bool {
        return self == .notSupport
    }
}

定义完协议及枚举,接下来就是将具体的权限一一遵循该协议即可。

定位服务

定义定位服务权限结构体,并遵循权限协议,实现协议属性及方法,fullAccuracy属性是为适配iOS14推出的精确定位和模糊定位,可以拿到用户选择的定位精度。

/// 全局变量定位授权才能正常弹窗
fileprivate var _locationManager = CLLocationManager()

// MARK: - 定位
/// 定位权限
struct BKLocationPermission: BKPermissionInterface {

    var isAuthorized: Bool {
        if #available(iOS 14.0, *) {
            return _locationManager.authorizationStatus == .authorizedWhenInUse || _locationManager.authorizationStatus == .authorizedAlways
        } else {
            return CLLocationManager.authorizationStatus() == .authorizedWhenInUse || CLLocationManager.authorizationStatus() == .authorizedAlways
        }
    }
    
    var isDenied: Bool {
        if #available(iOS 14.0, *) {
            return _locationManager.authorizationStatus == .denied
        } else {
            return CLLocationManager.authorizationStatus() == .denied
        }
    }
    
    var isNotDetermined: Bool {
        if #available(iOS 14.0, *) {
            return _locationManager.authorizationStatus == .notDetermined
        } else {
            return CLLocationManager.authorizationStatus() == .notDetermined
        }
    }
    
    @available(iOS 14.0, *)
    var fullAccuracy: Bool {
        return _locationManager.accuracyAuthorization == .fullAccuracy
    }
    
    func request(completion callback: ((BKAuthorizationStatus) -> Void)?) {
        DispatchQueue.global().async {
            if CLLocationManager.locationServicesEnabled() {
                var authStatue: BKAuthorizationStatus = .unknown
                if #available(iOS 14.0, *) {
                    switch _locationManager.authorizationStatus {
                    case .authorizedWhenInUse, .authorizedAlways: authStatue = .authorized
                    case .notDetermined: authStatue = .notDetermined
                    case .restricted: authStatue = .restricted
                    case .denied: authStatue = .denied
                    default: authStatue = .unknown
                    }
                } else {
                    switch CLLocationManager.authorizationStatus() {
                    case .authorizedWhenInUse, .authorizedAlways: authStatue = .authorized
                    case .notDetermined: authStatue = .notDetermined
                    case .restricted: authStatue = .restricted
                    case .denied: authStatue = .denied
                    default: authStatue = .unknown
                    }
                }
                if authStatue == .notDetermined {
                    _locationManager.requestWhenInUseAuthorization()
                }
                callback?(authStatue)
            } else {
                callback?(.notSupport)
            }
        }
    }
    
}

健康

定义健康权限结构体,并遵循权限协议,实现协议属性及方法

// MARK: - 健康
/// 健康权限
struct BKHealthKitPermission: BKPermissionInterface {

    private let healthStore = HKHealthStore()
    
    private func getHealthKitAuthStatus() -> HKAuthorizationStatus {
        return healthStore.authorizationStatus(for: .workoutType)
    }
    
    var isAuthorized: Bool {
        return self.getHealthKitAuthStatus() == .sharingAuthorized
    }
    
    var isDenied: Bool {
        return self.getHealthKitAuthStatus() == .sharingDenied
    }
    
    var isNotDetermined: Bool {
        return self.getHealthKitAuthStatus() == .notDetermined
    }
    
    func request(completion callback: ((BKAuthorizationStatus) -> Void)?) {
        if HKHealthStore.isHealthDataAvailable() {
            var authStatue: BKAuthorizationStatus = .unknown
            switch self.getHealthKitAuthStatus() {
            case .sharingAuthorized: authStatue = .authorized
            case .notDetermined: authStatue = .notDetermined
            case .sharingDenied: authStatue = .denied
            default: authStatue = .unknown
            }
            if authStatue == .notDetermined {
                healthStore.requestAuthorization(toShare: HKT.dataTypesToWrite(), read: HKT.dataTypesToRead()) { authorized, error in
                    callback?(authorized ? .authorized : .denied)
                }
            } else {
                callback?(authStatue)
            }
        } else {
            callback?(.notSupport)
        }
    }
    
}

调用权限申请

有了先前的权限协议接口,现在就可以定义权限管理类BKPermission,内部则使用枚举来一一拿到对应的权限结构体即可。

class BKPermission: NSObject {
    
    enum BKPermissionType: String {
        case photoLibrary = "照片"
        case camera = "相机"
        case microphone = "麦克风"
        case location = "定位服务"
        case contacts = "通讯录权限"
        case cmMotion = "运动与健身"
        case siri = "Siri服务"
        case userNotifi = "消息推送"
        case health = "健康"
        
        var msg: String {
            switch self {
            case .photoLibrary: return "请前往「设置—隐私—照片」中打开开关。"
            case .camera: return "请前往「设置—隐私—相机」中打开开关。"
            case .microphone: return "请前往「设置—隐私—麦克风」中打开开关。"
            case .location: return "想要更准确地记录你的运动记录。点击“设置”,开启定位服务。"
            case .contacts: return "请前往「设置—隐私—通讯录」中打开开关。"
            case .cmMotion: return "想要获取步数等数据。点击“设置”,开启运动与健身。"
            case .siri: return "想要通过Siri控制跑步。点击“设置”,开启Siri。"
            case .userNotifi: return "想要及时获取消息。点击“设置”,开启通知。"
            case .health: return "想要同步健康中体能训练等数据。请前往「设置—隐私—健康」中打开开关。"
            }
        }
    }
    
    /// 是否允许权限
    static func isAllowed(_ type: BKPermissionType) -> Bool {
        let manager = self.getManagerForPermission(type)
        return manager.isAuthorized
    }
    
    /// 是否拒绝权限
    static func isDenied(_ type: BKPermissionType) -> Bool {
        let manager = self.getManagerForPermission(type)
        return manager.isDenied
    }
    
    /// 是否是【下次询问或在我共享时】【允许一次】【每次询问】
    static func isNotDetermined(_ type: BKPermissionType) -> Bool {
        let manager = self.getManagerForPermission(type)
        return manager.isNotDetermined
    }
    
    /// 定位权限的精度是否是精确位置
    @available(iOS 14.0, *)
    static func isFullAccuracy() -> Bool {
        let manager = self.getManagerForPermission(.location) as! BKLocationPermission
        return manager.fullAccuracy
    }
    
    /// 请求权限
    static func request(_ type: BKPermissionType, completion callback: ((BKAuthorizationStatus) -> Void)? = nil) {
        let manager = self.getManagerForPermission(type)
        manager.request { status in
            DispatchQueue.main.async {
                if status.isNotSupport {
                    BPM.showAlert(.warning, msg: "当前设备不支持\(type.rawValue)!")
                }
                callback?(status)
            }
        }
    }
    
    static func requests() {
        self.request(.location)
        self.request(.cmMotion)
        self.request(.siri)
    }
    
}

// MARK: - Private
extension BKPermission {
    
    private static func getManagerForPermission(_ type: BKPermissionType) -> BKPermissionInterface {
        switch type {
        case .photoLibrary: return BKPhotoLibraryPermission()
        case .camera: return BKCameraPermission()
        case .microphone: return BKMicrophonePermission()
        case .location: return BKLocationPermission()
        case .contacts: return BKContactsPermission()
        case .cmMotion: return BKCMMotionPermission()
        case .siri: return BKSiriPermission()
        case .userNotifi: return BKUserNotifiPermission()
        case .health: return BKHealthKitPermission()
        }
    }
    
}

这样后续管理新增的权限,只需要新增对应的权限结构体并遵循权限协议即可,实现规范化统一管理。

联系方式:kim77895pl@gmail.com

作者Kim,Hope it helps for you。2022.9.27