Dispatch_Once After the Swift 3 Gcd API Changes

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.

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.

How to handle many API calls with Swift 3 GCD

Well, I think I got this covered! I decided to climb out of the rabbit hole a ways and simplify things. I wrote my own session instead of relying on the wrapper, and set up semaphores in it, threw it in an OperationQueue and it seems to be working perfectly.

This was the video I followed to set up my simplified semaphores request. https://www.youtube.com/watch?v=j4k8sN8WdaM

I'll have to tweak the below code to be a PUT instead of the GET I've been using for testing, but that part is easy.

//print (row[0])
let myOpQueue = OperationQueue()
myOpQueue.maxConcurrentOperationCount = 3
let semaphore = DispatchSemaphore(value: 0)
var i = 0
while i < 10 {
let myURL = NSURL(string: "https://my.server.com/APIResources/computers/id/\(i)")
myOpQueue.addOperation {

let request = NSMutableURLRequest(url: myURL! as URL)
request.httpMethod = "GET"
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = ["Authorization" : "Basic 123456789ABCDEFG=", "Content-Type" : "text/xml", "Accept" : "text/xml"]
let session = Foundation.URLSession(configuration: configuration)
let task = session.dataTask(with: request as URLRequest, completionHandler: {
(data, response, error) -> Void in
if let httpResponse = response as? HTTPURLResponse {
print(httpResponse.statusCode)
semaphore.signal()
self.lblLine.stringValue = "\(i)"
self.appendLogString(stringToAppend: "\(httpResponse.statusCode)")
print(myURL!)

}
if error == nil {
print("No Errors")
print("")
} else {
print(error!)
}
})

task.resume()
semaphore.wait()

}
i += 1
}

Swift GCD Work Item Dispatch queue cancelling

You need to declare DispatchWorkItem variable at the top of class. In your code work1 variable is becoming inaccessible as soon as compiler is out of function. Every time you change your switch a new DispatchWorkItem variable initialize. Please go through below example for correct use of DispatchWorkItem with stop/resume functionality

    @IBOutlet weak var button: UIButton!
@IBOutlet weak var label: UILabel!
var isStart = false
var work: DispatchWorkItem!

private func getCurrentTime() -> DispatchWorkItem {

work = DispatchWorkItem { [weak self] in

while true {
if (self?.work.isCancelled)!{break}
let date = Date()
let component = Calendar.current.dateComponents([.second], from: date)
DispatchQueue.main.async {
self?.label.text = "\(component.second!)"
}

}

}

return work
}

@IBAction func btnPressed() {

isStart = !isStart
button.setTitle(isStart ? "Stop" : "Start", for: .normal)

let workItem = getCurrentTime()
let globalQueue = DispatchQueue.global(qos: .background)
if isStart {
globalQueue.async(execute: workItem)
} else {
workItem.cancel()
}

}

This example will display current time seconds value in label. I have used while true, just to show an example.

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.

Is there any shorter alternative to dispatch_once?

I just made a small macro which basically lets you write quite short stuff

+ (instancetype)sharedInstance
{
return dispatch_once_and_return(id, [self new]);
}

Also blocks is supported with this semantic

+ (NSString *)altRFC2822StringFromDate:(NSDate *)date
{
NSDateFormatter *formatter = dispatch_once_and_return(NSDateFormatter *, ^{
NSDateFormatter *f = [NSDateFormatter new];
// setup formatter
return f;
}());

return [formatter stringFromDate:date];
}

(The trick is to add () after the block, which basically executes the block right away).

The macro

#define dispatch_once_and_return(type, value) ({\
static type cachedValue;\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
cachedValue = value;\
});\
cachedValue;\
})


Related Topics



Leave a reply



Submit