├── .gitignore ├── Archivable.swift ├── LICENSE ├── README.md └── RawByteConvertable.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /Archivable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | //==--------------------------------------------------------- 4 | /// Simply write the values you want to archive into the given 5 | /// ArchiveWriter. If implemented manually, make sure you 6 | /// deocde them in exactly the same order and with exactly the 7 | /// same types or else bad things will happen. Use Archivable 8 | /// instead so that you don't have to worry about this. 9 | /// 10 | public protocol ArchiveWriteable { 11 | func write(to archive: ArchiveWriter) throws 12 | } 13 | 14 | //==--------------------------------------------------------- 15 | /// Unlike Swift's Codable, the ArchiveReadable implementation 16 | /// requires the ability to initialize an instance of a type 17 | /// before it can fully decode it. This is so we can support 18 | /// reference types automatically - Class type instances are 19 | /// only stored once in the archive. Decoding a reference 20 | /// requires the ability to first make an instance and then 21 | /// fill it in with the values that are read from the archive 22 | /// in a seperate step because Swift is very strict and has 23 | /// strong opinions about these things. Other solutions to 24 | /// this problem are possible but they require more foresight 25 | /// and code redesign than I'd like to be forced to do. 26 | /// 27 | public protocol ArchiveReadable { 28 | init() 29 | 30 | mutating func read(from archive: ArchiveReader) throws 31 | 32 | /// If you need to do something after a type instance "wakes up" 33 | /// then this is where you can do it. Most of the time you don't 34 | /// so there is an empty default implementation. 35 | mutating func awake(from archive: ArchiveReader) throws 36 | } 37 | 38 | //==--------------------------------------------------------- 39 | /// Use this protocol if your type doesn't need to archive 40 | /// any properties. Useful for enums and such. 41 | /// 42 | public typealias Archivable = ArchiveReadable & ArchiveWriteable 43 | 44 | //==--------------------------------------------------------- 45 | /// This is the magic protocol your types should conform to. 46 | /// They will automatically get write(to:) and read(from:). 47 | /// You will need to supply ArchiveReadable's default init() 48 | /// initializer as well as Archivable's archivingKeyPaths 49 | /// static property which lists the key paths you wish to be 50 | /// included in the generated archives. 51 | /// 52 | public protocol KeyPathArchivable: ArchiveWriteable & ArchiveReadable { 53 | typealias Archive = ArchivableKeyPath 54 | 55 | /// This list is used to determine which properties are 56 | /// saved and restored from the archive. The order listed 57 | /// is the same order they are read and written. 58 | static var archivingKeyPaths: [Archive] { get } 59 | } 60 | 61 | // Default implementations can make life easier sometimes. 62 | extension ArchiveReadable { 63 | public mutating func awake(from archive: ArchiveReader) throws { 64 | } 65 | } 66 | 67 | // here's the magic that makes Archivable read/write using 68 | // the keypaths specified by the static archivingKeyPaths 69 | // property: 70 | public struct ArchivableKeyPath { 71 | fileprivate let valueType: ArchiveReadable.Type 72 | fileprivate let get: (Root)->ArchiveWriteable 73 | fileprivate let set: (inout Root, ArchiveReadable)->Void 74 | 75 | init(_ keyPath: WritableKeyPath) { 76 | self.valueType = Value.self 77 | 78 | self.get = { instance in 79 | return instance[keyPath: keyPath] as ArchiveWriteable 80 | } 81 | 82 | self.set = { instance, newValue in 83 | let value = newValue as! Value 84 | instance[keyPath: keyPath] = value 85 | } 86 | } 87 | } 88 | 89 | extension KeyPathArchivable { 90 | func write(to archive: ArchiveWriter) throws { 91 | for property in Self.archivingKeyPaths { 92 | try archive.write(property.get(self)) 93 | } 94 | } 95 | 96 | mutating func read(from archive: ArchiveReader) throws { 97 | for property in Self.archivingKeyPaths { 98 | property.set(&self, try archive.readArchiveReadable(property.valueType)) 99 | } 100 | } 101 | } 102 | 103 | 104 | //==--------------------------------------------------------- 105 | // types that can read/write their raw bytes directly: 106 | //==--------------------------------------------------------- 107 | 108 | extension Int8: Archivable {} 109 | extension UInt8: Archivable {} 110 | extension Int16: Archivable {} 111 | extension UInt16: Archivable {} 112 | extension Int32: Archivable {} 113 | extension UInt32: Archivable {} 114 | extension Int64: Archivable {} 115 | extension UInt64: Archivable {} 116 | extension Float32: Archivable {} 117 | extension Float64: Archivable {} 118 | extension Data: Archivable {} 119 | 120 | // (and this is how they do it...) 121 | extension RawByteConvertable where Self: Archivable { 122 | public func write(to archive: ArchiveWriter) throws { 123 | try archive.writeRawBytes(self.rawBytes) 124 | } 125 | 126 | public mutating func read(from archive: ArchiveReader) throws { 127 | try self = .init(rawBytes: archive.readRawBytes(count: MemoryLayout.size)) 128 | } 129 | } 130 | 131 | 132 | //==--------------------------------------------------------- 133 | // string is an exception because of built-in support for 134 | // uniquing strings so they are only stored once the actual 135 | // encoding/decoding is handled by ArchiveWriter/ArchiveReader 136 | //==--------------------------------------------------------- 137 | 138 | extension String: Archivable { 139 | public mutating func read(from archive: ArchiveReader) throws { try self = archive.read() } 140 | public func write(to archive: ArchiveWriter) throws { try archive.write(self) } 141 | } 142 | 143 | 144 | //==--------------------------------------------------------- 145 | // Types that are encoded in terms of other archivable types: 146 | //==--------------------------------------------------------- 147 | 148 | extension Bool: Archivable { 149 | public mutating func read(from archive: ArchiveReader) throws { try self = (archive.read() as UInt8 > 0) } 150 | public func write(to archive: ArchiveWriter) throws { try archive.write(UInt8(self ? 1 : 0)) } 151 | } 152 | 153 | // Int is always stored as an Int64 154 | extension Int: Archivable { 155 | public mutating func read(from archive: ArchiveReader) throws { try self = Int(archive.read() as Int64) } 156 | public func write(to archive: ArchiveWriter) throws { try archive.write(Int64(self)) } 157 | } 158 | 159 | // UInt is always stored as a UInt64 160 | extension UInt: Archivable { 161 | public mutating func read(from archive: ArchiveReader) throws { try self = UInt(archive.read() as UInt64) } 162 | public func write(to archive: ArchiveWriter) throws { try archive.write(UInt64(self)) } 163 | } 164 | 165 | extension Array: Archivable where Element: Archivable { 166 | public mutating func read(from archive: ArchiveReader) throws { 167 | let count: Int = try archive.read() 168 | self = try (0..(_ value: T, as type: T.Type, to stream: OutputStream, userInfo: Any? = nil, version: Int = 0) throws { 258 | // make a writer instance 259 | let archive = ArchiveWriter(to: stream, userInfo: userInfo) 260 | 261 | // immediately write the simplest header ever 262 | try archive.write(ArchiveWriter.encodingVersion as Int) 263 | try archive.write(version as Int) 264 | 265 | // write the value 266 | try archive.write(value) 267 | } 268 | 269 | public static func data(for value: T, as type: T.Type, userInfo: Any? = nil) throws -> Data { 270 | let stream = OutputStream.toMemory() 271 | stream.open() 272 | 273 | try write(value, as: type, to: stream, userInfo: userInfo) 274 | 275 | guard let data = stream.property(forKey: .dataWrittenToMemoryStreamKey) as? Data else { 276 | throw ArchivingError.writeFailed 277 | } 278 | 279 | return data 280 | } 281 | 282 | private init(to stream: OutputStream, userInfo: Any?) { 283 | self.stream = stream 284 | self.objectIds = [:] 285 | self.stringIds = [:] 286 | self.userInfo = userInfo 287 | } 288 | 289 | private func encodeString(_ string: String) throws { 290 | if let idx = stringIds[string] { 291 | try write(idx) 292 | } else { 293 | let idx = stringIds.count 294 | stringIds[string] = idx 295 | try write(idx) 296 | try write(Array(string.utf8)) 297 | } 298 | } 299 | 300 | private func encodeReference(_ obj: AnyObject & ArchiveWriteable) throws { 301 | let ref = ObjectIdentifier(obj) 302 | if let idx = objectIds[ref] { 303 | try write(idx) 304 | } else { 305 | let idx = objectIds.count 306 | objectIds[ref] = idx 307 | try write(idx) 308 | try obj.write(to: self) 309 | } 310 | } 311 | 312 | public func write(_ value: ArchiveWriteable) throws { 313 | if type(of: value) is String.Type { 314 | try encodeString(value as! String) 315 | } else if type(of: value) is AnyClass { 316 | try encodeReference(value as! AnyObject & ArchiveWriteable) 317 | } else { 318 | try value.write(to: self) 319 | } 320 | } 321 | 322 | public func writeRawBytes(_ bytes: [UInt8]) throws { 323 | let written = stream.write(bytes, maxLength: bytes.count) 324 | guard bytes.count == written else { 325 | throw ArchivingError.readFailed 326 | } 327 | } 328 | } 329 | 330 | /// This is the opposite of the ArchiveWriter. 331 | public final class ArchiveReader { 332 | public let userInfo: Any? 333 | public private(set) var version: Int = 0 334 | 335 | private let stream: InputStream 336 | private var strings: [Int : String] 337 | private var objects: [Int : ArchiveReadable] 338 | 339 | public static func read(_ type: T.Type, from stream: InputStream, userInfo: Any? = nil) throws -> T { 340 | // make and reader 341 | let archive = ArchiveReader(forReadingFrom: stream, userInfo: userInfo) 342 | 343 | // immediately read the header 344 | let encodingVersion = try archive.read() as Int 345 | archive.version = try archive.read() as Int 346 | 347 | // we only support this version for now 348 | guard encodingVersion == ArchiveWriter.encodingVersion else { throw ArchivingError.incompatibleArchiver } 349 | 350 | // load the value 351 | return try archive.read() 352 | } 353 | 354 | public static func read(_ type: T.Type, from source: Data, userInfo: Any? = nil) throws -> T { 355 | let stream = InputStream(data: source) 356 | stream.open() 357 | return try read(type, from: stream, userInfo: userInfo) 358 | } 359 | 360 | private init(forReadingFrom stream: InputStream, userInfo: Any?) { 361 | self.stream = stream 362 | self.strings = [:] 363 | self.objects = [:] 364 | self.userInfo = userInfo 365 | } 366 | 367 | private func decodeString() throws -> String { 368 | let id: Int = try read() 369 | 370 | if let str = strings[id] { 371 | return str 372 | } 373 | 374 | let input: [UInt8] = try read() 375 | 376 | guard let str = String(bytes: input, encoding: .utf8) else { 377 | throw ArchivingError.readFailed 378 | } 379 | 380 | strings[id] = str 381 | return str 382 | } 383 | 384 | private func decodeReference(of type: ArchiveReadable.Type) throws -> ArchiveReadable { 385 | let id: Int = try read() 386 | 387 | if let obj = objects[id] { 388 | return obj 389 | } 390 | 391 | var obj = type.init() 392 | objects[id] = obj 393 | try obj.read(from: self) 394 | try obj.awake(from: self) 395 | return obj 396 | } 397 | 398 | fileprivate func readArchiveReadable(_ type: ArchiveReadable.Type) throws -> ArchiveReadable { 399 | if type is String.Type { 400 | return try decodeString() 401 | } else if type is AnyClass { 402 | return try decodeReference(of: type) 403 | } else { 404 | var value = type.init() 405 | try value.read(from: self) 406 | try value.awake(from: self) 407 | return value 408 | } 409 | } 410 | 411 | public func read() throws -> T { 412 | return try readArchiveReadable(T.self) as! T 413 | } 414 | 415 | public func readRawBytes(count: Int) throws -> [UInt8] { 416 | var buffer: [UInt8] = Array(repeating: 0, count: count) 417 | let got = stream.read(&buffer, maxLength: count) 418 | guard got == count else { 419 | throw ArchivingError.readFailed 420 | } 421 | return buffer 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sean Heber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archivable 2 | A custom Swift 4.2 Archiver that supports reference types 3 | -------------------------------------------------------------------------------- /RawByteConvertable.swift: -------------------------------------------------------------------------------- 1 | // copy the raw bytes of a value 2 | private func unsafeBytes(for value: T) -> [UInt8] { 3 | return withUnsafeBytes(of: value) { Array($0) } 4 | } 5 | 6 | // use raw bytes to recreate a value 7 | private func unsafeValue(from bytes: [UInt8]) -> T { 8 | return bytes.withUnsafeBytes { $0.baseAddress!.load(as: T.self) } 9 | } 10 | 11 | /// Types that conform to this protocol are promising that they can be converted to/from an array of raw bytes. 12 | public protocol RawByteConvertable { 13 | init(rawBytes: [UInt8]) 14 | var rawBytes: [UInt8] { get } 15 | } 16 | 17 | // always uses bigEndian 18 | extension RawByteConvertable where Self: FixedWidthInteger { 19 | public init(rawBytes: [UInt8]) { self.init(bigEndian: unsafeValue(from: rawBytes)) } 20 | public var rawBytes: [UInt8] { return unsafeBytes(for: bigEndian) } 21 | } 22 | 23 | // this just writes the raw in-memory representation of the floating point - I do not know for sure if that is always safe/correct 24 | extension RawByteConvertable where Self: BinaryFloatingPoint { 25 | public init(rawBytes: [UInt8]) { self.init(unsafeValue(from: rawBytes) as Self) } 26 | public var rawBytes: [UInt8] { return unsafeBytes(for: self) } 27 | } 28 | 29 | extension Int8: RawByteConvertable {} 30 | extension UInt8: RawByteConvertable {} 31 | extension Int16: RawByteConvertable {} 32 | extension UInt16: RawByteConvertable {} 33 | extension Int32: RawByteConvertable {} 34 | extension UInt32: RawByteConvertable {} 35 | extension Int64: RawByteConvertable {} 36 | extension UInt64: RawByteConvertable {} 37 | extension Float32: RawByteConvertable {} 38 | extension Float64: RawByteConvertable {} 39 | 40 | #if canImport(Foundation) 41 | import Foundation 42 | extension Data: RawByteConvertable { 43 | public init(rawBytes: [UInt8]) { self = Data(bytes: rawBytes) } 44 | public var rawBytes: [UInt8] { return Array(self) } 45 | } 46 | #endif 47 | --------------------------------------------------------------------------------