In Swift, why does assigning to a static variable also invoke its getter

て烟熏妆下的殇ゞ 提交于 2019-12-19 02:52:38

问题


I understand that in Swift, static vars are implicitly lazy: https://stackoverflow.com/a/34667272/1672161

But I'm not clear on why this happens:

protocol HatType {}

class Hat: HatType {
    init() { print("real hat") }
}

class MockHat: HatType {
    init() { print("mock hat") }
}

struct HatInjector {
    static var hat: HatType = Hat()
}

HatInjector.hat = MockHat()

// Output:
// real hat
// mock hat

What I'm seeing is that the assignment to the static var, is also invoking the getter in a sense. This isn't intuitive to me. What is happening here? Why doesn't the assignment only happen?


回答1:


This is because static and global stored variables are currently (this is all subject to change) only given one accessor by the compiler – unsafeMutableAddressor, which gets a pointer to the variable's storage (this can be seen by examining the SIL or IR emitted).

This accessor:

  1. Gets a pointer to a compiler-generated global flag determining whether the static variable has been initialised.

  2. Calls swift_once with this pointer, along with a function that initialises the static variable (this is the initialiser expression you give it, i.e = Hat()). On Apple platforms, swift_once simply forwards onto dispatch_once_f.

  3. Returns a pointer to the static variable's storage, which the caller is then free to read and mutate – as the storage has static lifetime.

So it does more or less the equivalent of the Objective-C thread-safe lazy initialisation pattern:

+(Hat*) hat {

    static Hat* sharedHat = nil;
    static dispatch_once_t oncePredicate;

    dispatch_once(&oncePredicate, ^{
        sharedHat = [[Hat alloc] init];
    });

    return sharedHat;
}

The main difference being that Swift gives back a pointer to the storage of sharedHat (a pointer to a reference), rather than sharedHat itself (just a reference to the instance).

Because this is the one and only accessor for static and global stored variables, in order to perform an assignment, Swift needs to call it in order to get the pointer to the storage. Therefore, if it wasn't initialised already – the accessor needs to first initialise it to its default value (as it has no idea what the caller is going to do with it), before the caller then sets it to another value.

This behaviour is indeed somewhat unintuitive, and has been filed as a bug. As Jordan Rose says in the comments of the report:

This is currently by design, but it might be worth changing the design.

So this behaviour could well change in a future version of the language.




回答2:


Same solution as in Setting lazy static variable first initializes then assigns?

Try lazy loading:

struct HatInjector {
    private static var _hat: HatType?
    static var hat: HatType {
        get { return _hat ?? Hat() }
        set(value) { _hat = value }
    }
}

Or:

struct HatInjector {
    private static var _hat: HatType?
    static var hat: HatType {
        get {
            if _hat == nil {
                _hat = Hat()
            }
            return _hat!
        }
        set(value) { _hat = value }
    }
}

Reason: The static var in your code is not optional. Therefore when using it swift has to ensure that it isn't nil (swift is save!). Therefore the compiler request that you set an initial value. You can't define:

static var prop1: MyProtocol

This will result in a compiler error. If you define

static var prop1: MyProtocol?

it will be valid because it's a shortcut for

static var prop1: MyProtocol? = nil


来源:https://stackoverflow.com/questions/43374222/in-swift-why-does-assigning-to-a-static-variable-also-invoke-its-getter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!