flanker
flanker

Reputation: 4200

Swift 4 Closure: failing to access another local property from within the closure

I've been trying to do something in Swift that I thought should be relatively straightforward - store a closure as a property in a struct so it can be used to filter an array by using another property of the struct. However I've come up against a block - I can't add the closure in struct initialiser as the variable it wants to reference doesn't exist, I can't make it a lazy variable as its part of a protocol (not sure if this would work anyway), and I can't assign it after the struct has been initialised as it will then capture the context of the object where eI make the assignment from and try to use that for the local property. A simplified scenario is below:

struct EventSpec {
    var dateRange: DateInterval
    var filterClosure: ((Date) -> Bool)?

    init( dateRange: DateInterval) {
        self.dateRange = dateRange
    }

    func provideMatchingEvents(for events: [Event] {
        return events.filters{filterClosure($0.date)}
}

struct Event {
    name: String
    date: Date
}

ideally would want to init like

eventSpec = EventSpec(dateRange: aDateRamge, filterClosure: { date in self.dateRange.contains(date)}

but that doesn't work as there isn't a self.dateRange at this point.

Trying to add the closure afterwards, either directly or by a 'builder' function doesn't work either as it captures the 'self' of where it was being involved from, rather than accessing the EventSpec's dateRange property.

I'm sure this should be a common pattern, and I' missing something obvious, but can only find references (lots of them) to adding closures as variables that don't reference other properties.

(I realise if I hard-coded the closure within the struct I could access the local variables through a standard capture list, but this removes the ability to define a filter at run-time).

Anyone any ideas?

Upvotes: 0

Views: 508

Answers (1)

Fogmeister
Fogmeister

Reputation: 77641

The scope of the code that is inside the closure is in the object that it is written in. Not the object it is passed into.

So in...

EventSpec(dateRange: aDateRamge) { date in
    self.dateRange.contains(date)
}

self is whatever the place is that is creating the EventSpec object.

What you can do is capture the dateRange in the closure itself.

So you could redefine you EventSpec like so...

struct EventSpec {
    var filterClosure: ((Date) -> Bool)?

    func provideMatchingEvents(for events: [Event]) {
        return events.filters{ filterClosure($0.date) }
    }
}

And then create it like...

let dateRange = //some date range that you have already got
let eventSpec = EventSpec(filterClosure: { dateRange.contains($0) })
// in this line the dateRange is captured by the closure so you don't need to capture it as a separate property

This would do what you are trying to do I think.

Upvotes: 1

Related Questions