Search code examples
iosswiftcore-datansmanagedobjectnsset

I'm looking for a shorter/better way to get the first object from and NSSet in my Core Data app


Currently I have this code :

if let myPhoneNumbers = person.phoneNumbers?.allObjects as? [PhoneNumber] {
        for myPhoneNumber in myPhoneNumbers {
        mainPhoneNumber = myPhoneNumber.number
        break
    }
}

Is this the correct way of coding this. I know that NSSet is unordered and probably is the reason it does not have its own implementation of .first such as:

person.phoneNumbers.first

But I'm thinking my code is not the most elegant code and maybe not proper Swift code.


Solution

  • In your code, myPhoneNumbers is an array, so you could use myPhoneNumbers.first instead of looping.

    if let myPhoneNumbers = person.phoneNumbers?.allObjects as? [PhoneNumber] {
        mainPhoneNumber = myPhoneNumbers.first
    }
    

    However, as has been pointed out, you should impose some sort of order on your set, so that the result of executing this code is the same for any given set. Arbitrary results are usually bad.

    Since you mentioned you are using Core Data, I suggest adding a convenience property on your Person entity to return the phone numbers as a sorted array.

    If your entities look something like this...

    class PhoneNumber: NSManagedObject {
        @NSManaged var number: String
    }
    
    class PersonEntity: NSManagedObject {
        @NSManaged var phoneNumbers: NSSet?
    }
    

    ...you could create a convenience property like this:

    class PersonEntity: NSManagedObject {
    
        @NSManaged var phoneNumbers: NSSet?
    
        // Arrays are often easier to work with than sets, 
        //  so provide a convenience property that returns
        //  phone numbers sorted
        var phoneNumbersArray: [PhoneNumber] {
            get {
                if let phoneNumbers = phoneNumbers {
                    return (phoneNumbers.allObjects as! [PhoneNumber]).sorted() {
                        // here, sorting by phone number, but
                        // you can sort on whatever you want
                        $1.number > $0.number
                    }
                }
                // return empty array if set is nil
                return [PhoneNumber]()
            }
        }
    }
    

    Finally, get the first number with one line of code like this:

    mainPhoneNumber = person.phoneNumbersArray.first
    

    I like to add this type of convenience array property to all my entities that have sets. Arrays are easier to work with, in my opinion. Here I chose to return an empty array instead of a nil array, when the set is nil. This works if you are OK with treating nil and empty phone sets the same.

    Also note that .first returns nil if phoneNumbersArray is empty.