2016-09-28 65 views
5

我想知道什麼是最佳實踐,當我需要一些函數公開和一些內部使用協議時。

我正在寫一個AudioManagerSwift 3包裝AVPlayer作爲一個框架。

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

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

這裏是一個例子。具有內部函數和屬性的Swift公共協議

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工作看起來像這樣:

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

但我想知道如果有一個更好的解決方案?
類似於標記AudioPlayerProtocol細化了InternalAudioPlayerProtocol ...

或者你們是如何做到這一點的?
如果該框架不公開用於內部使用的方法和變量,則該框架更美觀!

謝謝!

回答

0

沒有,有沒有更好的解決方案這一點,考慮到協議時,這裏至少是爲什麼:

設想一個場景,使用你的框架有人想要寫的AudioPlayerProtocol的延伸,又如何pause(reason:)方法可以實現,如果它是內部的?

你可以只繼承和這段代碼實際上將編譯實現它:

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

    internal func pause(reason: PauseReason) { 
    } 
} 

有了協議,這是不是這樣的,因爲你根本,如果有人用公共訪問級別要不能保證實施內部功能使用您的混合公共/內部協議。

0

如果將協議拆分爲內部和公共,然後讓公共實現類委託給內部實現,那麼該怎麼辦?像這樣

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) 
    } 
}