Swift - проверка свойства одиночного значения неуправляемой адресной книги для nil

Я отношусь к iOS-Development и быстрому. Но до этого момента я всегда мог помочь себе в некоторых исследованиях в stackoverflow и нескольких документах и ​​учебниках. Однако есть проблема, которую я пока не нашел.

Я хочу получить некоторые данные из адресной книги пользователей (например, свойство одиночного значения kABPersonFirstNameProperty). Поскольку функция .takeRetainedValue() вызывает ошибку, если этот контакт не имеет значения firstName в адресной книге, мне нужно убедиться, что функция ABRecordCopyValue() возвращает значение. Я попытался проверить это в закрытии:

let contactFirstName: String = {
   if (ABRecordCopyValue(self.contactReference, kABPersonFirstNameProperty) != nil) {
      return ABRecordCopyValue(self.contactReference, kABPersonFirstNameProperty).takeRetainedValue() as String
   } else {
      return ""
   }
}()

contactReference - это переменная типа ABRecordRef!

Когда контакт адресной книги предоставляет значение firstName, все работает нормально. Но если firstName отсутствует, приложение вылетает с помощью функции .takeRetainedValue(). Кажется, что инструкция if не помогает, потому что неуправляемое возвращаемое значение функции ABRecordCopyValue() не равно нулю, хотя firstName отсутствует.

Надеюсь, я смог сделать свою проблему понятной. Было бы здорово, если бы кто-нибудь мог помочь мне с некоторой мозговой волной.

Ответ 1

Если мне нужны значения, связанные с различными свойствами, я использую следующий синтаксис:

let first = ABRecordCopyValue(person, kABPersonFirstNameProperty)?.takeRetainedValue() as? String
let last  = ABRecordCopyValue(person, kABPersonLastNameProperty)?.takeRetainedValue() as? String

Или вы можете использовать необязательную привязку:

if let first = ABRecordCopyValue(person, kABPersonFirstNameProperty)?.takeRetainedValue() as? String {
    // use `first` here
}
if let last  = ABRecordCopyValue(person, kABPersonLastNameProperty)?.takeRetainedValue() as? String {
    // use `last` here
}

Если вы действительно хотите вернуть необязательный параметр, где отсутствующее значение представляет собой строку с нулевой длиной, вы можете использовать оператор ??:

let first = ABRecordCopyValue(person, kABPersonFirstNameProperty)?.takeRetainedValue() as? String ?? ""
let last  = ABRecordCopyValue(person, kABPersonLastNameProperty)?.takeRetainedValue() as? String ?? ""

Ответ 2

Я воспользовался этой функцией здесь:

func rawValueFromABRecordRef<T>(recordRef: ABRecordRef, forProperty property: ABPropertyID) -> T? {
    var returnObject: T? = nil
    if let rawValue: Unmanaged<AnyObject>? = ABRecordCopyValue(recordRef, property) {
        if let unwrappedValue: AnyObject = rawValue?.takeRetainedValue() {
            println("Passed: \(property)")
            returnObject = unwrappedValue as? T
        }
        else {
            println("Failed: \(property)")
        }
    }
    return returnObject
}

Вы можете использовать его в своем свойстве:

let contactFirstName: String = {
    if let firstName: String = rawValueFromABRecordRef(recordRef, forProperty: kABPersonFirstNameProperty) {
        return firstName
    }
    else {
        return ""
    }
}()

Ответ 3

Возможно, это больше, чем просто ответ на ваш вопрос, но именно так я занимаюсь с адресной книгой.

Я определил пользовательский оператор:

infix operator >>> { associativity left }
func >>> <T, V> (lhs: T, rhs: T -> V) -> V {
    return rhs(lhs)
}

позволяет связывать несколько вызовов с функциями более читаемым способом, например:

funcA(funcB(param))

становится

param >>> funcB >>> funcA

Затем я использую эту функцию для преобразования Unmanaged<T> в быстрый тип:

func extractUnmanaged<T, V>(value: Unmanaged<T>?) -> V? {
    if let value = value {
        var innerValue: T? = value.takeRetainedValue()
        if let innerValue: T = innerValue {
            return innerValue as? V
        }
    }
    return .None
}

и аналог, работающий с CFArray:

func extractUnmanaged(value: Unmanaged<CFArray>?) -> [AnyObject]? {
    if let value = value {
        var innerValue: CFArray? = value.takeRetainedValue()
        if let innerValue: CFArray = innerValue {
            return innerValue.__conversion()
        }
    }
    return .None
}

и это код, который открывает адресную книгу, извлекает все контакты и для каждого читает имя и организацию (в симуляторе firstName всегда имеет значение, а у отдела нет, поэтому это хорошо для тестирования):

let addressBook: ABRecordRef? = ABAddressBookCreateWithOptions(nil, nil) >>> extractUnmanaged
let results = ABAddressBookCopyArrayOfAllPeople(addressBook) >>> extractUnmanaged
if let results = results {
    for result in results {
        let firstName: String? = (result, kABPersonFirstNameProperty) >>> ABRecordCopyValue >>> extractUnmanaged
        let organization: String? = (result, kABPersonOrganizationProperty) >>> ABRecordCopyValue >>> extractUnmanaged
        println("\(firstName) - \(organization)")
    }
}

Обратите внимание, что оператор println печатает необязательный, поэтому вы увидите в консоли Optional("David") вместо David. Конечно, это только для демонстрации.

Функция, отвечающая на ваш вопрос, extractUnmanaged, которая принимает необязательный неуправляемый, разворачивает его, извлекает сохраненное значение как необязательное, разворачивает его и в конце пытается применить к целевому типу, который равен String для свойства имени. Тип inferral заботится о том, какие T и V: T - тип, завернутый в Unmanaged, V - тип возвращаемого значения, который известен, поскольку указан при объявлении целевой переменной let firstName: String? = ....

Предполагаю, что вы уже позаботились о том, чтобы проверить и попросить пользователя получить доступ к адресной книге.