Whither Dispatch_Once in Swift 3

Whither dispatch_once in Swift 3?

Since Swift 1.x, Swift has been using dispatch_once behind the scenes to perform thread-safe lazy initialization of global variables and static properties.

So the static var above was already using dispatch_once, which makes it sort of weird (and possibly problematic to use it again as a token for another dispatch_once. In fact there's really no safe way to use dispatch_once without this kind of recursion, so they got rid of it. Instead, just use the language features built on it:

// global constant: SomeClass initializer gets called lazily, only on first use
let foo = SomeClass()

// global var, same thing happens here
// even though the "initializer" is an immediately invoked closure
var bar: SomeClass = {
let b = SomeClass()
b.someProperty = "whatever"
b.doSomeStuff()
return b
}()

// ditto for static properties in classes/structures/enums
class MyClass {
static let singleton = MyClass()
init() {
print("foo")
}
}

So that's all great if you've been using dispatch_once for one-time initialization that results in some value -- you can just make that value the global variable or static property you're initializing.

But what if you're using dispatch_once to do work that doesn't necessarily have a result? You can still do that with a global variable or static property: just make that variable's type Void:

let justAOneTimeThing: () = {
print("Not coming back here.")
}()

And if accessing a global variable or static property to perform one-time work just doesn't feel right to you -- say, you want your clients to call an "initialize me" function before they work with your library -- just wrap that access in a function:

func doTheOneTimeThing() {
justAOneTimeThing
}

See the migration guide for more.

dispatch_once after the Swift 3 GCD API changes

From the doc:

Dispatch

The free function dispatch_once is no longer available in
Swift. In Swift, you can use lazily initialized globals or static
properties and get the same thread-safety and called-once guarantees
as dispatch_once provided. Example:

let myGlobal: () = { … global contains initialization in a call to a closure … }()
_ = myGlobal // using myGlobal will invoke the initialization code only the first time it is used.

dispatch_once_t alternative in swift

In swift singleton can be written as,

class Model: NSObject {
static let sharedInstance = Model()
}

then use Model.sharedInstance. you dont need dispatch once like in objective c.

source https://thatthinginswift.com/singletons/

How do I dispatch_sync, dispatch_async, dispatch_after, etc in Swift 3, Swift 4, and beyond?

Since the beginning, Swift has provided some facilities for making ObjC and C more Swifty, adding more with each version. Now, in Swift 3, the new "import as member" feature lets frameworks with certain styles of C API -- where you have a data type that works sort of like a class, and a bunch of global functions to work with it -- act more like Swift-native APIs. The data types import as Swift classes, their related global functions import as methods and properties on those classes, and some related things like sets of constants can become subtypes where appropriate.

In Xcode 8 / Swift 3 beta, Apple has applied this feature (along with a few others) to make the Dispatch framework much more Swifty. (And Core Graphics, too.) If you've been following the Swift open-source efforts, this isn't news, but now is the first time it's part of Xcode.

Your first step on moving any project to Swift 3 should be to open it in Xcode 8 and choose Edit > Convert > To Current Swift Syntax... in the menu. This will apply (with your review and approval) all of the changes at once needed for all the renamed APIs and other changes. (Often, a line of code is affected by more than one of these changes at once, so responding to error fix-its individually might not handle everything right.)

The result is that the common pattern for bouncing work to the background and back now looks like this:

// Move to a background thread to do some long running work
DispatchQueue.global(qos: .userInitiated).async {
let image = self.loadOrGenerateAnImage()
// Bounce back to the main thread to update the UI
DispatchQueue.main.async {
self.imageView.image = image
}
}

Note we're using .userInitiated instead of one of the old DISPATCH_QUEUE_PRIORITY constants. Quality of Service (QoS) specifiers were introduced in OS X 10.10 / iOS 8.0, providing a clearer way for the system to prioritize work and deprecating the old priority specifiers. See Apple's docs on background work and energy efficiency for details.

By the way, if you're keeping your own queues to organize work, the way to get one now looks like this (notice that DispatchQueueAttributes is an OptionSet, so you use collection-style literals to combine options):

class Foo { 
let queue = DispatchQueue(label: "com.example.my-serial-queue",
attributes: [.serial, .qosUtility])
func doStuff() {
queue.async {
print("Hello World")
}
}
}

Using dispatch_after to do work later? That's a method on queues, too, and it takes a DispatchTime, which has operators for various numeric types so you can just add whole or fractional seconds:

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // in half a second...
print("Are we there yet?")
}

You can find your way around the new Dispatch API by opening its interface in Xcode 8 -- use Open Quickly to find the Dispatch module, or put a symbol (like DispatchQueue) in your Swift project/playground and command-click it, then brouse around the module from there. (You can find the Swift Dispatch API in Apple's spiffy new API Reference website and in-Xcode doc viewer, but it looks like the doc content from the C version hasn't moved into it just yet.)

See the Migration Guide for more tips.

Is it safe to reset a dispatch_once (no threading involved)

The dispatch_once singleton model is used to provide thread-safety. If you're positive that the singleton will only ever be accessed from the main UI thread (as it seems in this case) there's no substantial reason not to fall back to the older singleton model:

static NSDateFormatter* myFormatter;

+(NSDateFormatter*)mySingletonFormatter
{
if(!myFormatter)
{
myFormatter = [...];
}
return myFormatter;
}

+(void)resetMyFormatter
{
myFormatter = nil;
}

If you have to do both (reset in a multithreaded environment) the you can wrap the formatter creation and reset in @synchronized(self).

I would avoid modifying the private bits of dispatch_once_t since it's undocumented how it's used (although it's implied that resetting it to 0 will clear it, it's not documented.) To maintain thread safety, you would have to wrap the whole thing in semaphores anyway, so might as well just fall back to a known documented solution.

How to implement method swizzling swift 3.0?

First of all dispatch_once_t is unavailable in Swift 3.0.
You can choose from two alternatives:

  1. Global variable

  2. Static property of struct, enum or class

For more details, see that Whither dispatch_once in Swift 3

For different purposes you must use different implementation of swizzling

  • Swizzling CocoaTouch class, for example UIViewController;
  • Swizzling custom Swift class;

Swizzling CocoaTouch class

example swizzling viewWillAppear(_:) of UIViewController using global variable

private let swizzling: (UIViewController.Type) -> () = { viewController in

let originalSelector = #selector(viewController.viewWillAppear(_:))
let swizzledSelector = #selector(viewController.proj_viewWillAppear(animated:))

let originalMethod = class_getInstanceMethod(viewController, originalSelector)
let swizzledMethod = class_getInstanceMethod(viewController, swizzledSelector)

method_exchangeImplementations(originalMethod, swizzledMethod) }

extension UIViewController {

open override class func initialize() {
// make sure this isn't a subclass
guard self === UIViewController.self else { return }
swizzling(self)
}

// MARK: - Method Swizzling

func proj_viewWillAppear(animated: Bool) {
self.proj_viewWillAppear(animated: animated)

let viewControllerName = NSStringFromClass(type(of: self))
print("viewWillAppear: \(viewControllerName)")
}
}

Swizzling custom Swift class

To use method swizzling with your Swift classes there are two requirements that you must comply with (for more details):

  • The class containing the methods to be swizzled must extend NSObject
  • The methods you want to swizzle must have the dynamic attribute

And example swizzling method of custom Swift base class Person

class Person: NSObject {
var name = "Person"
dynamic func foo(_ bar: Bool) {
print("Person.foo")
}
}

class Programmer: Person {
override func foo(_ bar: Bool) {
super.foo(bar)
print("Programmer.foo")
}
}

private let swizzling: (Person.Type) -> () = { person in

let originalSelector = #selector(person.foo(_:))
let swizzledSelector = #selector(person.proj_foo(_:))

let originalMethod = class_getInstanceMethod(person, originalSelector)
let swizzledMethod = class_getInstanceMethod(person, swizzledSelector)

method_exchangeImplementations(originalMethod, swizzledMethod)
}

extension Person {

open override class func initialize() {
// make sure this isn't a subclass
guard self === Person.self else { return }
swizzling(self)
}

// MARK: - Method Swizzling

func proj_foo(_ bar: Bool) {
self.proj_foo(bar)

let className = NSStringFromClass(type(of: self))
print("class: \(className)")
}
}


Related Topics



Leave a reply



Submit