Skip to content

Instantly share code, notes, and snippets.

@ncreated
Last active August 12, 2018 18:56
Show Gist options
  • Select an option

  • Save ncreated/4ad3bb8a7a5d6d536708 to your computer and use it in GitHub Desktop.

Select an option

Save ncreated/4ad3bb8a7a5d6d536708 to your computer and use it in GitHub Desktop.
A generic approach to parse XML REST API with `Alamofire` and `SWXMLHash`. Still in progress...
//
// APIService+Alamofire.swift
//
// Created by Maciek on 07.10.2015.
// Copyright © 2015 Code Latte. All rights reserved.
//
import SWXMLHash
import Alamofire
enum ModelError: ErrorType {
case BadXML(XMLIndexer)
}
public protocol XMLSerializable {
init(node: XMLIndexer) throws
}
extension Alamofire.Request {
public func responseObjectForKeyPath<T: XMLSerializable>(keyPath: String, completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<T>) -> ()) -> Self {
let responseSerializer = GenericResponseSerializer<T> { request, response, data in
guard let data = data else {
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: "Received empty response")
return .Failure(nil, error)
}
do {
let xml = SWXMLHash.parse(data)
let modelXML = try xml.byKeyPath(keyPath)
let model = try T(node: modelXML)
return .Success(model)
} catch let error {
return .Failure(data, error)
}
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
public func responseArrayForKeyPath<T: XMLSerializable>(keyPath: String, completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<[T]>) -> ()) -> Self {
let responseSerializer = GenericResponseSerializer<[T]> { request, response, data in
guard let data = data else {
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: "Received empty response")
return .Failure(nil, error)
}
do {
var models = [T]()
let xml = SWXMLHash.parse(data)
let collection = try xml.byKeyPath(keyPath)
// TODO: use .map
for modelXML in collection {
models.append(try T(node: modelXML))
}
return .Success(models)
} catch let error {
return .Failure(data, error)
}
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
//
// APIService.swift
//
// Created by Maciek on 05.09.2015.
// Copyright © 2015 Code Latte. All rights reserved.
//
import Foundation
import SWXMLHash
import Alamofire
enum APIEndpoint {
case Museums
case Museum(id: String)
case Exhibitions(museumID: String)
case Exhibition(museumID: String, uri: String)
private var url: String {
return "http://<domain-here>\(relativeURLString)"
}
private var relativeURLString: String {
switch self {
case .Museums: return "/api/museum/pl.xml"
case .Museum(let id): return "/api/museum/\(id)/pl.xml"
case .Exhibitions(let museumID): return "/api/museum/\(museumID)/exhibition/pl.xml"
case .Exhibition(let museumID, let uri): return "/api/museum/\(museumID)/exhibition/\(uri)/pl.xml"
}
}
var request: Alamofire.Request {
switch self {
case .Museums: return Alamofire.request(.GET, url)
case .Museum: return Alamofire.request(.GET, url)
case .Exhibitions: return Alamofire.request(.GET, url)
case .Exhibition: return Alamofire.request(.GET, url)
}
}
var responseNodePath: String {
switch self {
case .Museums: return "collection.museum"
case .Museum: return "museum"
case .Exhibitions: return "collection.exhibition"
case .Exhibition: return "exhibition"
}
}
}
final class APIService {
// MARK: - API fetching
func fetchObjectFromEndpoint<T: XMLSerializable>(endpoint: APIEndpoint, callback: (Result<T>) -> ()) {
endpoint.request.validate().responseObjectForKeyPath(endpoint.responseNodePath) { (_, _, result: Result<T>) in
callback(result)
}
}
func fetchCollectionFromEndpoint<T: XMLSerializable>(endpoint: APIEndpoint, callback: (Result<[T]>) -> ()) {
endpoint.request.validate().responseArrayForKeyPath(endpoint.responseNodePath) { (_, _, result: Result<[T]>) in
callback(result)
}
}
}
//
// Exhibition.swift
//
// Created by Maciek on 11.10.2015.
// Copyright © 2015 Code Latte. All rights reserved.
//
import Foundation
import SWXMLHash
struct Exhibition: XMLSerializable {
let uri: String
let name: String
let description: String?
init(node: XMLIndexer) throws {
guard let name = node["name"].element?.text,
uri = node["uri"].element?.text else {
throw ModelError.BadXML(node)
}
self.name = name
self.uri = uri
self.description = node["description"].element?.text
}
}
//
// Museum.swift
//
// Created by Maciek on 06.09.2015.
// Copyright © 2015 Code Latte. All rights reserved.
//
import Foundation
import CoreLocation
import SWXMLHash
struct Museum: XMLSerializable {
let beaconUUID: String
let name: String
let description: String?
let iconResourceURI: String?
let location: CLLocation?
init(node: XMLIndexer) throws {
guard let name = node["name"].element?.text,
beaconUUID = node["beaconUUID"].element?.text else {
throw ModelError.BadXML(node)
}
self.name = name
self.beaconUUID = beaconUUID
self.description = node["description"].element?.text
self.iconResourceURI = node["self.iconResourceURI"].element?.text
var location: CLLocation? = nil
if let latitudeString = node["latitude"].element?.text,
longitudeString = node["longitude"].element?.text {
let latitude = NSString(string: latitudeString).doubleValue
let longitude = NSString(string: longitudeString).doubleValue
location = CLLocation(latitude: latitude, longitude: longitude)
}
self.location = location
}
}
@ncreated

Copy link
Copy Markdown
Author

Usage:

let museumID = "50a21449-6fb3-4ab2-89c1-71d1237bc6e9"
let exhibitionURI = "oa_exhibition_wystawa_1_1"

services.api.fetchCollectionFromEndpoint(.Museums) { (result: Result<[Museum]>) in
    switch result {
        case .Success(let museums):
            print("museums: \(museums)\n")
            break
        case .Failure(_, let error):
            print("\(error)\n")
            break
    }
}

services.api.fetchObjectFromEndpoint(.Museum(id: museumID)) { (result: Result<Museum>) in
    switch result {
        case .Success(let museum):
            print("museum: \(museum)\n")
            break
        case .Failure(_, let error):
            print("\(error)\n")
            break
    }
}


services.api.fetchCollectionFromEndpoint(.Exhibitions(museumID: museumID)) { (result: Result<[Exhibition]>) in
    switch result {
        case .Success(let museums):
            print("exhibitions: \(museums)\n")
            break
        case .Failure(_, let error):
            print("\(error)\n")
            break
        }
}

services.api.fetchObjectFromEndpoint(.Exhibition(museumID: museumID, uri: exhibitionURI)) { (result: Result<Exhibition>) in
    switch result {
        case .Success(let museum):
            print("exhibition: \(museum)\n")
            break
        case .Failure(_, let error):
            print("\(error)\n")
            break
    }
}

@ncreated

Copy link
Copy Markdown
Author
//
//  Exhibition.swift
//
//  Created by Maciek on 11.10.2015.
//  Copyright © 2015 Code Latte. All rights reserved.
//

import SWXMLHash


struct Exhibition: XMLDeserializable, Equatable {
    let uri: String
    let name: String
    let description: String?
    let iconResourceURI: String?

    var listImageURL: NSURL? {
        guard let iconResourceURI = iconResourceURI else { return nil }
        return APIEndpoint.Image(uri: iconResourceURI, size: nil).URL
    }


    // MARK: - XMLDeserializable

    static func deserialize(node: XMLIndexer) throws -> Exhibition {
        return try Exhibition(
            uri: node["uri"].value(),
            name: node["name"].value(),
            description: node["description"].value(),
            iconResourceURI: node["iconResourceUri"].value()
        )
    }
}


// MARK: - Equatable

func == (a: Exhibition, b: Exhibition) -> Bool {
    return a.uri == b.uri &&
           a.name == b.name &&
           a.description == b.description
}

@phschneider

Copy link
Copy Markdown

This code isn't compatible with Alamofire 3.0 isn't it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment