2

I'd like to implement such property, that it's value is available for reading only one time, and then the property should be set to nil.

I've implemented it in such way:

private var _readOnce: String?

var readOnce: String? {
    get {
        let value = _readOnce
        _readOnce = nil
        return value
        }
    set {
        _readOnce = newValue
    }
}

readOnce = "Andrej"

print("read once = \(readOnce)")  // prints read once = Optional("Andrej")\n"
print("read once = \(readOnce)")  // prints read once = nil\n"

But I'feel like using a separate property _readOnce is not the "swifty" / "most elegant" way to do it.

Does anyone know of a different way, that wouldn't require to use a separate property?

I can confirm that the above code works, it's only that I feel it could be more elegant with less lines to achieve the same behaviour.

Andrej
  • 7,266
  • 4
  • 38
  • 57
  • 1
    Curious...why do you want to do this? – mfaani Jul 07 '17 at 18:31
  • I thought I could use such approach to react to push notification and make sure I react only once. So I've been exploring this approach. But as it looks right now I'll have to take a different approach as some other things don't behave exactly as I was expecting. – Andrej Jul 07 '17 at 20:53
  • 1
    I'm don't know the exact issue of your problem. But I'm thinking of multiple other ways you could solve this 1. Just have boolean property *somewhere* 2. in iOS 10 the different delegate methods have made it easier to manage what you intend. That is if your notification has `content-available : 1` then your `application(_:didReceiveRemoteNotification:fetchCompletionHandler:)` will get called. If user taps on the notification `didreceivenotificationresponse` will get called. If user is in the app the `willPresentNotification` will get called. – mfaani Jul 07 '17 at 21:33
  • 1
    Tricky part is if you have a push notification that sends `alert`/`sound` but also has `content-available:1` meaning you will get 2 callbacks. For that should understand and separate your concerns. For **iOS9** managing the callbacks is much more difficult. See [here](https://stackoverflow.com/a/22085855/5175709). As for managing app state in regards to push notifications. See [here](https://stackoverflow.com/questions/26302770/ios-apns-best-effort-fallback) and [here](https://stackoverflow.com/questions/44442192/should-i-update-my-app-upon-receiving-payload-or-i-should-always-update-it-by-a) – mfaani Jul 08 '17 at 12:38

3 Answers3

2

I don't know that there's a way to avoid having a backing property, but what I'd probably do is to make a helper type to wrap up the behavior. Something like this:

struct OneTimeValue<T>
{
    private var isUnread = true

    private let value : T

    init(_ value: T)
    {
        self.value = value
    }

    func get() -> T?
    {
        guard isUnread else {
            return nil
        }
        self.isUnread = false
        return self.value
    }
}

You could also write this a little differently if you prefer, by nilling out value inside of get(), for example, but the general plan holds.

Then your class becomes:

class Miser
{
    var readOnce : String?
    {
        return self._readOnce.get()
    }
    private let _readOnce = OneTimeValue("Can't touch this (twice)")
}

I've also used this pattern for a UserDefaultsValue (storage to/from user defaults) and a SynchronizedValue (read-write lock on a property) and I think it works well.

jscs
  • 63,694
  • 13
  • 151
  • 195
1

As far as I know it is not possible without a second variable. This is because computed properties do not store any data for the variable they represent:

In addition to stored properties, classes, structures, and enumerations can define computed properties, which do not actually store a value.

For non-computed properties, the only observers you can have are based upon the setting of the variable, not the getting (i.e. willSet and didSet)

Hope that helps!

EDIT: It can be done with closures and property observers if you're careful: This requires no other variables (instead the value is captured by the closure), but it is rather unclear — I wouldn't recommend it.

var readOnce: () -> String? = {nil} {
    didSet{
        readOnce = { [weak self, readOnce] in
            self?.readOnce = {nil}
            return readOnce()
        }
    }
}

readOnce()        // returns nil
readOnce = {"Hi"} 
readOnce()        // returns optional wrapped "Hi"
readOnce()        // returns nil

A more 'Swifty' answer for you :D

  • `didSet` is after your first *set* not after your first *get* /read – mfaani Jul 07 '17 at 18:35
  • @Honey Yes, agreed — however this solution is based on the idea of altering the value to be a closure after `readOnce` is set. **This closure is then executed when the variable is read**, and readOnce reset to {nil} so that it cannot be read twice. The code above works, but again, due to the hackery involved, I wouldn't recommend. – kaz boddington Jul 07 '17 at 18:45
  • Sure, very interesting approach, learned something new. :) Also agree with you that it's kind of hackey and wouldn't use in project with other developers as it's not instantly clear what's going on. – Andrej Jul 07 '17 at 20:57
0

After Swift 5.1, We can use Property Wrapper

@propertyWrapper
struct ReturnAndFree<T> {
  private var value: T?
  init(wrappedValue: T?) {
    value = wrappedValue
  }
  
  var wrappedValue: T? {
    mutating get {
      defer { value = nil }
      return value
    }
    set {
      value = newValue
    }
  }
  
}
Learing
  • 559
  • 4
  • 13