2

I would like to initialize a variable obj by taking it from UserDefaults, which returns a String?, and if it's nil build the value and assign it.

The following code works, but, at the end, my obj is a String? while I want it to be a String (since it can't be nil at this stage).

var obj = UserDefaults.standard.string(forKey: "my_key")// Here, obj is a String?
if obj == nil {
    obj =  ProcessInfo.processInfo.globallyUniqueString// Returns, a String
    defaults.set(obj, forKey: "my_key")
    defaults.synchronize()
}
// Here, obj is still a String?

Is there a good pattern / best practice for this kind of situation ?

Matusalem Marques
  • 2,399
  • 2
  • 18
  • 28
Drico
  • 1,284
  • 15
  • 33
  • 1
    FYI - there is [no need to call `synchronize`](https://stackoverflow.com/questions/40808072/when-and-why-should-you-use-nsuserdefaultss-synchronize-method). – rmaddy Oct 25 '17 at 16:29

4 Answers4

7

You can use the nil-coalescing operator ?? with an "immediately evaluated closure":

let obj = UserDefaults.standard.string(forKey: "my_key") ?? {
    let obj = ProcessInfo.processInfo.globallyUniqueString
    UserDefaults.standard.set(obj, forKey: "my_key")
    return obj
}()

print(obj) // Type is `String`

If the user default is not set, the closure is executed. The closure creates and sets the user default (using a local obj variable) and returns it to the caller, so that it is assigned to the outer obj variable.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
3

Optional Binding. You can read up on it here

let obj: String

if let string = UserDefaults.standard.string(forKey: "my_key") {
    obj = string
} else {
    obj = ProcessInfo.processInfo.globallyUniqueString
    UserDefaults.standard.set(obj, forKey: "my_key")
}

print(obj)
Hodson
  • 3,438
  • 1
  • 23
  • 51
1

You can also use an implicitly unwrapped optional, like this:

var obj: String! = UserDefaults.standard.string(forKey: "my_key")// Here, obj is a (possibly `nil`) `String!`
if obj == nil {
    obj =  ProcessInfo.processInfo.globallyUniqueString // Returns, a String
    defaults.set(obj, forKey: "my_key")
    defaults.synchronize()
}

// Here, obj is a non-`nil` `String!`, which will work the same as a `String`
Matusalem Marques
  • 2,399
  • 2
  • 18
  • 28
0

Use either guard let or if let.

1) guard let (not common for your case, though)

guard let obj = UserDefaults.standard.string(forKey: "my_key") else { 
    // guard failed - obj is nil; perform something and return
    obj =  ProcessInfo.processInfo.globallyUniqueString
    defaults.set(obj, forKey: "my_key")
    return 
}

// obj is not nil, you have it at your disposal
print(obj)

2) if let

if let obj = UserDefaults.standard.string(forKey: "my_key") {
   // obj exists
} else {
    let obj =  ProcessInfo.processInfo.globallyUniqueString
    defaults.set(obj, forKey: "my_key")
    print(obj)
} 

(!) Also, there really is no more need to call defaults.synchronize():

Waits for any pending asynchronous updates to the defaults database and returns; this method is unnecessary and shouldn't be used.

https://developer.apple.com/documentation/foundation/nsuserdefaults/1414005-synchronize

Hexfire
  • 5,945
  • 8
  • 32
  • 42
  • 1
    That `if let` example won't work as you're trying to assign a value to `obj` which is a let constant. Also, it tries to overwrite the value returned from `UserDefaults` which is the opposite of what is requested – Hodson Oct 25 '17 at 14:46
  • You are right. Wasn't attentive enough. Will correct. – Hexfire Oct 25 '17 at 14:47