0

I have the following function:

func saveCredential(_ credential: String, service: String, account: String) async throws {
  let query = [
    kSecValueData: credential.data(using: .utf8)!,
    kSecClass: kSecClassGenericPassword,
    kSecUseDataProtectionKeychain: true,
    kSecAttrSynchronizable: true,
    kSecAttrService: service,
    kSecAttrAccount: account,
  ] as CFDictionary
        
  let status = (Task {
    // Add data in query to keychain
    return SecItemAdd(query, nil)
  }).value
                
  switch status {
  case errSecSuccess:
    return;
  case errSecDuplicateItem:
    try updateCredential(credential, service: service, account: account)
  default:
    try throwStatus(status)
  }
}

While discussing whether this is the idiomatic way to do this code, someone in a discord server said that it would be cleaner to write this as:

func addKeychainItem(attributes attrs: CFDictionary) async -> (OSStatus, CFTypeRef?) {
  return await withUnsafeContinuation { continuation in
    var item: CFTypeRef?
    let result = SecItemAdd(attrs, &item)
    completion(result, item)
  }
}

Now, this code obviously doesn't do the same thing as the example above, because it has a bunch of stuff left out, but is there a functional difference between the two? An is one of them indeed considered more idiomatic?

bigblind
  • 12,539
  • 14
  • 68
  • 123
  • The patterns are not comparable. A `continuation` is to bridge an **asynchronous** completion handler to async/await. A Task bridges a **synchronous** context to async/await to be able to call `async` APIs. Your example is not very meaningful anyway because there is no `async` API in the body, `SecItemAdd` is synchronous, and it doesn't have a completion handler either. – vadian Dec 30 '22 at 19:10
  • Watch [meet async await](https://developer.apple.com/wwdc21/10132) both samples have serious issues. They are “using” async await but not really using it. The “cleaner” version is just nonsense because you create the continuation but just bypass it. – lorem ipsum Dec 30 '22 at 19:24
  • ah right, the "cleaner" version doesn't really run the code on a separate thread, as I understand it now. All I'm trying to do is run that bit of code on a separate thread, to make the function async. – bigblind Dec 30 '22 at 19:31
  • Async await/concurrency doesn’t deal with threads, It deals with actors and putting the async keyword or using Task does not guarantee that the operation with leave the main thread or main actor. The only way to force something into a different actor is to “detach” it. – lorem ipsum Dec 30 '22 at 19:37
  • Ok sure, I guess async/await abstracts away where the code runs, but I'm at least hoping that it'll prevent the operation from blocking the main thread when it could cause UI stutter. I'm guessing that, as with goroutines in Go and lightweight concurrency mechanisms in other languages, Apple has some smarts under the hood to determine where to run the code most efficiently (including avoiding blocking other work) – bigblind Dec 30 '22 at 19:42
  • It will not be guaranteed to leave the Main thread or actor unless you detach. You can [check your thread](https://stackoverflow.com/questions/39307800/how-to-check-current-thread-in-swift-3#39308150) if you would like inside the task. It isn’t as smart as you would think. – lorem ipsum Dec 30 '22 at 19:44
  • Right, so I guess I'm not as interested in the guarantees about threads, as I am in turning a piece of blocking code, into a piece of code that can be efficiently used in a codebase that uses async/await. – bigblind Dec 30 '22 at 19:46
  • Here is a sample of how to do it. It’s based on SwiftUI but the core code will work for either framework https://stackoverflow.com/questions/73538764/trouble-running-async-functions-in-background-threads-concurrency/73542522#73542522 but you can put that code in a swift file and see that without detached the UI gets blocked – lorem ipsum Dec 30 '22 at 19:49

0 Answers0