Swift - 检查未托管的地址簿单个值属性是否为nil

12

我相对于iOS开发和Swift还比较新。但到目前为止,我总是能够通过在stackoverflow和几个文档和教程上的一些研究来自己解决问题。 然而,有一个问题我仍然找不到解决方案。

我想从用户通讯录中获取一些数据(例如单个值属性kABPersonFirstNameProperty)。因为如果这个联系人在通讯录中没有firstName值,.takeRetainedValue()函数会抛出错误,所以我需要确保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() 函数的不受管理的返回值不是 nil,尽管没有 firstName。

我希望我能清楚地说明我的问题。如果有人能帮我出出主意,那就太好了。

3个回答

32
如果我想要获取与不同属性相关联的值,则可以使用以下语法:
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 ?? ""

我喜欢你的第二个版本。谢谢你! - Rob
2
几天后,这就是我一直在寻找的答案!!非常感谢您的分享! - kev
当在手机上直接部署存档时,如果访问没有名字的记录,这仍然对我没有用,但最终我使用NSString并使用可选绑定使其工作,如此处所示。 - Gus

5

我通过这个函数得到它:

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 ""
    }
}()

为什么要点踩?如果你不说错在哪里,我又如何来纠正呢? - Logan
谢谢你的回答。很高兴看到这么多帮助!顺便说一下,我没有对任何东西进行投票。你是什么意思? - Rob
@Rob - 我并不是在说是你,但有人给我的答案投了反对票。我很想知道为什么,因为这是我正在使用的方法,而且它对我很有效。 - Logan

1
Maybe这不仅仅是回答你的问题,而是我处理通讯录的方式。
我定义了一个自定义运算符:
infix operator >>> { associativity left }
func >>> <T, V> (lhs: T, rhs: T -> V) -> V {
    return rhs(lhs)
}

允许以更易读的方式链接多个函数调用,例如:

funcA(funcB(param))

变成

param >>> funcB >>> funcA

然后我使用这个函数将 Unmanaged<T> 转换为 Swift 类型:
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始终有值,而department则没有,因此适用于测试):

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。类型推断负责确定T和V是什么:T是包装在Unmanaged中的类型,V是返回类型,在声明目标变量let firstName: String? = ...时已知。

我假设您已经处理了检查并请求用户允许访问通讯录的事宜。


感谢Antonio提供详细的答案! 看到您处理问题的灵活和可重用方式也很棒。 稍微修改了您的extractUnmanaged()函数(没有泛型),解决了我的问题。 - Rob
非泛型版本是我的第一稿,然后我不得不转换不同类型,最终想出了基于泛型的解决方案和其他所有东西。顺便说一句,很高兴它能帮到你。 - Antonio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接