Swift extension storage for protocols
Any protocol object can be converted into a type-erased class. Build an AnySomeProtocol
and store that.
private var sourceKey: UInt8 = 0
final class AnySomeProtocol: SomeProtocol {
func getData() -> String { return _getData() }
init(_ someProtocol: SomeProtocol) { _getData = someProtocol.getData }
private let _getData: () -> String
}
extension UIViewController: SomeProtocolInjectable {
var source: SomeProtocol! {
get {
return objc_getAssociatedObject(self, &sourceKey) as? SomeProtocol
}
set(newValue) {
objc_setAssociatedObject(self, &sourceKey, AnySomeProtocol(newValue), .OBJC_ASSOCIATION_RETAIN)
}
}
}
class MyViewController : UIViewController {
override func viewDidLoad() {
self.title = source.getData()
}
}
The caller can only use this to access the protocol methods. You can't force it back into its original type with as
, but you should avoid that anyway.
As a side note, I'd really recommend making source
return SomeProtocol?
rather than SomeProtocol!
. There's nothing here that promises that source
will be set. You don't even set it until viewDidLoad
.
Alternative to storing a variable in a Swift extension
Don't make it more complex than it needs to be. UIView
and its subclasses must derive from NSObject
. Read the documentation on objc_getAssociatedObject
and objc_getAssociatedObject
. No need for protocols or other abstractions.
import ObjectiveC
private var key: Void? = nil // the address of key is a unique id.
extension UIView {
var propertiesToSend: [String: [String: String]] {
get { return objc_getAssociatedObject(self, &key) as? [String: [String: String]] ?? [:] }
set { objc_setAssociatedObject(self, &key, newValue, .OBJC_ASSOCIATION_RETAIN) }
}
}
This can be used as follows.
let button = UIButton()
button.propertiesToSend = ["a": ["b": "c"]]
print(button.propertiesToSend["a"]?["b"] ?? "unknown")
Swift property observer in protocol extension?
No, this is explicitly disallowed. See Extension: Computed Properties:
Extensions can add new computed properties, but they cannot add stored properties, or add property observers to existing properties.
Keep in mind that if this were legal, it would add some non-trivial confusion about order of execution. Imagine there were several extensions that added didSet
, and the actual implementation also had a didSet
. What order should they run in? This doesn't mean it's impossible to implement, but it could be somewhat surprising if we had it.
Swift - Protocol extensions - Property default values
It seems you want to add a stored property
to a type via protocol extension. However this is not possible because with extensions you cannot add a stored property.
I can show you a couple of alternatives.
Subclassing (Object Oriented Programming)
The easiest way (as probably you already imagine) is using classes instead of structs.
class IdentifiableBase {
var id = 0
var name = "default"
}
class A: IdentifiableBase { }
let a = A()
a.name = "test"
print(a.name) // test
Cons: In this case your A class needs to inherit from
IdentifiableBase
and since in Swift theres is not multiple inheritance this will be the only class A will be able to inherit from.
Components (Protocol Oriented Programming)
This technique is pretty popular in game development
struct IdentifiableComponent {
var id = 0
var name = "default"
}
protocol HasIdentifiableComponent {
var identifiableComponent: IdentifiableComponent { get set }
}
protocol Identifiable: HasIdentifiableComponent { }
extension Identifiable {
var id: Int {
get { return identifiableComponent.id }
set { identifiableComponent.id = newValue }
}
var name: String {
get { return identifiableComponent.name }
set { identifiableComponent.name = newValue }
}
}
Now you can make your type conform to Identifiable
simply writing
struct A: Identifiable {
var identifiableComponent = IdentifiableComponent()
}
Test
var a = A()
a.identifiableComponent.name = "test"
print(a.identifiableComponent.name) // test
Extension may not contain stored property but why is static allowed
Extensions cannot contain stored instance properties. Why? Because adding an instance property would change the size of instances of that type. What happens if one module adds an extension such that an Int
is now 2 words long? What should then happen when it, for example, gets an Int
from another module where they are still 1 word in size?
The reason why static stored properties are permitted in extensions is simply because they have static lifetime; they exist independently of any instances of the given type you're extending. Really they're nothing more than global stored variables, just namespaced to a type. Therefore they can be freely added without affecting code that has already been compiled without knowledge of them.
It's worth noting however that there are currently three restrictions on defining static stored properties.
1. You cannot define a static
stored property on a generic type
This would require separate property storage for each individual specialisation of the generic placeholder(s). For example, with:
struct S<T> {
static var foo: Int {
return 5
}
static let bar = "" // error: Static stored properties not supported in generic types
}
Just as foo
is called on individual specialisation of S
, e.g S<Int>.foo
and S<Float>.foo
and not on S
itself (in fact; S
is not even a type currently, it requires that T
be satisfied); bar
would (likely) be the same. It would be called as, for example, S<Int>.bar
, not S.bar
.
This is an important detail because the metatype that a static member is called on is passed to the receiver as the implicit self
argument. This is accessible in static property initialiser expressions; therefore allowing them to call other static methods.
Therefore being able to call the same static property initialiser on different specialisations of a generic type would have the potential to create different property values for each (consider the simple case of static let baz = T.self
). Therefore we need separate storage for each of them.
However, that all being said, there's no real reason why the compiler/runtime cannot do this, and it may well do in a future version of the language. Although one argument against this is that it may produce confusing behaviour in some cases.
For example, consider:
import Foundation
struct S<T> {
static let date = Date()
}
If the runtime implicitly generated new storage for date
each time it gets accessed on a new specialisation of S<T>
, then S<Float>.date
would not equal S<Int>.date
; which may be confusing and/or undesirable.
2. You cannot define a static
stored property in a protocol extension
This mostly follows on from the previous point. A static
stored property in a protocol extension would require separate storage for each conforming type of that protocol (but again; there's no reason why the compiler/runtime cannot do this).
This is necessary with protocols, as static
members in protocol extensions are not members on the protocol type itself. They are members on concrete types that conform to the protocol.
For example, if we have:
protocol P {}
extension P {
static var foo: Int {
return 5
}
static let bar = "" // error: Static stored properties not supported in generic types
// (not really a great diagnostic)
}
struct S : P {}
struct S1 : P {}
We cannot access foo
on the protocol type itself, we cannot say P.foo
. We can only say S.foo
or S1.foo
. This is important because foo
's getter can call out to static protocol requirements on self
; however this isn't possible if self
is P.self
(i.e the protocol type itself), as protocols don't conform to themselves.
The same would (likely) follow for static
stored properties such as bar
.
3. You cannot define a class
stored property
I don't believe there would be any problems with such a declaration in the class body itself (it would simply be equivalent to a computed class
property backed by a static
stored property).
However it would be potentially problematic in extensions, because extensions cannot add new members to a Swift class vtable (though they can add to the Obj-C counterpart if applicable). Therefore in most cases they wouldn't be dynamically dispatched to (so would effectively be final
, and therefore static
). Although that being said, class
computed properties are currently permitted in extensions, so it may be permissible in the interests of consistency.
How to have stored properties in Swift, the same way I had on Objective-C?
Associated objects API is a bit cumbersome to use. You can remove most of the boilerplate with a helper class.
public final class ObjectAssociation<T: AnyObject> {
private let policy: objc_AssociationPolicy
/// - Parameter policy: An association policy that will be used when linking objects.
public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) {
self.policy = policy
}
/// Accesses associated object.
/// - Parameter index: An object whose associated object is to be accessed.
public subscript(index: AnyObject) -> T? {
get { return objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T? }
set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) }
}
}
Provided that you can "add" a property to objective-c class in a more readable manner:
extension SomeType {
private static let association = ObjectAssociation<NSObject>()
var simulatedProperty: NSObject? {
get { return SomeType.association[self] }
set { SomeType.association[self] = newValue }
}
}
As for the solution:
extension CALayer {
private static let initialPathAssociation = ObjectAssociation<CGPath>()
private static let shapeLayerAssociation = ObjectAssociation<CAShapeLayer>()
var initialPath: CGPath! {
get { return CALayer.initialPathAssociation[self] }
set { CALayer.initialPathAssociation[self] = newValue }
}
var shapeLayer: CAShapeLayer? {
get { return CALayer.shapeLayerAssociation[self] }
set { CALayer.shapeLayerAssociation[self] = newValue }
}
}
Swift 2: UITableViewDataSource protocol extension
I know it's a bit late to respond, and you may not even be looking for this answer, but I just came across this exact issue and needed a real world "solution". You can implement the UITableViewDataSource methods in the class and then immediately hand off the work to the protocol extension like the example below. If swift makes improvements that no longer require this, it's simple to change back to the code in your original post.
//: Playground - noun: a place where people can play
import UIKit
protocol ArrayContainer {
associatedtype T
var array: [T] { get }
}
class MyViewController: UIViewController, ArrayContainer, UITableViewDataSource {
typealias T = String
var array = ["I am", "an Array"]
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.internal_numberOfSectionsInTableView(tableView)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.internal_tableView(tableView, numberOfRowsInSection: section)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return self.internal_tableView(tableView, cellForRowAtIndexPath: indexPath)
}
}
extension UITableViewDataSource where Self: ArrayContainer {
func internal_numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func internal_tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
func internal_tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Whatever
return UITableViewCell()
}
}
Related Topics
Swiftui 2 Pop to Root View with No Scene Delegate
Swift; Delegate Embedded View Controller and Parent
iOS Swift: Unsafemutableaddressor Crash on iOS 8
Non Exhaustive List When Handling Errors Inside a Class Function in Swift
Swift Threading with Dispatchgroup
How to Rotate an Arkit 4X4 Matrix Around Y Using Apple's Simd Library
How to Use Socket in Swift (Connect, Send and Get Message)
Swiftui Inputaccesoryview Implementation
Swift String Permutations Allowing the Same Strings
How to Integrate Uisearchcontroller with Swiftui
iOS Charts Remove Decimal from Yvalues
Subclass Uiscrollview in Swift for Touches Began & Touches Moved
Macos App Local Notification Not Showing When Testing with Xcode
Xcode Swift. How to Programmatically Select Cell in View-Based Nstableview