具有内部函数和属性的Swift公共协议

时间:2016-09-28 14:53:43

标签: ios swift swift3 access-modifiers swift-protocols

我想知道当我希望某些功能公开时,最好的做法是什么,当我使用协议时,有些内容对我来说是内部的。

我正在写一个AudioManager Swift 3 包装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 ......

或者你们的程序员如何做到这一点?如果框架没有公开供内部使用的方法和变量,框架会更美观!

谢谢!

3 个答案:

答案 0 :(得分:2)

不,没有更优雅的解决方案,至少在考虑协议时,这就是原因:

想象一下使用您的框架的某人想要为AudioPlayerProtocol编写扩展名的情景,如果它是内部的,那么如何实现pause(reason:)方法?

你可以通过子类化实现它,这个代码实际上将编译:

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

    internal func pause(reason: PauseReason) {
    }
}

对于协议情况并非如此,因为如果具有公共访问级别的人想要使用混合公共/内部协议,则无法保证实现内部功能。

答案 1 :(得分:1)

如果将协议拆分为内部和公共,然后让公共实现类委托给内部实现,那该怎么办?像这样

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

答案 2 :(得分:0)

这是一个古老的话题,但是实际上相反。 代替使用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)
    }
}