I'm currently working on my first real Swift project. A from-scratch update of one of ZenRockers old apps to make it fit better with iOS8 in terms of look and features.

Swift performance has been close to Objective C for the most part. But there are times when Swift suddenly fall far behind.

The app needs to import some JSON to CoreData when starting up the first time (~150KB, 350 objects with 15 values each). On the old app I've never profiled the code but you don't notice it even on an iPhone 4. But the Swift version of the code locks up my iPhone 5 for almost 3 seconds when running.

Cut down version of the import function:

func parseJSON(data: NSData) -> Bool {
    let startTime = CACurrentMediaTime()
    var itemCount = 0
    var error: NSError? = nil
    
    if let json = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error:&error) as? Dictionary<String, AnyObject> {
        if let items = json["items"] as? Array<Dictionary<String, AnyObject>> {
            CoreDataHelper.deleteAll("Item")
            for item in items {
                let entity =  NSEntityDescription.entityForName("Item", inManagedObjectContext: managedContext)
                let itemObj = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedContext)
                
                itemObj.setValue(item["id"], forKey: "id")
                itemObj.setValue(item["name"], forKey: "name")
                itemObj.setValue(item["potato"], forKey: "potato")
                itemObj.setValue(item["keyboard"], forKey: "keyboard")
                itemObj.setValue(item["towel"], forKey: "towel")
                
                itemCount += 1
            }
        } else {
            NSLog("Failed to load items from json")
            return false
        }
        
        error = nil
        if managedContext.save(&error) == false {
            NSLog("Failed to save data: \(error)")
            return false
        }
    } else {
        NSLog("Failed to parse JSON: \(error)")
        return false
    }
    
    let runTime = CACurrentMediaTime() - startTime
    NSLog("Import finished (Swift)")
    NSLog("Item: %d", itemCount)
    NSLog("Time: %.3fs", runTime)
    
    return true;
}

Running the code on an iPhone 5 produces:

app[22795:3788246] Import finished (Swift)
app[22795:3788246] Item: 350
app[22795:3788246] Time: 2.719s

After trying a a few different variations and libraries like SwiftyJSON i was stuck at around 2.5 - 3.5 seconds for the import.

Profiling the code show that more than 80% of the time is spent inside the Swift core.

Swift Profiling

So it seemed worth moving the JSON import part to Objective C to see if there was any major performance gain:

- (BOOL)runImport {
    CFTimeInterval startTime = CACurrentMediaTime();
    int itemCount = 0;
    NSError *error;
    NSDictionary *json = [NSJSONSerialization JSONObjectWithData:_data options:NSJSONReadingMutableContainers error:&error];
    
    if(!json) {
        NSLog(@"Failed to parse JSON: %@", error);
        return false;
    }

    NSArray *items = json[@"items"];
    if(items) {
        [self deleteAll:@"Item"];
        for (NSDictionary *item in items) {
            NSEntityDescription *entity = [NSEntityDescription entityForName:@"Item" inManagedObjectContext:_ctx];
            NSManagedObject *itemObj = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:_ctx];
            
            [itemObj setValue:item[@"id"] forKey:@"id"];
            [itemObj setValue:item[@"name"] forKey:@"name"];
            [itemObj setValue:item[@"potato"] forKey:@"potato"];
            [itemObj setValue:item[@"keyboard"] forKey:@"keyboard"];
            [itemObj setValue:item[@"towel"] forKey:@"towel"];
            
            itemCount += 1;
        }
    } else {
        NSLog(@"Failed to load items from json");
        return false;
    }
    
    if([_ctx save:&error] == false) {
        NSLog(@"Failed to save data: %@", error);
        return false;
    }
    
    CFTimeInterval runTime = CACurrentMediaTime() - startTime;
    NSLog(@"Import finished (ObjC)");
    NSLog(@"Item: %d", itemCount);
    NSLog(@"Time: %.3fs", runTime);
    
    return true;
}

And the result?

app[22795:3788246] Import finished (ObjC)
app[22795:3788246] Item: 350
app[22795:3788246] Time: 0.293s

More than 9x faster than the Swift version.

When profiling the Swift code [NSManagedObjectContext save:] was the 4th highest running time in the function at 91ms / 3.5%. In Objective C it had the highest running time at 98ms / 31.5%. Combined with a CoreData fetch request in [JSONImporter deleteAll:] then CoreData is 60.4% of the running time in the Objective C importer.

I'm not sure what is happening in the Swift core to create this huge difference. The profiler just shows memory references and no actual names when you go deeper into the stack. Maybe it is doing some heavy data conversions when talking to the Objective C NSJSONSerialization library?

I still like Swift. It feels like a good replacement for Objective C in terms of readability and flexibility. And luckily it's very easy to mix it with Objective C so you can have the best of both worlds.