Swift: Why Cannot Add Store Property in Extension? What's the Different Between Store Property and Computed Property in Memory

Why extensions cannot add stored properties

In simple words

Because properties need storage, adding properties would change the memory structure of the class

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.

Why does Swift not allow stored properties in extensions?

Extensions are for extending the functionality of an existing class without changing the memory structure. It's more or less syntactic sugar. Imagine you could add stored properties and methods, what would it be? Nothing else but inheritance. So if you like to add new properties and methods just inherit from the class.

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

Have outlets in extensions

According the documentation, extensions cannot add stored properties to the class:

Extensions can add new computed properties, but they cannot add stored properties, or add property observers to existing properties.

Outlets are stored properties that are populated by the storyboards. Therefore you cannot define an outlet in the extension.

You can take a look at following SO question for some reasoning behind not allowing stored properties on extensions.

Is it possible use non read-only computed property in extension?


Is it possible computed property in extension that has getter and setter?

Yes.

Probably one of the most common uses of computed properties in extensions in my experience is providing a wrapper to make easier access to particular properties.

For example, when we want to modify the border layer, border color, or corner radius of anything out of UIKit, we're stuck going through the layer property.

But we can extend UIView with a property with both a setter & getter to provide a much more convenient means of changing the properties of its layer:

extension UIView {
var borderColor: UIColor? {
get {
guard let color = self.layer.borderColor else {
return nil
}
return UIColor(CGColor: color)
}
set {
self.layer.borderColor = newValue?.CGColor
}
}
}

Moreover, if we really want to, we can leverage the Objective-C run time to emulate stored properties in extensions (which of course mean setting & getting). Take part of this Stack Overflow answer for example:

private var kAssociationKeyNextField: UInt8 = 0

extension UITextField {
@IBOutlet var nextField: UITextField? {
get {
return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
}
set(newField) {
objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)
}
}
}

This serves as just one example of a property in an extension with a setter & getter.

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.



Related Topics



Leave a reply



Submit