Skip to content

Instantly share code, notes, and snippets.

@ncreated
Last active August 12, 2018 18:56
Show Gist options
  • Save ncreated/4ad3bb8a7a5d6d536708 to your computer and use it in GitHub Desktop.
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
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

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