Networking
Networking in Swift 4
To enable your apps to access data on the internet, you will use a networking library. You can use the URLSession
class to access network resources or you can use the AlamoFire library. Some common networking tasks:
- Downloading text or binaray data such as json or images
- Uploading text or binary data such as json or images
- Perform background upload/download tasks
References
https://www.raywenderlich.com/567-urlsession-tutorial-getting-started https://grokswift.com/updating-nsurlsession-to-swift-3-0/ https://www.weheartswift.com/need-know-json/
URLSession basics
Downloading JSON with URLSession
The code above does the following
- Validates the url
- Creates a configuration and a session object based on the configuration
- Uses the
dataTask()
method of the session object to make a request to the web server and download the json - Validates that we have no errors and that the response data returned is not nil
- Attempts to parse the JSON
The last step is dependent on the format of the JSON that you are attempting to parse. In my case, the JSON looks like this:
{
"cards": [
{
"title": "Panicked Pilot",
"amount": 2,
"type": "Pilot",
"text": "Gain 2 stress tokens. Then repair this card."
},
{
"title": "Blinded Pilot",
"amount": 2,
"type": "Pilot",
"text": "While you perform an attack, you can modify your dice only
by spending [Force] for their default effect. Action: Repair this card."
}
We have a data structure that is a dictionary that is keyed by a String
called cards
and the value in the dictionary is an array of dictionaries. The code to parse this json is
do {
// json root
guard let rootDictionary = try JSONSerialization.jsonObject(
with: response,
options: []) as? [String: Any] else {
print("error trying to convert data to JSON")
return
}
// cards dictionary
if let cards = rootDictionary["cards"] as? [[String: Any]] {
for card in cards {
if let title = card["title"] as? String {
print(title)
}
}
}
} catch {
print("failed to serialize data to JSON")
return
}
First we parse the root of the JSON into the rootDictionary
variable which is of type [String: Any]
. The next step
is to parse the array of dictionaries into the cards
array [[String: Any]]
. Then we can iterate over the array and extract values from the dictionaries within the array.
Parsing JSON with Codeable
We can simplify things greatly by using the Codeable
protocol which was introduced in Swift 4.
To use Codeable
we need each type that is to be encoded/decoded to adopt the Codeable
protocol for our sample JSON we
have the following types
struct DamageCardList : Codable {
let cards: [DamageCard]
}
This type corresponds to the cards
[String: Any]
dictionary
"cards": [...]
struct DamageCard: Codable {
let title: String
let amount: Int
let type: DamageCardType
let text: String
}
This type corresponds to each dictionary within the array keyed by the cards
string
{
"title": "Panicked Pilot",
"amount": 2,
"type": "Pilot",
"text": "Gain 2 stress tokens. Then repair this card."
},
{
"title": "Blinded Pilot",
"amount": 2,
"type": "Pilot",
"text": "While you perform an attack, you can modify your dice only
by spending [Force] for their default effect. Action: Repair this card."
}
enum DamageCardType: String, Codable {
case Ship
case Pilot
}
We can also create custom types that will support serialization into custom objects, in this case an enum
representation
of the type
key within the dictionary.
Instead of requesting the json from the network, we can also read the json from a file stored in our app bundle:
func readDamageCards() -> String {
var contents: String = ""
if let fileURL = Bundle.main.url(forResource:"core", withExtension: "json")
{
do {
contents = try String(contentsOf: fileURL, encoding: String.Encoding.utf8)
print(contents)
} catch {
print("Error: \(error.localizedDescription)")
}
} else {
print("No such file URL")
}
return contents
}
Just read the contents from Bundle.main.url
func serializeJSON(jsonString: String) -> DamageCardList {
let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let cards: DamageCardList = try! decoder.decode(DamageCardList.self, from: jsonData)
return cards
}
Now that we have our json, we can create a JSONDecoder
and call its decode
function, passing in DamageCardList.self
as the type. Since all types contained within DamageCardList
are also Codable
they will also get hydrated from the json.