Swift IOS AddressBook: Cast Optional AnyObject without Null Pointer Exception

The runtime environment of Apple’s Swift contains an error in handling optional AnyObjects. This blog discusses a workaround for this crash.

Swift is a new language created by Apple making iOS programming easier. Swift code runs on the operating system stack of a Mac or iPhone, and it uses a runtime library for type handling and memory allocation. A swift-programmer would normally never be bothered by null-pointer checking (introduction of optionals), allocating memory (no retain, release) and unnecessary untyped variables (syntax for casting). An isolated environment supports developers to write correct, and working, code in different situations. For example, null-pointer checking cannot be performed in swift, as null-pointer handling is performed by the optional syntax. These language constructs avoid runtime crashes caused by the famous null-pointer exception, which is a huge benefit for reliability of applications.

For example, in Swift, one can write a cast of an optional AnyObject to String as follows:

var result:String = "";
var _result:AnyObject = _objc_result.takeRetainedValue()
if let __result: String = _result as? String {
  result = __result ;
}
println(result);

The theory is nice, however, in practice it only holds if the typing and runtime library are implemented (and designed) correctly. Unfortunately, the dynamic runtime library of swift in IOS 8.1.2 (and earlier) contains a bug regarding casting optional AnyObject types: The code presented above will crash if it tries to lookup the type of a nil-AnyObject:

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
Triggered by Thread:  0

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libswiftCore.dylib            	0x00000001003964e8 0x10021c000 + 1549544
1   libswiftCore.dylib            	0x0000000100372bf4 0x10021c000 + 1403892

The crash is caused by the var as? type expression. The runtime library tries first to type the variable, instead of checking whether it is set or not. This lookup causes a null-pointer exception if it has no value, which is allowed for an optional! Of course, this issue will probably be fixed in newer releases of iOS 8 🙂

As we want to publish also apps today, we present a workaround in this blog. As earlier stated, Swift hides the existence of nil via optionals, so we need a trick. A common way in programming to enable super-power is to escape a lower execution environment, and fortunately Swift allows that. The proposed solution uses a short Objective-C for the casting, preventing Swift from crashing.

The header file (.h):

@interface AddressBookWrapper : NSObject

- (NSString *) anyObjectToString:(id) object;

@end

The code file (.m):

#import <Foundation/Foundation.h>

#import "AddressBookWrapper.h"

@implementation AddressBookWrapper

- (NSString *) anyObjectToString:(id) object {
    if (object != nil && [object isKindOfClass:[NSString class]]) {
      NSString *result = [NSString stringWithFormat:@"%@", object];
      return result;
    } else {
      return @"";
    }
}

@end

The calling code in Swift:

private func extractStringProperty(propertyName : ABPropertyID) -> String? {
   var result:String = ""
     if let _result = ABRecordCopyValue(self.internalRecord, propertyName)? {
         var addressBookWrapper: AddressBookWrapper = AddressBookWrapper()
         var __result:AnyObject = _result.takeRetainedValue()
         result = addressBookWrapper.anyObjectToString(__result)
     }
     return result;
}

We wrote the wrapper as class, we keep writing it as a static function as exercise for the reader :-).

The Swift code calls the wrapper before the value is used for further processing. The wrapper ensures that the value is safe in the context of Swift, and that it will not cause a run-time exception.

The AddressBook of iOS is implemented in C, and bridging code is necessary to access the AddressBook from a Swift-based application. The wrapper as described above is necessary to access the values from the AddressBook within Swift. In case a field is empty, i.e. nil, the Swift-based application will crash if the Objective-C wrapper is not used.

This bug is not reproducible if the code is executed via Xcode, but only occurs when an App is published as .ipa.