【问题标题】:Swift: how to stop exposing the delegate methodsSwift:如何停止公开委托方法
【发布时间】:2019-09-03 10:22:15
【问题描述】:

解决方法:

我正在创建一个实用程序类,比如说BLEScanManager,它负责扫描附近的 BLE 设备。实用程序类的唯一工作是扫描 BLE 设备并列出它。

其他类可以创建此BLEScanManager 的对象并获取BLE 设备数组,例如[Bluetooth](这里Bluetooth 是自定义模态类)。

现在要扫描 BLE 设备,我在同一个类中创建了 BLEScanManager 的扩展,并覆盖了它的委托方法,如下所示:

import UIKit
import CoreBluetooth

protocol BLEScanDelegate {
   func reloadDeviceList()
}

internal class BLEScanManager: NSObject {

    private var centralManager: CBCentralManager?
    var devices : [Bluetooth] = []
    var delegate: BLEScanDelegate?

    override init() {
        super.init()
        centralManager = CBCentralManager(delegate: self, queue: .main)
    }

    // MARK:- Custom methods
    func isScanning() -> Bool {
        return centralManager?.isScanning ?? false
    }

    func stopScanning() {
        centralManager?.stopScan()
    }

    func startScanning() {
        devices.removeAll()
        let options: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey:
            NSNumber(value: false)]
        centralManager?.scanForPeripherals(withServices: nil, options: options)
    }
}

extension BLEScanManager : CBCentralManagerDelegate {
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
            case .unknown:      print("central.state is .unknown")
            case .resetting:    print("central.state is .resetting")
            case .unsupported:  print("central.state is .unsupported")
            case .unauthorized: print("central.state is .unauthorized")
            case .poweredOff:   print("central.state is .poweredOff")
            case .poweredOn:    print("central.state is .poweredOn")
                                self.startScanning()
            @unknown default:   fatalError("unknow state")
        }
    }

    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        print("Discovered \(peripheral)")
        var bluetooth = Bluetooth()
        bluetooth.name = peripheral.name
        bluetooth.identifier = peripheral.identifier.uuidString
        bluetooth.state = peripheral.state.rawValue
        bluetooth.advertisementData = advertisementData
        let power = advertisementData[CBAdvertisementDataTxPowerLevelKey] as? Double
        let value: Double = pow(10, ((power ?? 0 - Double(truncating: RSSI))/20))
        bluetooth.signalStrength = String(describing: value.round)

        // Do not add duplicate device
        let fitlerArray : [Bluetooth] = devices.filter { $0.identifier ==  bluetooth.identifier}
        if fitlerArray.count == 0 {
            devices.append(bluetooth)
        }
        self.delegate?.reloadDeviceList()
    }
}

这里的问题是这些方法也暴露在其他类中。

例如:

我在其他类BLEListViewController 中创建了一个BLEScanManager 对象,以显示UITableView 中的BLE 设备列表。

class BLEListViewController: UITableViewController {
     var scanManager: BLEScanManager!
}

我可以使用对象scanManager 访问BLEListViewController 类中的CBCentralManagerDelegate 委托方法。

如下所示,

self.scanManager.centralManager?(<#T##central: CBCentralManager##CBCentralManager#>, didConnect: <#T##CBPeripheral#>)

这应该将内部实用程序委托方法暴露给外界。

问题是如何停止暴露这些代表?

请注意,如果我使用 internal 关键字,它只会隐藏该特定方法。但它仍然允许访问所有其他 CBCentralManagerDelegate 方法。

【问题讨论】:

  • 不确定它是否有效,但您是否尝试过private extension BLEScanManager : CBCentralManagerDelegate
  • 我之前使用过SwiftyBluetooth Library,它允许我使用委托方法将核心蓝牙更改为基于闭包的方法。也许这可以帮助你
  • @Tj3n:是的,我试过了,它给了我一个错误:'private' 修饰符不能与声明协议一致性的扩展一起使用
  • @chirag90:谢谢,让我也检查一下。
  • 为了更清楚,我已经添加了实用程序类的所有代码

标签: ios swift delegates swift-extensions


【解决方案1】:

您对此无能为力。这是所有可选协议方法的问题。问题是可选的协议方法是一个 Objective C 的特性。您不能将这样的方法隐藏为私有,因为现在 Objective C 看不到它,因此永远不会发现和调用该方法。这是 Swift 与 Objective C 接口方式的一个缺陷。你只需要忍受它。

【讨论】:

  • 我想知道我们是否可以这样做。但我想那是不可能的。感谢您的详细解释,希望我们将来能找到方法。
【解决方案2】:

您需要做的是隐藏在协议后面 - 请参阅以下示例。

protocol Exposed {
 func run()
}

struct Bluetooth {
 func hidden() {
  print("This method is hidden - ish")
 }
}

extension Bluetooth: Exposed {
 func run() {
  print("this method is exposed")
 }
}

struct BluetoothScanManager {
 static func scan() -> [Exposed] {
  return [Bluetooth(), Bluetooth()]
 }
}

现在,当您运行BluetoothScanManager 时,您将获得仅使用run() 方法可用的公开列表,但在代码后面是Bluetooth

for exposed in BluetoothScanManager.scan() {
 exposed.run()
 exposed.hidden() // not possible
} 

【讨论】:

  • 非常感谢您的帮助,但问题是代表是CBCentralManagerDelegate,不受我们控制。如果我们使用自定义委托方法,您的答案将起作用。但是我们如何将上述策略应用于CBCentralManagerDelegate 方法呢?
  • @Kampai 可能会使用一个内部类来确认 CBCentralManagerDelegate。
  • @Kampai 当类是内部的时,也将实例设为私有。当它的访问是私有的时,它不应该对外界可见/不可访问
  • 也许我对您的问题有错误的认识。但是你仍然可以隐藏在一个接口后面,只是不允许创建 BLEScanManager,而是让它有一个类似的方法: `` struct BLEScanManager { static func createManager() -> Exposed { return BLEScanManager() } private init() {} } 扩展 BLEScanManager:暴露 { ... }
  • @magnuskahr:您能否编辑您的答案并在答案中提供这些 cmets 详细信息,以便我理解。
【解决方案3】:

这样做的唯一方法(我认为这也是正确的方法)是在实现CBCentralManagerDelegateBLEScanManager 类中创建一个新的私有对象。

您可以将此对象传递给CBCentralManager init 方法。

如果您随后需要从该对象与BLEScanManager 通信,您可以简单地使用另一个具有私有协议的委托,这样它就不会在类之外显示这些方法。

看起来像这样:


import UIKit
import CoreBluetooth

protocol BLEScanDelegate {
   func reloadDeviceList()
}



internal class BLEScanManager: NSObject {

    private var centralManager: CBCentralManager?
    var devices : [Bluetooth] = []
    var delegate: BLEScanDelegate?
    let centralManagerDelegate = MyCentralManagerDelegateImplementation()
    override init() {
        super.init()
        centralManagerDelegate.delegate = self
        centralManager = CBCentralManager(delegate: centralManagerDelegate, queue: .main)
    }

    // MARK:- Custom methods
    func isScanning() -> Bool {
        return centralManager?.isScanning ?? false
    }

    func stopScanning() {
        centralManager?.stopScan()
    }

    func startScanning() {
        devices.removeAll()
        let options: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey:
            NSNumber(value: false)]
        centralManager?.scanForPeripherals(withServices: nil, options: options)
    }
}

private extension BLEScanManager: CentralManagerEventsDelegate {
    func didUpdateState(){
        // Here goes the switch with the startScanning
    }


    func didDiscoverPeripheral(...) {
        // Here goes the logic on new peripheral and the call on self.delegate?.reloadDeviceList
    }

}

internal protocol CentralManagerEventsDelegate: class {
    func didUpdateState()
    func didDiscoverPeripheral(...)
}

internal class MyCentralManagerDelegateImplementation: NSObject, CBCentralManagerDelegate {

    weak var delegate: CentralManagerEventsDelegate?

    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        self.delegate?.didUpdateState()
    }

    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        self.delegate?.didDiscoverPeripheral(actualParams)
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多