簡體   English   中英

具有內部函數和屬性的Swift公共協議

[英]Swift Public protocols with Internal functions and properties

我想知道什么是最好的做法,當我想要一些功能公開,一些內部使用協議時我。

我正在Swift 3中編寫一個AudioManager ,將AVPlayer包裝為框架。

我想要一些方法是公共的,所以例如使用AudioManager的ViewController可以訪問某些方法,但是某些方法不會暴露在框架之外
- >即將訪問修飾符設為internal而非public

我正在用協議驅動設計編寫框架,幾乎每個部分都應該有一個協議。
因此協議正在與框架內的協議進行通信。
例如,主類 - AudioManager - 有一個AudioPlayer ,應該能夠調用它上面的一些internal函數,
例如, pause(reason:)但該方法應該是internal ,不會暴露在框架之外。

這是一個例子。

internal enum PauseReason {
    case byUser
    case routeChange
}

// Compilation error: `Public protocol cannot refine an internal protocol`
public protocol AudioPlayerProtocol: InternalAudioPlayerProtocol { 
   func pause() // I want 
}

internal protocol InternalAudioPlayerProtocol {
    func pause(reason: PauseReason) // Should only be accessible within the framework
}

public class AudioPlayer: AudioPlayerProtocol {
    public func pause() {
        pause(reason: .byUser)
    }

    // This would probably not compile because it is inside a public class...
    internal func pause(reason: PauseReason) { //I want this to be internal
        // save reason and to stuff with it later on
    }
}

public protocol AudioManagerProtocol {
    var audioPlayer: AudioPlayerProtocol { get }
}

public class AudioManager: AudioManagerProtocol {
    public let audioPlayer: AudioPlayerProtocol

    init() {
        audioPlayer = AudioPlayer()
        NotificationCenter.default.addObserver(self, selector: #selector(handleRouteChange(_:)), name: NSNotification.Name.AVAudioSessionRouteChange, object: nil)
    }

    func handleRouteChange(_ notification: Notification) {
        guard
        let userInfo = notification.userInfo,
        let reasonRaw = userInfo[AVAudioSessionRouteChangeReasonKey] as? NSNumber,
        let reason = AVAudioSessionRouteChangeReason(rawValue: reasonRaw.uintValue)
        else { print("what could not get route change") }
        switch reason {
        case .oldDeviceUnavailable:
            pauseBecauseOfRouteChange()
        default:
            break
        }
    }
}

private extension AudioManager {
    func pauseBecauseOfRouteChange() {
        audioPlayer.pause(reason: .routeChange)
    }
}

// Outside of Audio framework
class PlayerViewController: UIViewController {
    fileprivate let audioManager: AudioManagerProtocol 
    @IBAction didPressPauseButton(_ sender: UIButton) {
        // I want the `user of the Audio framwwork` (in this case a ViewController)
        // to only be able to `see` `pause()` and not `pause(reason:)` 
        audioManager.audioPlayer.pause()
    }
}

我知道我可以通過將pauseBecauseOfRouteChange方法pauseBecauseOfRouteChange為如下所示來實現它:

func pauseBecauseOfRouteChange() {
    guard let internalPlayer = audioPlayer as? InternalAudioPlayerProtocol else { return }
    internalPlayer.pause(reason: .routeChange)
}

但我想知道是否有更優雅的解決方案?
像標記AudioPlayerProtocol精煉InternalAudioPlayerProtocol ...

或者你們的程序員如何做到這一點?
如果它不公開供內部使用的方法和變量,那么框架會更漂亮!

謝謝!

不,沒有更優雅的解決方案,至少在考慮協議時,這就是原因:

想象一下,有人使用你的框架想要為AudioPlayerProtocol編寫擴展,如果它是內部的,那么如何實現pause(reason:)方法呢?

你可以通過子類化實現它,這個代碼實際上將編譯:

public class AudioPlayer: AudioPlayerProtocol {
    public func pause() {
        pause(reason: .byUser)
    }

    internal func pause(reason: PauseReason) {
    }
}

對於協議,情況並非如此,因為如果具有公共訪問級別的人想要使用混合公共/內部協議,則無法保證實現內部功能。

如果將協議拆分為內部和公共,然后讓公共實現類委托給內部實現,那該怎么辦? 像這樣

internal protocol InternalAudioPlayerProtocol {
    func pause(reason: PauseReason) 
}

public protocol AudioPlayerProtocol {
    func pause()
}

internal class InternalAudioPlayer: InternalAudioPlayerProtocol {
    internal func pause(reason: PauseReason) { 
    }
}

public class AudioPlayer: AudioPlayerProtocol  {
    internal var base: InternalAudioPlayerProtocol

    internal init(base: InternalAudioPlayerProtocol) {
        self.base = base
    }

    public func pause() {
        base.pause(reason: .byUser)
    }
}

public protocol AudioManagerProtocol {
    var audioPlayer: AudioPlayerProtocol { get }
}

public class AudioManager: AudioManagerProtocol {
    internal let base = InternalAudioPlayer()
    public let audioPlayer: AudioPlayerProtocol

    public init() {
        audioPlayer = AudioPlayer(base: base)
    }

    internal func handleSomeNotification() {            
        pauseBecauseOfRouteChange() //amongst other things
    }

    internal func pauseBecauseOfRouteChange() {
        base.pause(reason: .routeChange)
    }
}

這是一個古老的話題,但人們能做的卻恰恰相反。 而不是publicProtocol擴展internalProtocol有internalProtocol擴展publicProtocol。

public protocol AudioPlayerProtocol { 
   func pause() // I want 
}

internal protocol InternalAudioPlayerProtocol: AudioPlayerProtocol {
    func pause(reason: PauseReason) // Should only be accessible within the framework
}

public class AudioPlayer: InternalAudioPlayerProtocol {
    public func pause() {
        pause(reason: .byUser)
    }

    internal func pause(reason: PauseReason) { 
        //Do stuff
    }
}

然后在經理

public class AudioManager: AudioManagerProtocol {
    public let audioPlayer: AudioPlayerProtocol
    private let intAudioPlayer: InternalAudioPlayerProtocol

    init() {
        intAudioPlayer = AudioPlayer()
        audioPlayer = intAudioPlayer
        ...
    }
    ...
    private func pauseBecauseOfRouteChange() {
        intAudioPlayer.pause(reason: .routeChange)
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM