I'm trying to find out if there's any documentation on Swift's protocol associatedtype
using a =
instead of a :
.
Eg.
associatedtype Thing = SomeOtherThing
vs
associatedtype Thing: SomeOtherThing
Not to be confused with (what probably confused me) typealias Thing = SomeOtherThing
which is always a =
I believe.
I was trying to abstract usage of a class that included a delegate and was using =
for associatedtype
s until I ran into an issue where the variable that was that type wasn't exposing its properties due to the =
and instead needed the usual :
, which to me made sense, in a way, but then when I changed one particular =
to a :
it caused everything to break. I've included an example below, where the idea was to be able to get/set the delegate object freely, but more or less one protocol was saying that it's delegate must be the type of the associated type (with the =
) rather than just "you must conform to this" as the :
seems to suggest.
I also don't know if I've taken this one step too far and there's some other better way to express this in terms of testing. This seems to be required as I can't exactly rely on the external object to work as expected in the tests, and instead need to mock it to fail on purpose and such in some cases.
import Foundation
// Concrete objects (eg external framework)
protocol ManagerDelegate: AnyObject {
func managerDidSomething(_ manager: Manager)
}
class Manager {
weak var delegate: ManagerDelegate?
func doSomething() {
delegate?.managerDidSomething(self)
}
}
// Custom objects using concrete objects, set up to be testable
class CustomManagerDelegate: ManagerDelegate {
func managerDidSomething(_ manager: Manager) {
print(#function)
}
}
class CustomClass<ManagerType: SomeManager> {
private(set) var doSomethingCustomCalled = false
private let managerDelegate: ManagerType.DelegateType
private let manager: ManagerType
init(manager: ManagerType, managerDelegate: ManagerType.DelegateType) {
self.manager = manager
self.managerDelegate = managerDelegate
manager.delegate = managerDelegate
}
func doSomethingCustom() {
doSomethingCustomCalled = true
manager.doSomething()
}
}
// Example creation of custom object
class Example {
static func createCustomObject() {
let customObject = CustomClass(
manager: Manager(),
// if `:` used instead of `=` for `SomeManager.DelegateType`, error is:
// Cannot convert value of type 'CustomManagerDelegate' to expected argument type 'Manager.DelegateType'
managerDelegate: CustomManagerDelegate() // error fix: add `as! Manager.DelegateType`
)
customObject.doSomethingCustom()
}
}
// Testable interface
protocol SomeManager: AnyObject {
// This `=` is the only thing keeping it together
associatedtype DelegateType = SomeManagerDelegate
// This doesn't work
//associatedtype DelegateType: SomeManagerDelegate
var delegate: DelegateType? { get set }
func doSomething()
}
protocol SomeManagerDelegate {
associatedtype ManagerType: SomeManager
func managerDidSomething(_ manager: ManagerType)
}
// Testable interface conformance
// if `:` used instead of `=` for `SomeManager.DelegateType`, error is:
// Type 'Manager' does not conform to protocol 'SomeManager'
extension Manager: SomeManager {
// already conforms
}
class MockManagerDelegate: SomeManagerDelegate {
typealias ManagerType = MockManager
func managerDidSomething(_ manager: ManagerType) {
print(#function)
}
}
class MockManager: SomeManager {
weak var delegate: MockManagerDelegate?
func doSomething() {
delegate?.managerDidSomething(self)
}
}
// Tests
class CustomClassTests {
func testCustomSomethingWasCalled() {
let mockInjectedCustomClass = CustomClass(
manager: MockManager(),
managerDelegate: MockManagerDelegate()
)
mockInjectedCustomClass.doSomethingCustom()
print("Was Called:", mockInjectedCustomClass.doSomethingCustomCalled)
assert(mockInjectedCustomClass.doSomethingCustomCalled)
}
}
CustomClassTests().testCustomSomethingWasCalled()
/* console:
managerDidSomething(_:)
Was Called: true
*/