Swift Public protocols with Internal functions and properties - ios

I am wondering what the best practice is when I want some functions to be public and some to me internal when working with protocols.I am writing an AudioManager in Swift 3 wrapping AVPlayer as a framework. I want some methods to be public, so that e.g. a ViewController making use of the AudioManager can access some methods, but some methods would not be exposed outside the framework -> i.e. having the access modifier internal instead of public. I am writing the framework with protocol driven design, almost every part should have a protocol.So protocols are talking to protocols within the framework. E.g. the main class - AudioManager - has an AudioPlayer, and should be able to call some internal functions on it, e.g. pause(reason:) but that method should be internal and not exposed outside the framework.Here is an example.
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()
}
}
I know I can get it to work by changing the method pauseBecauseOfRouteChange to look like this:
func pauseBecauseOfRouteChange() {
guard let internalPlayer = audioPlayer as? InternalAudioPlayerProtocol else { return }
internalPlayer.pause(reason: .routeChange)
}
But I am wondering if there is a more elegant solution? Something like marking that the AudioPlayerProtocol refines the InternalAudioPlayerProtocol...
Or how do you fellow programmers do it?The framework is more beautiful if it does not expose methods and variables that are intended for internal use!
Thanks!

No, there is no more elegant solution to this, at least when considering protocols, and here is why:
Imagine a scenario that someone using your framework wants to write an extension for the AudioPlayerProtocol, how then pause(reason:) method can be implemented if it's internal?
You can achieve it by just subclassing and this code actually will compile:
public class AudioPlayer: AudioPlayerProtocol {
public func pause() {
pause(reason: .byUser)
}
internal func pause(reason: PauseReason) {
}
}
With protocols this is not the case, because you simply cannot guarantee implementation of internal function if someone with public access level wants to use your mixed public/internal protocol.

How about if you split your protocol into internal and public and then let the public implementation class delegate to an internal implementation. Like so
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)
}
}

It's an old topic but what one can do is actually the opposite.
Instead of publicProtocol extending internalProtocol have internalProtocol extending 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
}
}
Then in the manager
public class AudioManager: AudioManagerProtocol {
public let audioPlayer: AudioPlayerProtocol
private let intAudioPlayer: InternalAudioPlayerProtocol
init() {
intAudioPlayer = AudioPlayer()
audioPlayer = intAudioPlayer
...
}
...
private func pauseBecauseOfRouteChange() {
intAudioPlayer.pause(reason: .routeChange)
}
}

Related

Redefine protocol functions using constraints without having to expose method

Ugly 1
protocol Persisting {
func persist()
}
extension Persisting {
func persist() { print("persisting") }
}
protocol Service {
func get()
func persistIfAble() // If I remove this, "Not able to persist" gets printed twice
}
extension Service {
func get() {
persistIfAble()
}
}
extension Service {
func persistIfAble() {
print("Not able to persist")
}
}
extension Service where Self: Persisting {
func persistIfAble() {
persist()
}
}
struct OnlyService: Service {}
struct Both: Service, Persisting {}
let both = Both()
both.get()
let onlyService = OnlyService()
onlyService.get()
print("Can now directly call `persistIfAble` which is not wanted")
onlyService.persistIfAble() // DONT WANT THIS TO BE POSSIBLE
This solution would be elegant if I could remove func persistIfAble() from protocol declaration. Because I do not want it to be exposed. However, what is really interesting is that if I remove it, then the behavior changes, then the implementation inside extension Service where Self: Persisting never gets called.
Ugly 2
protocol Persisting {
func persist()
}
extension Persisting {
func persist() { print("persisting") }
}
protocol Service {
func get()
}
extension Service {
func get() {
// Ugly solution, I do not want to cast, `Service` should not have to know about `Persisting`
if let persisting = self as? Persisting {
persisting.persist()
} else {
print("not able to persist")
}
}
}
extension Service where Self: Persisting {
func persistIfAble() {
persist()
}
}
struct OnlyService: Service {}
struct Both: Service, Persisting {}
let both = Both()
both.get()
let onlyService = OnlyService()
onlyService.get()
The code in both ugly solutions is of course an extremely simplified version of my actual scenario, where I really do not want to perform casts, because it makes the code so much more difficult to read. Even if I would change if let to guard let.
Ugly 3 (ugliest?)
protocol Persisting {
func persist()
}
extension Persisting {
func persist() { print("persisting") }
}
protocol Service {
func get()
func persistIfAble(allowed: Bool)
}
extension Service {
func get() {
persistIfAble(allowed: true)
}
}
extension Service {
func persistIfAble(allowed: Bool = false) {
guard allowed else { print("KILL APP"); return }
print("Not able to persist")
}
}
extension Service where Self: Persisting {
func persistIfAble(allowed: Bool = false) {
guard allowed else { print("BREAKING RULES"); return }
persist()
}
}
struct OnlyService: Service {}
struct Both: Service, Persisting {}
let both = Both()
both.get()
let onlyService = OnlyService()
onlyService.get()
print("Can now directly call `persistIfAble` which is not wanted")
// DONT WANT THIS TO BE POSSIBLE
onlyService.persistIfAble() // prints: "KILL APP"
What am I missing?
Where is the beautiful solution?
I wonder if maybe what you really want is to use composition of actual objects, not just interfaces (and some default implementations). Consider this: Both Persisting and Service as you've defined them really need to be implemented in concrete classes or structs, so that they can contain context about where they are accessing their data. So I'd imagine that you could skip the protocol extensions, leave the real "guts" up to concrete implementations of those protocols, and then something like your Both would be implemented like this:
struct Both: Persisting, Service {
let persisting: Persisting
let service: Service
// a default init lets you pass in concrete implementations of both of those things
func persist() {
persisting.persist()
}
func get() {
service.get()
persist()
}
This obviously doesn't give you the automatic sort of "mix-in" effect that it looks like you're trying to achieve, but OTOH it's pretty clear to understand.
How about removing one level of indirection (i.e. 'persistIfAble')?
protocol Persisting {
func persist()
}
extension Persisting {
func persist() {
print("persisting")
}
}
protocol Service {
func get()
}
extension Service where Self: Persisting {
func get() {
persist()
}
}
extension Service {
func get() {
print("Not able to persist")
}
}

Subscribing to a property

I have a singleton service class which maintains a value of heading it gets from the compass. I have a UIView which draws some custom graphics based on this. I'm trying to do something like an Observable in javascript, where my code gets executed when there's a change in the value.
final class LocationService: NSObject {
static let shared = LocationService()
public var heading:Int
public func getHeading() -> Int {
return self.heading
}
Then in my UIView subclass:
var ls:LocationService = LocationService.shared
var heading: Int = ls.getHeading() {
didSet {
setNeedsDisplay()
}
}
I tried also just directly accessing the property via ls.heading but this doesn't get accepted either. It's telling me I cannot use the instance member within the property initialiser. What's a proper swift method of doing this?
Edit:
I've been working with Christian's answer below and some other documentation and now got to here where it all compiles nicely, but doesn't actually work. Here's my delegator and protocol:
final class LocationService: NSObject {
static let shared = LocationService()
weak var delegate: CompassView?
var heading:Int
func headingUpdate(request:HeadingRequest, updateHeading:CLHeading) {
print ("New heading found: \(updateHeading)")
self.heading = Int(updateHeading.magneticHeading)
self.delegate?.setHeading(newHeading: Int(updateHeading.magneticHeading))
}
public func getHeading() -> Int {
return self.heading
}
}
protocol LSDelegate: class {
func setHeading(newHeading:Int)
}
Then in the delegate:
class CompassView: UIView, LSDelegate {
func setHeading(newHeading:Int) {
self.heading = newHeading
print("heading updated in compass view to \(self.heading)")
setNeedsDisplay()
}
}
So I get the print message that the heading has been updated in the headingUpdate function. The print message in the setHeading function in the delegate CompassView never gets displayed.
You can use the delegation pattern and have that class that wants to consume your events implement the functions in your protocol.
protocol MyDelegate {
func setNeedsDisplay()
}
class LocationService: NSObject {
var myDelegate : MyDelegate?
var heading: Int = ls.getHeading() {
didSet {
myDelegate?.setNeedsDisplay()
}
}
...
func assignDelegate() {
self.myDelegate = MyConsumer()
}
}
class MyConsumer : MyDelegate {
func setNeedsDisplay()
{
}
}

Composition rather than inheritance for shared variables

I was previously using a class that could be simplfied down to this:
class Whatever {
var someArray = [Int]()
func unchangingFunction {
print("test")
}
func functionForOverride() {}
}
I was asking of ways to improve this, and I got told to favour composition over inheritance, using something like the following:
protocol Implementation {
func functionForOverride()
}
final class Whatever {
var someArray = [Int]() // How can I access this?
let implementation: Implementation
init(implementation: Implementation) {
self.implementation = implementation
}
func unchangingFunction() {
print("test")
}
func functionForOverride() {
implementation.functionForOverride()
}
}
However, with this, I can't find a way to do anything with the someArray array:
struct Something: Implementation {
func functionForOverride() {
print(someArray) // This cannot work
}
}
With the original code I am able to access and alter someArray however I want, but with this new way, I can't think of an easy solution.
I think we should use a "real" example in order to make things clearer.
Inheritance (and why it's wrong)
We have the following classes
class Robot {
var battery = 0
func charge() {
print("⚡️")
battery += 1
}
}
class Human {
func eat() {
print("🍽")
}
}
class RobotCleaner: Robot {
func clean() {
print("💧")
}
}
class HumanCleaner: Human {
func clean() {
print("💧")
}
}
Code duplication!!!
As you can see the clean() method is duplicated in RobotCleaner and HumanCleaner. Can you find a way (using inheritance) to remove code duplication?
Ok think about that, I'll wait on the next paragraph... :)
...
Oh, here you are! There's no way to fix that with inheritance right? Well, let's see what we can do with composition.
Composition (the classic way)
Let's define the following 3 protocols and related components
protocol Robot {
mutating func charge()
}
struct RobotComponent: Robot {
var battery = 0
mutating func charge() {
print("⚡️")
battery += 1
}
}
protocol Human {
func eat()
}
struct HumanComponent: Human {
func eat() {
print("🍽")
}
}
protocol Cleaner {
func clean()
}
struct CleanerComponent: Cleaner {
func clean() {
print("💧")
}
}
Now we can build any combination of the previous 3 elements
struct RobotCleaner: Robot, Cleaner {
var robotComponent = RobotComponent()
let cleanerComponent = CleanerComponent()
mutating func charge() {
robotComponent.charge()
}
func clean() {
cleanerComponent.clean()
}
}
struct HumanCleaner: Human, Cleaner {
let humanComponent = HumanComponent()
let cleanerComponent = CleanerComponent()
func eat() {
humanComponent.eat()
}
func clean() {
cleanerComponent.clean()
}
}
Protocol Oriented Programming: Composition the Swifty way
Swift offers a very neat way of doing composition.
First of all let's define the following 3 protocols (and related extensions).
protocol Robot {
var battery: Int { get set }
}
extension Robot {
mutating func charge() {
print("⚡️")
battery += 1
}
}
protocol Human { }
extension Human {
func eat() {
print("🍽")
}
}
protocol Cleaner { }
extension Cleaner {
func clean() {
print("💧")
}
}
Now we can create a Type which has any combination of the previous 3 entities. Let's see how.
struct HumanCleaner: Human, Cleaner { }
struct RobotCleaner: Robot, Cleaner {
var battery: Int = 0
}
If 'Implementation' requires 'someArray' to do what it is intended to do, then you should have 'Implementation' require any object conforming to it to also declare 'someArray'
Like this:
protocol Implementation {
var someArray: [Int]
}
And if you know what you want to do with 'someFunction', then you could give a default implementation of it with a protocol extension like so:
extension Implementation {
func someFunction() {
//you can do some stuff with someArray here
}
}
Then when you conform to 'Implementation' you need to declare 'someArray' but not 'someFunction', unless you want to override the default function.
E.g.
class MyClass: Implementation {
var someArray: [Int]!
init() {}
}
Note that MyClass now has access to 'someFunction', which can be freely overridden in your class, and that you can add as many functions as you want to 'Implementation's extension.

iOS Swift How to create a Protocol instance

I am a Android developer, and I'm very new to Swift so please bear with me. I am trying to implement callback functions with Protocol in Swift. In Java I can create an Interface and make it an instance without linking it to any implementing class so that I can pass it around, for example:
public interface SomeListener {
void done();
}
SomeListener listener = new SomeListener() {
#Override
public void done() {
// do something
}
}
listener.done();
How can I do it with Protocol in Swift? Or can it actually be done?
That`s a way you can implement a protocol. Its like the delegate pattern in ObjC
protocol DoneProtocol {
func done()
}
class SomeClass {
var delegate:DoneProtocol?
func someFunction() {
let a = 5 + 3
delegate?.done()
}
}
class Listener : DoneProtocol {
let someClass = SomeClass()
init() {
someClass.delegate = self
someClass.someFunction()
}
// will be called after someFunction() is ready
func done() {
println("Done")
}
}

Why is my simple swift delegate and protocol setup not working?

I am trying to grasp the concept of delegates and protocols in swift. So I have implemented my own PlayableMedia protocol with two concrete classes BlueRayMedia and DVDMedia like so:
#protocol PlayableMedia {
func play()
func stop()
}
class BlueRayMedia:PlayableMedia {
func play() {
println("BlueRayMedia is playing")
}
func stop() {
println("BlueRayMedia has stopped playing")
}
}
class DVDMedia:PlayableMedia {
func play() {
println("DVD is playing")
}
func stop() {
println("DVD has stopped playing")
}
}
So now I have a DVDPlayer class that uses this setup:
class DVDPlayer {
var media:PlayableMedia // delegate property
init(media:PlayableMedia){
self.media = media
}
func didStartPlaying() {
media.play()
}
func didStopPlaying() {
media.stop()
}
}
But when I try to use it like this:
var dvdPlayer:DVDPlayer = DVDPlayer(media: BlueRayMedia())
dvdPlayer.didStartPlaying()
I get (no results) in my playground console. What am I doing wrong?
Ok so the simple mistake I made was use #protocol instead of just protocol
So this works:
protocol PlayableMedia {
func play()
func stop()
}
Inside the Playground println() doesn't work.
Add some other expression like let x = 5

Resources