In Swift, Why Does Assigning to a Static Variable Also Invoke Its Getter

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

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.

Setting lazy static variable first initializes then assigns?

You need lazy loading. Try this:

struct MyMap {
private static var _prop1: MyProtocol?
static var prop1: MyProtocol {
get { return _prop1 ?? MyClass() }
set(value) { _prop1 = value }
}
}

Or this:

struct MyMap {
private static var _prop1: MyProtocol?
static var prop1: MyProtocol {
get {
if _prop1 == nil {
_prop1 = MyClass()
}
return _prop1!
}
set(value) { _prop1 = value }
}
}

Implicitly lazy static members in Swift

The static property defines a "type property", one that is instantiated once and only once. As you note, this happens lazily, as statics behave like globals. And as The Swift Programming Language: Properties says:

Global constants and variables are always computed lazily, in a similar manner to Lazy Stored Properties. Unlike lazy stored properties, global constants and variables do not need to be marked with the lazy modifier.

This implicitly lazy behavior is because, as the Swift Blog: Files and Initialization says:

it allows custom initializers, startup time in Swift scales cleanly with no global initializers to slow it down, and the order of execution is completely predictable.

They consciously designed it that way to avoid unnecessarily delaying the startup of the app.

If you want to instantiate the static property at some particular point in your app (rather than deferring it to where it's first used), simply reference this static property at that earlier point and the object will be initialized at that time. Given the efforts we put into reducing the latency in starting our apps, you generally wouldn't to want this synchronously during the initial launch of the app, but you can do it wherever you want.


Note that unlike lazy instance properties, the instantiating of globals and static variables is thread-safe.

Static variables in protocols

A static variable belongs to the class, not the instance. You can refer to the class by calling dynamicType:

print(a.dynamicType.x)

Why doesn't Swift use getter and setter for properties as much as Java or C#?

Java historically had different syntax for direct property access versus a method call that gets the value (a "getter"). Since you might someday want to override a property with a method, for consistency it is common to create a method in all cases.

Swift avoids this problem by having the same syntax for direct property access and "getters" (computed properties). This means you can change your mind later without impacting callers, and so there is no reason to create a method "just in case."

A computed property is defined as one with a "getter" (a get method) in Swift.

Singelton and Static variable access in objective C

Solution:

Using the method + (void)initialize : Apple Documentation
And we declare the variable as static

City.m

#import "City.h"

static NSArray *cities = nil;

@implementation City

+ (void)initialize
{
if (self == [City self])
{
if (!cities)
{
cities = [self loadCitiesFromPlist];
}
}
}

-(id) initWithDictionary: (NSDictionary *) dictionay
{
self = [super init];
// convert the NSDictionary to City object
return self;
}


// This method load all cities from the file

+(NSArray *) loadCitiesFromPlist
{
NSLog(@"loadCitiesFromPlist");

NSMutableArray *citiesArray = [NSMutableArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"cities" ofType:@"plist"]];

NSMutableArray *citiesObjectsArray = [[NSMutableArray alloc] init];

for (NSDictionary *cityDict in citiesArray)
{
[ citiesObjectsArray addObject: [[City alloc]initWithDictionary:cityDict]] ;
}

return citiesObjectsArray;
}

+(City*) getCityById:(NSString *) cityId
{

NSUInteger barIndex = [cities indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {

City* city = (City*) obj;

if(city.cityId == [cityId intValue])
{
*stop = YES;
return YES;
}
return NO;
}];


if (barIndex != NSNotFound)
{
return [cities objectAtIndex:barIndex];
}
else
{
return nil;
}

}

+(NSArray*) getAllCitiesNames
{
NSMutableArray *citiesNames =[[NSMutableArray alloc] init];

for(City* city in cities)
{
[citiesNames addObject:city.name];
}

return citiesNames;
}

@end

More Information in this SO answer

Execute a method when a variable value changes in Swift

You can simply use a property observer for the variable, labelChange, and call the function that you want to call inside didSet (or willSet if you want to call it before it has been set):

class SharingManager {
var labelChange: String = Model().callElements() {
didSet {
updateLabel()
}
}
static let sharedInstance = SharingManager()
}

This is explained in Property Observers.

I'm not sure why this didn't work when you tried it, but if you are having trouble because the function you are trying to call (updateLabel) is in a different class, you could add a variable in the SharingManager class to store the function to call when didSet has been called, which you would set to updateLabel in this case.



Edited:

So if you want to edit a label from the ViewController, you would want to have that updateLabel() function in the ViewController class to update the label, but store that function in the singleton class so it can know which function to call:

class SharingManager {
static let sharedInstance = SharingManager()
var updateLabel: (() -> Void)?
var labelChange: String = Model().callElements() {
didSet {
updateLabel?()
}
}
}

and then set it in whichever class that you have the function that you want to be called, like (assuming updateLabel is the function that you want to call):

SharingManager.sharedInstance.updateLabel = updateLabel

Of course, you will want to make sure that the view controller that is responsible for that function still exists, so the singleton class can call the function.

If you need to call different functions depending on which view controller is visible, you might want to consider Key-Value Observing to get notifications whenever the value for certain variables change.

Also, you never want to initialize a view controller like that and then immediately set the IBOutlets of the view controller, since IBOutlets don't get initialized until the its view actually get loaded. You need to use an existing view controller object in some way.

Hope this helps.

Create a Setter Only in Swift

Well if I really have to, I would use this.

Swift compiler supports some attributes on getters, so you can use @available(*, unavailable):

public subscript(index: Int) -> T {
@available(*, unavailable)
get {
fatalError("You cannot read from this object.")
}
set(v) {
}
}

This will clearly deliver your intention to the code users.



Related Topics



Leave a reply



Submit