Swift: cannot convert value of type to @noescape while call indexOf, after filter using filter function

Dima Deplov picture Dima Deplov · Oct 30, 2015 · Viewed 11.1k times · Source

In Swift 2, I'm receiving an error:

Cannot convert value of type '[String:AnyObject]' to expected argument type '@noescape ([String:AnyObject])' throws -> Bool"

//today = NSDate()
//array : [[String:AnyObject]]
// I use if let because of we might now get element in the array matching our condition

if let elementOfArray = array.filter({$0["id"] as? Int == anotherDictionary["matchID"] as? Int && ($0["nextFireDate"] as? NSDate)?.compare(today) == NSComparisonResult.OrderedAscending}).first {

   let index = array.indexOf(elementOfArray) // error here
}

What I'm doing wrong? I can't understand. :/

My aim, is to find index of that item, I think that I open for alternative solutions, but of course this one is preferred, because I think this is the "right way".

Answer

nhgrif picture nhgrif · Oct 30, 2015

The indexOf method on Swift arrays does not take an object of a type matching the array's type. Instead, it takes a closure. That closure takes an element of the array's type and returns a bool.

So, in fact, we don't (and shouldn't) even bother with the filter call unless we actually need the resultant array. If we're just looking for the first object that passes whatever test you are filtering for... well we just pass that exact same test to indexOf.

So, to keep things simple, if we have an array of strings (and let's say they're all single letter strings with lots of repetition), and I want to find the first index of the string "a", rather than filtering the array down to strings that are "a", then finding the first string that passed that test with the first method, and then finding the index of that exact object, instead, I just pass that test into the indexOf method:

let letters: [String] = ["x", "y", "z", "a", "b", "c"]

let index = letters.indexOf {
    $0 == "a"
}

For clarity, it appears that simply passing an individual element and looking for that does work in some cases. It probably relies on conformance to Swift's Equatable protocol. I could for example have simplied used letters.indexOf("a") here. The compiler would have been happy. But obviously, not every array is composed required to hold things that conform to Equatable, and the array can't make assumptions about how to compare its elements then. In these cases, you will have to use the above example of passing a closure. It's probably worth noting that passing this closure to indexOf rather than first filtering and then calling indexOf is going to be egregiously more efficient anyway, even if your array allows the letters.indexOf("a") approach. If for example, I had more complex strings, and I just wanted the first string that started with the letter 'a', this would be far, far more efficient than starting by filtering down the original array to an array of strings starting with 'a'.