📜 ⬆️ ⬇️

Quick work with JSON in Swift

At first glance, working with the JSON format in Swift does not present any particular difficulties. On the one hand, the standard set contains the NSJSONSerialization class that can parse files, on the other hand, many third-party libraries promise to make this process easier, and the code is clearer. As part of this article, I would like to consider how to read JSON files faster and why obvious approaches are slow.

And so, at first it is worth formulating the problem to be solved: there is some fairly voluminous JSON (about 30 megabytes) of the following structure:

data -> [Node] -> [Item] -> id: String, pos: [Int], coo: [Double]

You need to parse and get an array of objects of the Item type with the string id field, respectively, the pos field is an integer array, and the coo field is an array of floating-point numbers.
')
Option one - use a third-party library:

I must say that all the solutions that came to me used the standard NSJSONSerialization as a parser, and their task was seen solely in adding “syntactic sugar” and more strict typing. As an example, take one of the most popular SwiftyJSON:

let json = JSON(data: data) if let nodes = json["data"].array { for node in nodes { if let items = node.array { for item in items { if let id = item["id"].string, let pos = item["pos"].arrayObject as? [Int], let coo = item["coo"].arrayObject as? [Double] { Item(id: id, pos: pos, coo: coo) } } } } } 

On iPhone 6, the execution of this code took about 7.5 seconds, which is inadmissible for a fairly quick device.
For further comparison, we will consider this time as a reference.
Now let's try to write the same thing without using SwiftyJSON.

Option two - use the “clean” swift:

 let json = try! NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) if let nodes = (json as? [String: AnyObject])?["data"] as? [[[String: AnyObject]]] { for node in nodes { for item in node { if let id = item["id"] as? String, let pos = item["pos"] as? [Int], let coo = item["coo"] as? [Double] { Item(id: id, pos: pos, coo: coo) } } } } 

Our "bicycle" managed in 6 seconds (80% of the original), but still very long.

Let's try to figure it out, the profiler suggests that the line:

 let nodes = (json as? [String: AnyObject])?["data"] as? [[[String: AnyObject]]] 

runs unexpectedly long.

The behavior of the NSJSONSerialization class in Swift is completely analogous to its behavior in Objective C, which means the result of parsing will be a certain hierarchy consisting of objects of the type NSDictionary, NSArray, NSNumber, NSString and NSNull. This command converts the objects of these classes into the structures of Swift Array and Dictionary, which means it copies the data! (Arrays in Swift are more similar to arrays in C ++ than in Objective C)

To avoid such copying, we try not to use Swift'a beautiful typed arrays.

Option three - without using Array and Dictionary:

 let json = try! NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) if let nodes = (json as? NSDictionary)?["data"] as? NSArray { for node in nodes { if let node = node as? NSArray { for item in node { if let item = item as? NSDictionary, let id = item["id"] as? NSString, let pos = item["pos"] as? NSArray, let coo = item["coo"] as? NSArray { var _pos = [Int](count: pos.count, repeatedValue: 0) var _coo = [Double](count: coo.count, repeatedValue: 0) for var i = 0; i < pos.count; i++ { if let p = pos[i] as? NSNumber { _pos.append(p.integerValue) } } for var i = 0; i < coo.count; i++ { if let c = coo[i] as? NSNumber { _coo.append(c.doubleValue) } } Item(id: String(id), pos: _pos, coo: _coo) } } } } } 

It looks awful, of course. But we are primarily interested in the speed of work: 2 seconds (almost 4 times faster than SwiftyJSON!)

Thus, eliminating implicit conversions can achieve a significant increase in speed.

Source: https://habr.com/ru/post/274809/


All Articles