プロトコルを操作するときに、一部の関数をパブリックにし、一部を内部に公開する場合のベストプラクティスは何でしょうか。
フレームワークとしてAVPlayer
をラップする AudioManager inSwift 3を書いています。
いくつかのメソッドを公開したいので、たとえば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
を次のように変更することで機能させることができます:
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) {
}
}
プロトコルの場合、これは当てはまりません。パブリックアクセスレベルの誰かがパブリック/内部の混合プロトコルを使用したい場合、内部機能の実装を保証できないためです。
これは古いトピックですが、実際にできることは逆です。 internalProtocolを拡張するpublicProtocolの代わりに、publicProtocolを拡張するinternalProtocolがあります。
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)
}
}
プロトコルを内部実装とパブリックに分割してから、パブリック実装クラスを内部実装に委任する場合はどうでしょうか。そのようです
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)
}
}