Search code examples
swiftabaddressbook

Address Book External Change Callback in Swift (with C Function Pointers?)


Original Question (see solution below):

I am trying to use the AddressBook.framework in my Swift App, but can't figure out how to implement the ABAddressBookRegisterExternalChangeCallback function.

In Objective-C, I just implement the callback as a C function and pass its pointer:

// somewhere in the initializer of the MyAddressBook class:
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(nil, nil);
ABAddressBookRegisterExternalChangeCallback(addressBook, externalChangeCallback, (__bridge void *)(self));

// somewhere else in the MyAddressBook class:
void externalChangeCallback(ABAddressBookRef reference, CFDictionaryRef info, void *context)
{
    [(__bridge MyAddressBook *)context addressBookDidChangeExternally];
}

- (void)addressBookDidChangeExternally
{
    // good old Obj-C from here on!
}

In Swift, it is proving very difficult for me to handle C functions. I found that Apple added the ability to pass C function pointers around in beta 3, but how do I declare such a function? It would be good to use Swift's closure syntax, but is that even possible here?

This is where I create the ABAddressBookRef:

var addressBookRef: ABAddressBookRef = {
    let addressBookRef: ABAddressBookRef = ABAddressBookCreateWithOptions(nil, nil).takeRetainedValue()

    // TODO: how do I make this work?
    let externalChangeCallback: ABExternalChangeCallback = {
        println("Address book changed externally!")
    }
    ABAddressBookRegisterExternalChangeCallback(addressBookRef, externalChangeCallback, nil)

    return addressBookRef
}()

So how can I implement this in Swift?


Solution (with flaws):

As suggested by pNre, this is how I implemented it now:

In Objective-C:

AddressBookExternalChangeCallback.h:

#import <AddressBook/AddressBook.h>
void registerExternalChangeCallbackForAddressBook(ABAddressBookRef addressBookRef);

AddressBookExternalChangeCallback.m:

#import "AddressBookExternalChangeCallback.h"

void addressBookExternalChangeCallback(ABAddressBookRef addressBookRef, CFDictionaryRef info, void *context)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:@"AddressBookDidChangeExternallyNotification" object:nil];
    });
}

void registerExternalChangeCallbackForAddressBook(ABAddressBookRef addressBookRef)
{
    ABAddressBookRegisterExternalChangeCallback(addressBookRef, addressBookExternalChangeCallback, nil);
}

In Swift:

after importing bridging header:

registerExternalChangeCallbackForAddressBook(addressBookRef)

A notification is posted whenever the address book changes. Only @objc classes can register for notifications, though, so is there a way to call a Swift function or method instead?


Solution

  • ABExternalChangeCallback is defined as

    typealias ABExternalChangeCallback = CFunctionPointer<((ABAddressBookRef!, CFDictionary!, UnsafeMutablePointer<()>) -> Void)>
    

    From the Xcode release notes:

    However, you cannot call a C function pointer (CFunctionPointer) or convert a closure to C function pointer type.

    This means you can't assign a block the way you're doing. However you can bypass this limitation calling ABAddressBookRegisterExternalChangeCallback in an objc function and calling it from your swift code.