Removal of dispatch_once() in Swift 3?


(William Shipley) #1

I may be missing something, but I don’t understand how to get the behavior of dispatch_once() without a bunch more code in cases in which I was using it to initialize “lazy-ish" instance variables.

public class PlatonicPieceOfFurniture {

    internal var modelDirectoryURL: URL

    /* … */

    public var floorGeometryAndMaterial: FloorPlatonicGeometryAndMaterial {
        dispatch_once(&dispatchOnceLoadGeometriesAndMaterials) {
            self.floorGeometryAndMaterialBacking = try! FloorPlatonicGeometryAndMaterial(modelDirectory: self.modelDirectoryURL)
  }
        return floorGeometryAndMaterialBacking!
    }
    private var floorGeometryAndMaterialBacking: FloorPlatonicGeometryAndMaterial?
    private var dispatchOnceLoadGeometriesAndMaterials: dispatch_once_t = 0

}

Note that ‘floorGeometryAndMaterial' isn’t a global, and that it requires ‘modelDirectoryURL’ as input when it is lazily initialized, so I can't just do:

    public lazy var floorGeometryAndMaterial: FloorPlatonicGeometryAndMaterial? = try! FloorPlatonicGeometryAndMaterial(modelDirectory: modelDirectoryURL)

Because that throws a “instance member ‘modelDirectoryURL’ cannot be used on type ‘PlatonicPieceOfFurniture’” error in Xcode. (Also, honestly, I don’t see much utility in lazy instance variables in Swift, since I can’t use any state from the current instance to initialize them, so they’re usually no better than static variables — I don’t think I’ve ever used them, despite trying a bunch.)

Also this needs to be thread-safe, and loading the backing is very slow so we really don’t want to ever accidentally do it twice (although it would be safe to do so in my code), so I can’t just check if ‘floorGeometryAndMaterialBacking’ is nil and load it up if it is.

I could do an ugly version of this with semaphores and an extra flag, but it seems a lot cleaner with dispatch_once().

Am I missing something obvious? The Swift 3 converter completely mangled my code into a static case which didn’t compile at all and couldn’t possibly work.

-Wil Shipley
Delicious Monster


(Charlie Monroe) #2

Hard to say without full code, but the following code compiles just fine in Xcode 8:

class FloorPlatonicGeometryAndMaterial {
  init(modelDirectory: NSURL) throws {
    /// ...
  }
}

class PlatonicPieceOfFurniture {
  internal var modelDirectoryURL: URL
  
  var floorGeometryAndMaterial: FloorPlatonicGeometryAndMaterial {
    return self.floorGeometryAndMaterialBacking!
  }
  
  private lazy var floorGeometryAndMaterialBacking: FloorPlatonicGeometryAndMaterial? =
        try! FloorPlatonicGeometryAndMaterial(modelDirectory: self.modelDirectoryURL)
  
  init(modelDirectoryURL: URL) {
    self.modelDirectoryURL = modelDirectoryURL
    /// ...
  }
}

All lazy initialization pretty much uses dispatch_once. Also remember, that your code can be as followed:

private lazy var stringValue: String? = {
  var str = self.description
  str += "\n"
  ...
  return str
}()

lazy var initialization doesn't have to be a one-liner, but can be an applied closure.

···

On Jun 16, 2016, at 7:48 PM, William Shipley via swift-evolution <swift-evolution@swift.org> wrote:

I may be missing something, but I don’t understand how to get the behavior of dispatch_once() without a bunch more code in cases in which I was using it to initialize “lazy-ish" instance variables.

public class PlatonicPieceOfFurniture {

    internal var modelDirectoryURL: URL

    /* … */

    public var floorGeometryAndMaterial: FloorPlatonicGeometryAndMaterial {
        dispatch_once(&dispatchOnceLoadGeometriesAndMaterials) {
            self.floorGeometryAndMaterialBacking = try! FloorPlatonicGeometryAndMaterial(modelDirectory: self.modelDirectoryURL)
  }
        return floorGeometryAndMaterialBacking!
    }
    private var floorGeometryAndMaterialBacking: FloorPlatonicGeometryAndMaterial?
    private var dispatchOnceLoadGeometriesAndMaterials: dispatch_once_t = 0

}

Note that ‘floorGeometryAndMaterial' isn’t a global, and that it requires ‘modelDirectoryURL’ as input when it is lazily initialized, so I can't just do:

    public lazy var floorGeometryAndMaterial: FloorPlatonicGeometryAndMaterial? = try! FloorPlatonicGeometryAndMaterial(modelDirectory: modelDirectoryURL)

Because that throws a “instance member ‘modelDirectoryURL’ cannot be used on type ‘PlatonicPieceOfFurniture’” error in Xcode. (Also, honestly, I don’t see much utility in lazy instance variables in Swift, since I can’t use any state from the current instance to initialize them, so they’re usually no better than static variables — I don’t think I’ve ever used them, despite trying a bunch.)

Also this needs to be thread-safe, and loading the backing is very slow so we really don’t want to ever accidentally do it twice (although it would be safe to do so in my code), so I can’t just check if ‘floorGeometryAndMaterialBacking’ is nil and load it up if it is.

I could do an ugly version of this with semaphores and an extra flag, but it seems a lot cleaner with dispatch_once().

Am I missing something obvious? The Swift 3 converter completely mangled my code into a static case which didn’t compile at all and couldn’t possibly work.

-Wil Shipley
Delicious Monster
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Ben Rimmington) #3

William Shipley wrote:

I may be missing something, but I don’t understand how to get the behavior
of dispatch_once() without a bunch more code in cases in which I was using
it to initialize “lazy-ish" instance variables.

In Objective-C, the `dispatch_once_t` predicate *must* be a static or global
variable (which is automatically initialized to zero).

<https://developer.apple.com/reference/dispatch/1447169-dispatch_once>

In any case, `dispatch_once` and `pthread_once` are not available. See the
"Interacting with C APIs" chapter in the pre-release "Using Swift with Cocoa
and Objective-C (Swift 3)".

<https://developer.apple.com/swift/resources/>

Note that "lazy stored properties" (i.e. `lazy var` instance variables)
are *not* safe to access by multiple threads simultaneously.

-- Ben


(Michael Peternell) #4

I've used something that is very similar to dispatch_once in Swift 2.2. It looks like this (taken from real code in a real application):

public static let defaultMap: RAStaticMap = RAStaticMap.loadCountries()

The function RAStaticMap.loadCountries() is actually private, and it is called exactly once. This is a feature of the "static let". The accessor is threadsafe. I'm not sure if this is documented behavior or if it is merely an undocumented feature of static let's.

This works at least for the common use case that you want to create some value exactly once, e.g. for a singleton "+sharedInstance" initializer.

-Michael

···

Am 16.06.2016 um 19:48 schrieb William Shipley via swift-evolution <swift-evolution@swift.org>:

I may be missing something, but I don’t understand how to get the behavior of dispatch_once() without a bunch more code in cases in which I was using it to initialize “lazy-ish" instance variables.

public class PlatonicPieceOfFurniture {

    internal var modelDirectoryURL: URL

    /* … */

    public var floorGeometryAndMaterial: FloorPlatonicGeometryAndMaterial {
        dispatch_once(&dispatchOnceLoadGeometriesAndMaterials) {
            self.floorGeometryAndMaterialBacking = try! FloorPlatonicGeometryAndMaterial(modelDirectory: self.modelDirectoryURL)
  }
        return floorGeometryAndMaterialBacking!
    }
    private var floorGeometryAndMaterialBacking: FloorPlatonicGeometryAndMaterial?
    private var dispatchOnceLoadGeometriesAndMaterials: dispatch_once_t = 0

}

Note that ‘floorGeometryAndMaterial' isn’t a global, and that it requires ‘modelDirectoryURL’ as input when it is lazily initialized, so I can't just do:

    public lazy var floorGeometryAndMaterial: FloorPlatonicGeometryAndMaterial? = try! FloorPlatonicGeometryAndMaterial(modelDirectory: modelDirectoryURL)

Because that throws a “instance member ‘modelDirectoryURL’ cannot be used on type ‘PlatonicPieceOfFurniture’” error in Xcode. (Also, honestly, I don’t see much utility in lazy instance variables in Swift, since I can’t use any state from the current instance to initialize them, so they’re usually no better than static variables — I don’t think I’ve ever used them, despite trying a bunch.)

Also this needs to be thread-safe, and loading the backing is very slow so we really don’t want to ever accidentally do it twice (although it would be safe to do so in my code), so I can’t just check if ‘floorGeometryAndMaterialBacking’ is nil and load it up if it is.

I could do an ugly version of this with semaphores and an extra flag, but it seems a lot cleaner with dispatch_once().

Am I missing something obvious? The Swift 3 converter completely mangled my code into a static case which didn’t compile at all and couldn’t possibly work.

-Wil Shipley
Delicious Monster
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(William Shipley) #5

  private lazy var floorGeometryAndMaterialBacking: FloorPlatonicGeometryAndMaterial? =
        try! FloorPlatonicGeometryAndMaterial(modelDirectory: self.modelDirectoryURL)

Oddly, the “self.” made all the difference in getting it to compile. I’m not clear why in this case, I guess there’s some part of the language I didn’t learn right. Thanks.

-W


(Charlie Monroe) #6

Yes, another headache that would be solved by the explicit self!

The issue here is often (in my experience) that if the closure/one-liner defining the default lazy value contains an error, the compiler won't emit an error about the actual cause but will compain very generically it can't assign.

You might consider reporting it on bugs.swift.org.

···

On Jun 16, 2016, at 11:30 PM, William Shipley <wjs@mac.com> wrote:

  private lazy var floorGeometryAndMaterialBacking: FloorPlatonicGeometryAndMaterial? =
        try! FloorPlatonicGeometryAndMaterial(modelDirectory: self.modelDirectoryURL)

Oddly, the “self.” made all the difference in getting it to compile. I’m not clear why in this case, I guess there’s some part of the language I didn’t learn right. Thanks.


(Joe Groff) #7

`lazy var` does *not* use dispatch_once, so it isn't thread-safe without synchronization. Only global and static properties are initialized with a dispatch_once-like mechanism.

-Joe

···

On Jun 16, 2016, at 11:32 AM, Charlie Monroe via swift-evolution <swift-evolution@swift.org> wrote:

Hard to say without full code, but the following code compiles just fine in Xcode 8:

class FloorPlatonicGeometryAndMaterial {
  init(modelDirectory: NSURL) throws {
    /// ...
  }
}

class PlatonicPieceOfFurniture {
  internal var modelDirectoryURL: URL
  
  var floorGeometryAndMaterial: FloorPlatonicGeometryAndMaterial {
    return self.floorGeometryAndMaterialBacking!
  }
  
  private lazy var floorGeometryAndMaterialBacking: FloorPlatonicGeometryAndMaterial? =
        try! FloorPlatonicGeometryAndMaterial(modelDirectory: self.modelDirectoryURL)
  
  init(modelDirectoryURL: URL) {
    self.modelDirectoryURL = modelDirectoryURL
    /// ...
  }
}

All lazy initialization pretty much uses dispatch_once. Also remember, that your code can be as followed:

private lazy var stringValue: String? = {
  var str = self.description
  str += "\n"
  ...
  return str
}()

lazy var initialization doesn't have to be a one-liner, but can be an applied closure.


(Ben Rimmington) #8

Ben Rimmington wrote:

William Shipley wrote:

> I may be missing something, but I don’t understand how to get the behavior
> of dispatch_once() without a bunch more code in cases in which I was using
> it to initialize “lazy-ish" instance variables.

In Objective-C, the `dispatch_once_t` predicate *must* be a static or global
variable (which is automatically initialized to zero).

Greg Parker on Stack Overflow:

<http://stackoverflow.com/a/19845164>

-- Ben