Skip to content

Instantly share code, notes, and snippets.

@msanders
Last active June 3, 2016 00:20
Show Gist options
  • Save msanders/d14f1d9dfbe427bb9afd36ad2515ba07 to your computer and use it in GitHub Desktop.
Save msanders/d14f1d9dfbe427bb9afd36ad2515ba07 to your computer and use it in GitHub Desktop.
NSCoding in Swift
import Foundation
import Mapper
protocol Archivable: Mappable {
func encode(archive: Archiver)
}
final class Archiver {
private var mapping: [String: AnyObject] = [:]
required init(_ object: Archivable) {
object.encode(self)
}
func encode(key: String, value: AnyObject?) {
mapping[key] = value
}
func encode<T: Archivable>(key: String, value: T?) {
if let value = value {
mapping[key] = Archiver(value).mapping
}
}
func encode<T: Archivable>(key: String, value: [T]?) {
mapping[key] = value?.map { Archiver($0).mapping }
}
}
private extension Mappable {
@warn_unused_result
static func from(JSON: [String: AnyObject]) throws -> Self {
return try self.init(map: Mapper(JSON: JSON))
}
@warn_unused_result
static func from(JSON: [[String: AnyObject]]) throws -> [Self] {
return try JSON.map { try self.from($0) }
}
}
enum ArchiverError: ErrorType {
case ConvertibleError(value: AnyObject?, type: Any.Type)
}
extension Archivable {
@warn_unused_result
static func decode(data: NSData) throws -> Self {
var unarchived: AnyObject?
do {
try INSObjC.catchException {
unarchived = NSKeyedUnarchiver.unarchiveObjectWithData(data)
}
} catch {
throw error
}
guard let dict = unarchived as? [String: AnyObject] else {
throw ArchiverError.ConvertibleError(value: unarchived, type: [String: AnyObject].self)
}
return try Self.from(dict)
}
@warn_unused_result
static func decodeArray(data: NSData) throws -> [Self] {
var unarchived: AnyObject?
do {
try INSObjC.catchException {
unarchived = NSKeyedUnarchiver.unarchiveObjectWithData(data)
}
} catch {
throw error
}
guard let array = unarchived as? [[String: AnyObject]] else {
throw ArchiverError.ConvertibleError(value: unarchived,
type: [[String: AnyObject]].self)
}
return try Self.from(array)
}
@warn_unused_result
func encode() throws -> NSData {
var data: NSData?
do {
try INSObjC.catchException {
data = NSKeyedArchiver.archivedDataWithRootObject(Archiver(self).mapping)
}
} catch {
throw error
}
if let data = data {
return data
} else {
fatalError() // Impossible
}
}
}
extension SequenceType where Generator.Element: Archivable {
@warn_unused_result
func encode() throws -> NSData {
let mappings = map { Archiver($0).mapping }
var data: NSData?
do {
try INSObjC.catchException {
data = NSKeyedArchiver.archivedDataWithRootObject(mappings)
}
} catch {
throw error
}
if let data = data {
return data
} else {
fatalError() // Impossible
}
}
}
@msanders
Copy link
Author

msanders commented Jun 1, 2016

Example usage:

struct APIFileData {
    let data: NSData
    let name: String
    let fileName: String
    let mimeType: String
}

struct APIRequest {
    let endpoint: String
    let method: String
    let timestamp: NSDate
    let parameters: [String: AnyObject]?
    let attachments: [APIFileData]?

    init(endpoint: String, method: String, timestamp: NSDate = NSDate(),
         parameters: [String: AnyObject]? = nil, attachments: [APIFileData]? = nil) {
        self.endpoint = endpoint
        self.method = method
        self.parameters = parameters
        self.timestamp = timestamp
        self.attachments = attachments
    }
}

extension APIRequest: Archivable {
    init(map: Mapper) throws {
        endpoint = try map.from("endpoint")
        method = try map.from("method")
        timestamp = try map.from("timestamp")
        parameters = map.optionalFrom("parameters")
        attachments = map.optionalFrom("attachments")
    }

    func encode(archive: Archiver) {
        archive.encode("endpoint", value: endpoint)
        archive.encode("method", value: method)
        archive.encode("timestamp", value: timestamp)
        archive.encode("parameters", value: parameters)
        archive.encode("attachments", value: attachments)
    }
}

extension APIFileData: Archivable {
    init(map: Mapper) throws {
        data = try map.from("data")
        name = try map.from("name")
        fileName = try map.from("file_name")
        mimeType = try map.from("mime_type")
    }

    func encode(archive: Archiver) {
        archive.encode("data", value: data)
        archive.encode("name", value: name)
        archive.encode("file_name", value: fileName)
        archive.encode("mime_type", value: mimeType)
    }
}
let request = APIRequest("endpoint", method: "POST")
let data = try? request.encode()
data?.writeToURL(/* ... */)

@montanalow
Copy link

Would be great to embed example usage as code comments for posterity.

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