具有内部函数和属性的 Swift 公共协议
Posted
技术标签:
【中文标题】具有内部函数和属性的 Swift 公共协议【英文标题】:Swift Public protocols with Internal functions and properties 【发布时间】:2017-02-06 15:22:49 【问题描述】:我想知道在使用协议时,当我希望一些函数是公开的,而一些函数对我来说是内部的时,最佳实践是什么。我正在 Swift 3 中写一个 AudioManager strong> 将 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
更改为如下所示来使其工作:
func pauseBecauseOfRouteChange()
guard let internalPlayer = audioPlayer as? InternalAudioPlayerProtocol else return
internalPlayer.pause(reason: .routeChange)
但我想知道是否有更优雅的解决方案? 类似于标记AudioPlayerProtocol
精炼InternalAudioPlayerProtocol
...
或者各位程序员是怎么做的?如果框架不暴露供内部使用的方法和变量,框架会更漂亮!
谢谢!
【问题讨论】:
【参考方案1】:不,至少在考虑协议时没有更优雅的解决方案,原因如下:
想象一个场景,有人使用您的框架想要为AudioPlayerProtocol
编写扩展,那么如果pause(reason:)
方法是内部的,那么如何实现它?
你可以通过子类化来实现它,这段代码实际上会编译:
public class AudioPlayer: AudioPlayerProtocol
public func pause()
pause(reason: .byUser)
internal func pause(reason: PauseReason)
对于协议,情况并非如此,因为如果具有公共访问级别的人想要使用您的混合公共/内部协议,您根本无法保证内部功能的实现。
【讨论】:
【参考方案2】:如果你把你的协议分成内部和公共,然后让公共实现类委托给一个内部实现呢?像这样
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)
【讨论】:
【参考方案3】:这是一个古老的话题,但实际上可以做的恰恰相反。 代替 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)
【讨论】:
以上是关于具有内部函数和属性的 Swift 公共协议的主要内容,如果未能解决你的问题,请参考以下文章
JsonSerializer 在反序列化期间不使用内部构造函数
如何从具有隐藏变量名称(参数前的_)或内部+外部(例如 foo(with bar:))的 Objective-C 调用 Swift 函数