I have a method in a protocol extension that plays music files. Since the protocol doesn't know if its conformer will be a class or struct and the methods in it change an ivar, it requires them to be marked as mutating.
When I conform a class to that protocol and try to call the method I'm getting the below error even though a class should always be muteable...
Cannot use mutating member on immutable value: 'self' is immutable
Here's the protocol...
import AVFoundation
/// Conformers are required to implement `player` property to gain access to `playMusic:fileLloops`
/// method.
protocol CanPlayMusic {
/// This instance variable stores the audio player for `playMusic:file:loops` method.
var player: AVAudioPlayer! { get set }
}
extension CanPlayMusic {
/// This method creates a new audio player from url and then plays for a number of given.
///
/// - Parameter file: The url where sound file is stored.
/// - Parameter loops: The number of loops to play (-1 is infinite).
mutating func playMusic(file url: URL, loops: Int = -1) {
player = try! AVAudioPlayer(contentsOf: url)
player.numberOfLoops = loops
player.play()
}
/// This method starts playing intro music.
mutating func playIntroMusic() {
let file = Assets.Music.chargeDeBurkel
let ext = Assets.Music.ext
guard let url = Bundle.main.url(forResource: file,
withExtension: ext) else { return }
playMusic(file: url)
}
/// This method starts playing game over music based on win/loss condition.
mutating func playGameOverMusic(isWinning: Bool) {
guard let lose = Bundle.main.url(forResource: Assets.Music.taps,
withExtension: Assets.Music.ext),
let win = Bundle.main.url(forResource: Assets.Music.reveille,
withExtension: Assets.Music.ext)
else { return }
playMusic(file: isWinning ? win : lose, loops: 1)
}
}
And here's how I call it in a class...
import UIKit
import AVFoundation
class EntranceViewController: UIViewController, CanGetCurrency, CanPlayMusic {
...
// MARK: - Properties: CanPlayMusic
var player: AVAudioPlayer!
...
// MARK: - Functions: UIViewController
override func viewDidLoad() {
playIntroMusic() // <-- Error thrown here
startButton.setTitle("", for: .normal)
getCurrency()
}
UPDATE
I use these methods in multiple places, both in UIKit scenes and SwiftUI views; moving it into the protocol extension was an attempt to reduce duplication of code.
For now, I'm using a class wrapper that I can call in both contexts; but I still haven't seen an explanation for the error triggering on a class (since they're pass by ref and all of their functions are considered mutating by default).
To clarify, my question is "Why is this class having issues with a mutating function?"
The error is a bit misleading, but I believe the reason of it is that you call a method marked with mutating (defined in your protocol extension) from a class – it's illegal.
Consider this simplified example:
protocol SomeProtocol {
var state: Int { get set }
}
extension SomeProtocol {
mutating func doSomething() {
state += 1
}
}
struct SomeValueType: SomeProtocol {
var state = 0
init() {
doSomething()
}
}
final class SomeReferenceType: SomeProtocol {
var state = 0
init() {
doSomething() // Cannot use mutating member on immutable value: 'self' is immutable
}
}
One way to get rid of the error is not using the same implementation for both structs and classes and defining their own implementations:
protocol SomeProtocol {
var state: Int { get set }
mutating func doSomething()
}
struct SomeValueType: SomeProtocol {
var state = 0
init() {
doSomething()
}
mutating func doSomething() {
state += 1
}
}
final class SomeReferenceType: SomeProtocol {
var state = 0
init() {
doSomething()
}
func doSomething() {
state += 1
}
}
Another way is to, at least, defining an own implementation for classes, which will shade the default implementation from the protocol extension:
protocol SomeProtocol {
var state: Int { get set }
}
extension SomeProtocol {
mutating func doSomething() {
state += 1
}
}
struct SomeValueType: SomeProtocol {
var state = 0
init() {
doSomething()
}
}
final class SomeReferenceType: SomeProtocol {
var state = 0
init() {
doSomething()
}
func doSomething() {
state += 1
}
}
Related
I am developing a state management library. The original design only has 1 listener, which works great until I need to support multiple listeners.
The original design is here:
Swift how to use generic protocol in generic class
This is what I have done to support multiple listeners:
public protocol StateObserver: AnyObject {
associatedtype State
func didUpdateState(_ state: State)
}
public final class StateStore<Observer: StateObserver> {
struct WeakRef<T: AnyObject> {
weak var value: T?
}
public private(set) var state: Observer.State
private var observers = [WeakRef<Observer>]()
public init(initialState: Observer.State) {
state = initialState
}
public func addObservers(_ observers: [Observer]) {
self.observers += observers.map { WeakRef(value: $0) }
}
public func update(_ block: (inout Observer.State) -> Void) {
var nextState = state
block(&nextState)
state = nextState
notify()
}
public func notify() {
for observer in observers {
observer.value?.didUpdateState(state)
}
}
}
Now I need to create the store with 2 observers:
class MyScene: SKScene {
init {
let leftPanel = LeftPanelSKNode()
let topBar = TopBarSKNode()
let store: StateStore<?> // How to make this support `LeftPanelSKNode `, `TopBarSKNode`, and `MyScene`?
store.addObservers([leftPanel, topBar, self])
}
Now I am stuck here. I need to create a StateStore<?> of something, which can be either MyScene, LeftPanelSKNode and TopBarSKNode.
First of all, I have to say that what you are building already exists in many reactive libraries:
CurrentValueSubject in Apple's Combine;
BehaviorSubject in RxSwift;
You can also check the small internal class I've made myself, it allows to hold the state and observe it ObservableProperty.
Back to your question, I've found a way to add the StateObserver one by one while keeping only the weak reference to them.
public protocol StateObserver: AnyObject {
associatedtype State
func didUpdateState(_ state: State)
}
class Node1: StateObserver {
typealias State = Int
func didUpdateState(_ state: Int) { }
}
class Node2: StateObserver {
typealias State = Int
func didUpdateState(_ state: Int) { }
}
class StateStore<StateType> {
private(set) var state: StateType
init(_ initialState: StateType) {
self.state = initialState
}
private var observers: [(StateType) -> Void] = []
func observe<Observer: StateObserver>(by observer: Observer) where Observer.State == StateType {
weak var weakObserver = observer
observers.append { state in
weakObserver?.didUpdateState(state)
}
}
func notify() {
observers.forEach {
$0(self.state)
}
}
}
let store = StateStore<Int>(0)
let node1 = Node1()
let node2 = Node2()
store.observe(by: node1)
store.observe(by: node2)
Adding the array-based observe API might be a problem because of the associatedtype in the StateObserver.
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()
{
}
}
I'm trying to implement SharedInstanceDelegate in App class. I have no idea why the functions under the protocol are not being called.
This is my Protocol and class.
class App {
let sharedInstance = SharedInstance.shared
init() {
self.sharedInstance.delegate = self
}
}
extension App: SharedInstanceDelegate {
func1() { } // this is not executed
func2() { }
}
protocol SharedInstanceDelegate: class {
func1()
func2()
}
class SharedInstance {
static let shared = SharedInstance()
weak var delegate: SharedInstanceDelegate?
private init() { }
func method1() {
self.delegate?.func1() // this is executed
}
}
I believe you meant to make SharedInstanceDelegate a protocol, but you've made it a class. In either case, App does not conform/inherit SharedInstanceDelegate, so it's not clear how this would even compile.
Here is how I would implement your code to work with the delegate:
class App {
let sharedInstance = SharedInstance.shared
init() {
self.sharedInstance.delegate = self
}
}
extension App: SharedInstanceDelegate {
func func1() { } // this will run now
func func2() { }
}
protocol SharedInstanceDelegate {
func func1()
func func2()
}
class SharedInstance {
static let shared = SharedInstance()
var delegate: SharedInstanceDelegate?
private init() { }
func method1() {
self.delegate?.func1() // this is executed
}
}
Still no idea why this was happening, but cleaning the project fixed this. This is very strange. I have other delegates that call successfully.
Your code could work but it depends on how you are calling func1(). Calling it like this:
let testinstance = App().sharedInstance
testinstance.delegate?.func1()
will not work because you are not holding on to the App object. In this case the App object is the delegate, but because its a weak member and no one is retaining it, it gets released right away.
If you call it like this:
let testapp = App()
testapp.sharedInstance.delegate?.func1()
it works. In this case the App object is being retained and is still around when func1() is called.
Either way the way these classes are related is confusing to me. Why have a separate SharedInstance class at all?
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)
}
}
I have an extension on UIView implementing a protocol
protocol SomeProtocol {
var property : Int
}
extension UIView : SomeProtocol {
var property : Int {
get {
return 0
}
set {
// do nothing
}
}
}
in a concrete subclass I want to override this extension method:
class Subclass : UIView, SomeProtocol {
var _property : Int = 1
var property : Int {
get { return _property}
set(val) {_property = val}
}
}
I set breakpoints and see that the extension method is called and not the concrete subclass method:
var subclassObject = Subclass()
someObject.doSomethingWithConcreteSubclassObject(subclassObject)
// other code;
fun doSomethingWithConcreteSuclassObject(object : UIView) {
var value = object.property // always goes to extension class get/set
}
As others have noted, Swift does not (yet) allow you to override a method declared in a class extension. However, I'm not sure whether you'll ever get the behavior you want even if/when Swift someday allows you to override these methods.
Consider how Swift deals with protocols and protocol extensions. Given a protocol to print some metasyntactic variable names:
protocol Metasyntactic {
func foo() -> String
func bar() -> String
}
An extension to provide default implementations:
extension Metasyntactic {
func foo() -> String {
return "foo"
}
func bar() -> String {
return "bar"
}
}
And a class that conforms to the protocol:
class FooBar : Metasyntactic {
func foo() -> String {
return "FOO"
}
func bar() -> String {
return "BAR"
}
}
Swift will use dynamic dispatch to call the appropriate implementations of foo() and bar() based on each variable's runtime type rather than on the type inferred by the compiler:
let a = FooBar()
a.foo() // Prints "FOO"
a.bar() // Prints "BAR"
let b: Metasyntactic = FooBar()
b.foo() // Prints "FOO"
b.bar() // Prints "BAR"
If, however, we extend the protocol further to add a new method:
extension Metasyntactic {
func baz() -> String {
return "baz"
}
}
And if we override our new method in a class that conforms to the protocol:
class FooBarBaz : Metasyntactic {
func foo() -> String {
return "FOO"
}
func bar() -> String {
return "BAR"
}
func baz() -> String {
return "BAZ"
}
}
Swift will now use static dispatch to call the appropriate implementation of baz() based on the type inferred by the compiler:
let a = FooBarBaz()
a.baz() // Prints "BAZ"
let b: Metasyntactic = FooBarBaz()
b.baz() // Prints "baz"
Alexandros Salazar has a fantastic blog post explaining this behavior in depth, but suffice it to say that Swift only uses dynamic dispatch for methods declared in the original protocol, not for methods declared in protocol extensions. I imagine the same would be true of class extensions, as well.
I know this question has been asked a while ago. But this will be handy for someone who looking for an easier way. There is a way of overriding an extension methods. I know its bit hacky but it does the job beautifully.
If you declare your protocol with #objc
#objc protocol MethodOverridable {
func overrideMe()
}
In Extension
extension MainClass: MethodOverridable {
func overrideMe() {
print("Something useful")
}
}
Subclass - You can able to override it in your subclass. It works like a magic. Well, not really when adding #objc it exposes your protocol to Objective-C and its Runtime. That allows your subclass to override.
class SubClass: MainClass {
override func overrideMe() {
print("Something more useful")
}
}
Swift 5
class Class
{
#objc dynamic func make() { print("make from class") }
}
class SubClass: Class {}
extension SubClass {
override func make() {
print("override")
}
}
It looks like you can override property for 2nd super class property. For example, you can access UIView property by making extension to the UILabel wanting to override frame property of UIView. This sample works for me in Xcode 6.3.2
extension UILabel {
override public var frame: CGRect {
didSet {
println("\(frame)")
}
}
}
You can't do this through normal means.
It's in Apple's docs that you can't override a method in an extension in a subclass.
Also, extensions can add new functionality to a type, but they cannot override existing functionality.
https://docs.swift.org/swift-book/LanguageGuide/Extensions.html
I think you forgot to override the superclass property in your subclass:
class Subclass : UIView {
var _property : Int = 1
override var property : Int {
get { return _property}
set(val) {_property = val}
}
}