2
\$\begingroup\$

I'm currently working with an SDK where methods return an optional array of objects with optional properties. Only the array itself is optional, it will either be nil or always contain non-optionals. I'm struggling to find a cleaner way to unwrap optional properties for each non-optional in the array:

class Person {
    var id: String?
}

func someOptionalArrayOfPeople() -> [Person]? {
    // returns either nil or some array of Person objects
}

let people = someOptionalArrayOfPeople()

if let unwrappedPeople = people {

    // 1
    for person in unwrappedPeople {
        if let id = person.id {
            print(id) // too many nested statements
        }
    }

    // 2
    for person in unwrappedPeople where person.id != nil {
        print(person.id!) // stil force unwrapping
    }
}

For option 1, I'm not thrilled about unwrapping the initial optional array, and I feel that there should be a way to skip the unwrapping of the id. Option 2 attempts to do that with a where clause, but the compiler isn't smart enough to determine that person.id is non-nil even if we explicitly check that id != nil.

What are my "Swifty" options for unwrapping optional properties on an optional array of objects? I am using Swift 2.2 with Xcode 7.3.1.

\$\endgroup\$
4
\$\begingroup\$

There are several options, but in any case you can use

public func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]

which returns an array containing the non-nil results of applying the transformation to all sequence elements.

For example

if let unwrappedPeople = people {
    for id in unwrappedPeople.flatMap({ $0.id }) {
        print(id)
    }
}

to iterate over the non-nil ids, or

if let unwrappedPeople = people {
    let ids = unwrappedPeople.flatMap { $0.id }
    print(ids)
}

to get the non-nil ids as an array.

To get rid of the outer unwrapping/optional binding, you can use optional chaining:

people?.flatMap({ $0.id }).forEach {
    print($0)
}

If people is nil, then flatMap() is not applied. Otherwise it returns an array of the non-nil ids, which are enumerated with forEach().

I think that all these methods (as well as your first method) are "Swifty", you have to decide how compact your code should be.

\$\endgroup\$
5
  • \$\begingroup\$ Awesome, thanks Martin! Will give these a shot and follow up if I have any other questions. \$\endgroup\$ – JAL Jun 3 '16 at 21:51
  • \$\begingroup\$ Hey Martin, could I flatten the array of people based on id so that I could have an array of Person objects with a non-nil id? Sort of like your second answer, but where I $0 is a reference to the person, not the id. Thanks. \$\endgroup\$ – JAL Jun 6 '16 at 13:44
  • \$\begingroup\$ Would something like people?.filter({ $0.id != nil}) work? \$\endgroup\$ – JAL Jun 6 '16 at 13:50
  • \$\begingroup\$ @JAL: Sure. But note that the result is an (optional) array of Person, so you still have to unwrap the ids (even if you know that they are not nil). \$\endgroup\$ – Martin R Jun 6 '16 at 13:52
  • \$\begingroup\$ Yep, that's another downer. Wish I could strangle the devs who architected this API. A property such as id which should be on every object should not be an optional. But that's another issue. Thanks Martin, wish I could upvote you twice. \$\endgroup\$ – JAL Jun 6 '16 at 13:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.