Struct with an 'expiry date' - how to create elegantly?

I am trying to create a struct which contains a value along with an 'expiry time'. I want to use a struct rather than a class so it can be set as a @Published property in an Observable object. Here is the code:

struct MarineDatum {
	
	var value: Double? {
		didSet {
			isNew = true
			timer?.invalidate()
			timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false, block: { timer in self.makeOld()})
		}
	}
	var isNew: Bool
	var timer: Timer?

	init(value: Double) {
		self.value = value
		self.isNew = true
	}
	
	mutating func makeOld() {
		self.isNew = false
	}
}

This code does exactly what I want, were it to actually compile, but I get an 'Escaping closure captures mutating 'self' parameter' error in the timer block.

Can anyone think of a way around this without creating an external variable to represent the timer object?

This code does exactly what I want, were it to actually compile

So, it does not do it…

There are several issues:

  • first, as the error says, you capture a value of self. It cannot be changed anymore (in older versions of Swift, you could, but the changes were ignored…).
  • second, take care: when instance is created, didSet is not called. It will be called when you further update the value
  • What is the problem using a class ? You may just have to make it hashable.

This works:

class MarineDatum {
    
    var value: Double? {
        didSet {
            isNew = true
            timer?.invalidate()
            print("Start")
            timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false, block: { timer in self.isNew = false ; print("Done")})
        }
    }
    var isNew: Bool
    var timer: Timer?

    init(value: Double) {
        print("init")
        self.value = value
        self.isNew = true
    }
    
//    mutating func makeOld() {
//        self.isNew = false
//    }
}

let marineDatum = MarineDatum(value: 100)
marineDatum.value = 200

Done is printed 5 s after Start…

I am trying to create a struct which contains a value along with an 'expiry time'

That's straightforward. But it doesn't seem to be what you are actually doing. What you actually seem to have implemented is a struct (or class) containing a timer. Don't do that, just store the expiry time.

Then in your is_new getter, compare the expiry time with the current time.

(Do you have some other reason for wanting to store the timer in the object?)

I've read much more about Combine now, and I think it's not possible to put this functionality into the struct itself. However, it CAN be put into a view model like this:

class MarineDatumViewModel: ObservableObject {
	@Published var marineDatum: MarineDatum = MarineDatum(value: 0.0) {
		didSet {
			if marineDatum.isNew {
				DispatchQueue.main.asyncAfter(deadline: .now() + 2) { self.marineDatum.makeOld() }
			}
		}
	}
}

I still need a timer somewhere to trigger the 'is_new' getter (do you mean the didSet?).

No, I mean getter. Don't store the isNew boolean. Compute it in the getter by comparing the expiry time in the struct with the current time. No timer required.

Struct with an 'expiry date' - how to create elegantly?
 
 
Q