├── Sources └── Primer │ ├── Concurrency │ ├── Atomics.swift │ ├── PropertyWrappers.swift │ └── Locking.swift │ ├── Assign.swift │ ├── Extensions │ ├── Binding.swift │ └── KeyPath+ReadableFormat.swift │ ├── ReadOnly.swift │ ├── Partial.swift │ └── Serialization │ └── DictionaryCodable.swift ├── Tests ├── LinuxMain.swift └── PrimerTests │ ├── XCTestManifests.swift │ └── BasicTests.swift ├── .swiftpm └── xcode │ └── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── WorkspaceSettings.xcsettings ├── Package.resolved ├── Package.swift ├── .gitignore └── README.md /Sources/Primer/Concurrency/Atomics.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Atomics 3 | 4 | extension ManagedAtomic: @unchecked Sendable { } 5 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import ProxyTests 2 | import XCTest 3 | 4 | var tests = [XCTestCaseEntry]() 5 | tests += ProxyTests.allTests() 6 | XCTMain(tests) 7 | -------------------------------------------------------------------------------- /Tests/PrimerTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | ] 7 | } 8 | #endif 9 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/Primer/Assign.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// This function is used to copy the values of all enumerable own properties from one or more 4 | /// source struct to a target struct. 5 | /// - note: If the argument is a reference type the same refence is returned. 6 | public func assign(_ value: T, changes: (inout T) -> Void) -> T { 7 | var copy = value 8 | changes(©) 9 | return copy 10 | } 11 | -------------------------------------------------------------------------------- /Sources/Primer/Extensions/Binding.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if canImport(SwiftUI) 3 | import SwiftUI 4 | 5 | /// Optional Coalescing for `Binding`. 6 | public func ?? (lhs: Binding, rhs: T) -> Binding { 7 | Binding( 8 | get: { lhs.wrappedValue ?? rhs }, 9 | set: { lhs.wrappedValue = $0 } 10 | ) 11 | } 12 | 13 | public extension Binding { 14 | /// When the `Binding`'s wrapped value changes, the given closure is executed. 15 | func onUpdate(_ closure: @escaping () -> Void) -> Binding { 16 | Binding( 17 | get: { wrappedValue }, 18 | set: { 19 | wrappedValue = $0 20 | closure() 21 | }) 22 | } 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "swift-atomics", 6 | "repositoryURL": "https://github.com/apple/swift-atomics.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "3e95ba32cd1b4c877f6163e8eea54afc4e63bf9f", 10 | "version": "0.0.3" 11 | } 12 | }, 13 | { 14 | "package": "swift-log", 15 | "repositoryURL": "https://github.com/apple/swift-log.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "5d66f7ba25daf4f94100e7022febf3c75e37a6c7", 19 | "version": "1.4.2" 20 | } 21 | } 22 | ] 23 | }, 24 | "version": 1 25 | } 26 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Primer", 8 | platforms: [ 9 | .iOS(.v13), 10 | .macOS(.v10_15), 11 | .tvOS(.v13), 12 | .watchOS(.v6) 13 | ], 14 | products: [ 15 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 16 | .library( 17 | name: "Primer", 18 | targets: ["Primer"]), 19 | ], 20 | dependencies: [ 21 | .package(url: "https://github.com/apple/swift-atomics.git", from: "0.0.3"), 22 | .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0") 23 | ], 24 | targets: [ 25 | .target( 26 | name: "Primer", 27 | dependencies: [ 28 | .product(name: "Atomics", package: "swift-atomics"), 29 | .product(name: "Logging", package: "swift-log") 30 | ], 31 | path: "Sources/Primer//"), 32 | .testTarget( 33 | name: "PrimerTests", 34 | dependencies: ["Primer"]), 35 | ] 36 | ) 37 | -------------------------------------------------------------------------------- /Sources/Primer/ReadOnly.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Combine 3 | 4 | /// Constructs a type with all properties of the given generic type `T` set to readonly, 5 | /// meaning that the properties of the constructed type cannot be reassigned. 6 | /// 7 | /// - note: A read-only object can propagate change events if the wrapped type ia an 8 | /// `ObservableObject` by calling `propagateObservableObject` at construction time. 9 | /// 10 | /// ``` 11 | /// struct Todo { var title: String; var description: String } 12 | /// let todo = Todo(title: "A Title", description: "A Description") 13 | /// let readOnlyTodo = ReadOnly(todo) 14 | /// readOnlyTodo.title // "A title" 15 | /// ``` 16 | /// 17 | @dynamicMemberLookup 18 | @propertyWrapper 19 | open class ReadOnly: ObservableObject { 20 | public private(set) var wrappedValue: T 21 | 22 | /// Constructs a new read-only proxy for the object passed as argument. 23 | init(object: T) { 24 | wrappedValue = object 25 | } 26 | 27 | public func read(keyPath: KeyPath) -> V { 28 | wrappedValue[keyPath: keyPath] 29 | } 30 | 31 | /// Use `@dynamicMemberLookup` keypath subscript to forward the value of the proxied object. 32 | public subscript(dynamicMember keyPath: KeyPath) -> V { 33 | wrappedValue[keyPath: keyPath] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/PrimerTests/BasicTests.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | import SwiftUI 3 | import XCTest 4 | 5 | @testable import Primer 6 | 7 | @available(OSX 10.15, iOS 13.0, *) 8 | final class BasicTests: XCTestCase { 9 | var subscriber: Cancellable? 10 | 11 | func testReadOnly() { 12 | let proxy = ReadOnly(object: TestData()) 13 | XCTAssert(proxy.constant == 1337) 14 | XCTAssert(proxy.label == "Initial") 15 | XCTAssert(proxy.number == 42) 16 | } 17 | 18 | func testPartial() { 19 | struct Todo { var title: String; var description: String } 20 | var partial = Partial { .success(Todo( 21 | title: $0.get(\Todo.title, default: "Untitled"), 22 | description: $0.get(\Todo.description, default: "No description"))) 23 | } 24 | partial.title = "A Title" 25 | partial.description = "A Description" 26 | XCTAssert(partial.title == "A Title") 27 | XCTAssert(partial.description == "A Description") 28 | var todo = try! partial.build().get() 29 | XCTAssert(todo.title == "A Title") 30 | XCTAssert(todo.description == "A Description") 31 | partial.description = "Another Descrition" 32 | XCTAssert(partial.description == "Another Descrition") 33 | todo = partial.merge(&todo) 34 | XCTAssert(todo.title == "A Title") 35 | XCTAssert(todo.description == "Another Descrition") 36 | } 37 | } 38 | 39 | // MARK: - Mocks 40 | 41 | struct TestData { 42 | let constant = 1337 43 | var label = "Initial" 44 | var number = 42 45 | 46 | init() {} 47 | init(label: String, number: Int) { 48 | self.label = label 49 | self.number = number 50 | } 51 | } 52 | 53 | enum TestEnum: Int { case started, ongoing, finished } 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | .DS_Store 7 | build/ 8 | DerivedData/ 9 | 10 | ## Various settings 11 | *.pbxuser 12 | !default.pbxuser 13 | *.mode1v3 14 | !default.mode1v3 15 | *.mode2v3 16 | !default.mode2v3 17 | *.perspectivev3 18 | !default.perspectivev3 19 | xcuserdata/ 20 | 21 | ## Other 22 | *.moved-aside 23 | *.xccheckout 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | *.dSYM.zip 30 | *.dSYM 31 | 32 | ## Playgrounds 33 | timeline.xctimeline 34 | playground.xcworkspace 35 | 36 | # Swift Package Manager 37 | # 38 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 39 | # Packages/ 40 | # Package.pins 41 | # Package.resolved 42 | .build/ 43 | 44 | # CocoaPods 45 | # 46 | # We recommend against adding the Pods directory to your .gitignore. However 47 | # you should judge for yourself, the pros and cons are mentioned at: 48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 49 | # 50 | # Pods/ 51 | 52 | # Carthage 53 | # 54 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 55 | # Carthage/Checkouts 56 | 57 | Carthage/Build 58 | 59 | # fastlane 60 | # 61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 62 | # screenshots whenever they are needed. 63 | # For more information about the recommended setup visit: 64 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 65 | 66 | fastlane/report.xml 67 | fastlane/Preview.html 68 | fastlane/screenshots/**/*.png 69 | fastlane/test_output 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Primer [![Swift](https://img.shields.io/badge/swift-5.1-orange.svg?style=flat)](#) 2 | 3 | ### Assign 4 | 5 | This function is used to copy the values of all enumerable own properties from one or more 6 | source struct to a target struct. 7 | If the argument is a reference type the same refence is returned. 8 | 9 | ```swift 10 | public func assign(_ value: T, changes: (inout T) -> Void) -> T 11 | ``` 12 | 13 | ### Partial 14 | 15 | Constructs a type with all properties of T set to optional. This utility will return a type 16 | that represents all subsets of a given type. 17 | 18 | The wrapped type can be then constructed at referred time by calling the `build()` method. 19 | The instance can be later on changed with the `merge(inout _:)` method. 20 | 21 | ```swift 22 | struct Todo { var title: String; var description: String } 23 | var partial = Partial { .success(Todo( 24 | title: $0.get(\Todo.title, default: "Untitled"), 25 | description: $0.get(\Todo.description, default: "No description"))) 26 | } 27 | partial.title = "A Title" 28 | partial.description = "A Description" 29 | var todo = try! partial.build().get() 30 | partial.description = "Another Descrition" 31 | todo = partial.merge(&todo) 32 | ``` 33 | 34 | ### ReadOnly 35 | 36 | Constructs a type with all properties of T set to readonly, meaning the properties of 37 | the constructed type cannot be reassigned. 38 | 39 | **Note** A read-only object can propagate change events if the wrapped type ia an 40 | `ObservableObject` by calling `propagateObservableObject()` at construction time. 41 | 42 | ```swift 43 | struct Todo { var title: String; var description: String } 44 | let todo = Todo(title: "A Title", description: "A Description") 45 | let readOnlyTodo = ReadOnly(todo) 46 | readOnlyTodo.title // "A title" 47 | ``` 48 | 49 | ### ObservableProxy 50 | 51 | Creates an observable Proxy for the object passed as argument (with granularity at the 52 | property level). 53 | 54 | 55 | ```swift 56 | struct Todo { var title: String; var description: String } 57 | let todo = Todo(title: "A Title", description: "A Description") 58 | let proxy = Proxy(todo) 59 | proxy.propertyDidChange.sink { 60 | if $0.match(keyPath: \.title) { 61 | ... 62 | } 63 | } 64 | proxy.title = "New Title" 65 | ``` 66 | 67 | ### Concurrency 68 | 69 | This package offer a variety of different lock implementations: 70 | * `Mutex`: enforces limits on access to a resource when there are many threads 71 | of execution. 72 | * `UnfairLock`: low-level lock that allows waiters to block efficiently on contention. 73 | * `ReadersWriterLock`: readers-writer lock provided by the platform implementation 74 | of the POSIX Threads standard. 75 | 76 | Property wrappers to work with any of the locks above or any `NSLocking` compliant lock: 77 | * `@LockAtomic` 78 | * `@SyncDispatchQueueAtomic` 79 | * `@ReadersWriterAtomic` 80 | 81 | 82 | The package also includes `LockfreeAtomic`: fine-grained atomic operations allowing for Lockfree concurrent programming. Each atomic operation is indivisible with regards to any other atomic operation that involves the same object. 83 | -------------------------------------------------------------------------------- /Sources/Primer/Concurrency/PropertyWrappers.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MARK: - Atomic 4 | 5 | @propertyWrapper 6 | public final class LockAtomic: @unchecked Sendable { 7 | private let lock: L 8 | private var value: T 9 | 10 | public init(wrappedValue: T, _ lock: L.Type) { 11 | self.lock = L.init() 12 | self.value = wrappedValue 13 | } 14 | 15 | public var wrappedValue: T { 16 | get { 17 | lock.withLock { 18 | value 19 | } 20 | } 21 | set { 22 | lock.withLock { 23 | value = newValue 24 | } 25 | } 26 | } 27 | 28 | /// Used for multi-statement atomic access to the wrapped property. 29 | /// This is especially useful to wrap index-subscripts in value type collections that 30 | /// otherwise would result in a call to get, a value copy and a subsequent call to set. 31 | public func mutate(_ block: (inout T) -> Void) { 32 | lock.withLock { 33 | block(&value) 34 | } 35 | } 36 | 37 | public var projectedValue: LockAtomic { self } 38 | } 39 | 40 | // MARK: - SyncDispatchQueueAtomic 41 | 42 | @propertyWrapper 43 | public final class SyncDispatchQueueAtomic: @unchecked Sendable { 44 | private let queue: DispatchQueue 45 | private var value: T 46 | private let concurrentReads: Bool 47 | 48 | public init(wrappedValue: T, concurrentReads: Bool = true) { 49 | self.value = wrappedValue 50 | self.concurrentReads = concurrentReads 51 | let label = "SyncDispatchQueueAtomic.\(UUID().uuidString)" 52 | self.queue = DispatchQueue(label: label, attributes: concurrentReads ? [.concurrent] : []) 53 | } 54 | 55 | public var wrappedValue: T { 56 | get { queue.sync { value } } 57 | set { queue.sync(flags: concurrentReads ? [.barrier] : []) { value = newValue } } 58 | } 59 | 60 | /// Used for multi-statement atomic access to the wrapped property. 61 | /// This is especially useful to wrap index-subscripts in value type collections that 62 | /// otherwise would result in a call to get, a value copy and a subsequent call to set. 63 | public func mutate(_ block: (inout T) -> Void) { 64 | queue.sync { 65 | block(&value) 66 | } 67 | } 68 | 69 | public var projectedValue: SyncDispatchQueueAtomic { self } 70 | } 71 | 72 | // MARK: - ReadersWriterAtomic 73 | 74 | @propertyWrapper 75 | public final class ReadersWriterAtomic: @unchecked Sendable { 76 | private let lock = ReadersWriterLock() 77 | private var value: T 78 | 79 | public init(wrappedValue: T) { 80 | self.value = wrappedValue 81 | } 82 | 83 | public var wrappedValue: T { 84 | get { 85 | lock.withReadLock { 86 | value 87 | } 88 | } 89 | set { 90 | lock.withWriteLock { 91 | self.value = newValue 92 | } 93 | } 94 | } 95 | 96 | /// Used for multi-statement atomic access to the wrapped property. 97 | /// This is especially useful to wrap index-subscripts in value type collections that 98 | /// otherwise would result in a call to get, a value copy and a subsequent call to set. 99 | public func mutate(_ block: (inout T) -> Void) { 100 | lock.withWriteLock { 101 | block(&value) 102 | } 103 | } 104 | 105 | public var projectedValue: ReadersWriterAtomic { self } 106 | } 107 | -------------------------------------------------------------------------------- /Sources/Primer/Extensions/KeyPath+ReadableFormat.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension KeyPath { 4 | 5 | /// Returns the human readable name for the property pointed at. 6 | var readableFormat: String? { 7 | guard let offset = MemoryLayout.offset(of: self) else { 8 | return nil 9 | } 10 | let typePtr = unsafeBitCast(Root.self, to: UnsafeMutableRawPointer.self) 11 | let metadata = typePtr.assumingMemoryBound(to: StructMetadata.self) 12 | let kind = metadata.pointee._kind 13 | 14 | // see https://github.com/apple/swift/blob/main/include/swift/ABI/MetadataKind.def 15 | guard kind == 1 || kind == 0x200 else { 16 | assertionFailure() 17 | return nil 18 | } 19 | 20 | let typeDescriptor = metadata.pointee.typeDescriptor 21 | let numberOfFields = Int(typeDescriptor.pointee.numberOfFields) 22 | let offsets = typeDescriptor.pointee.offsetToTheFieldOffsetVector.buffer( 23 | metadata: typePtr, 24 | count: numberOfFields) 25 | 26 | guard let fieldIndex = offsets.firstIndex(of: Int32(offset)) else { 27 | return nil 28 | } 29 | return typeDescriptor.pointee 30 | .fieldDescriptor.advanced().pointee 31 | .fields.pointer().advanced(by: fieldIndex).pointee 32 | .fieldName() 33 | } 34 | } 35 | 36 | // MARK: - Memory Layout 37 | 38 | private struct StructMetadata { 39 | var _kind: Int 40 | var typeDescriptor: UnsafeMutablePointer 41 | } 42 | 43 | private struct StructTypeDescriptor { 44 | var flags: Int32 45 | var parent: Int32 46 | var mangledName: RelativePointer 47 | var accessFunctionPtr: RelativePointer 48 | var fieldDescriptor: RelativePointer 49 | var numberOfFields: Int32 50 | var offsetToTheFieldOffsetVector: RelativeBufferPointer 51 | } 52 | 53 | private struct FieldDescriptor { 54 | var mangledTypeNameOffset: Int32 55 | var superClassOffset: Int32 56 | var _kind: UInt16 57 | var fieldRecordSize: Int16 58 | var numFields: Int32 59 | var fields: Buffer 60 | 61 | struct Record { 62 | var fieldRecordFlags: Int32 63 | var _mangledTypeName: RelativePointer 64 | var _fieldName: RelativePointer 65 | 66 | mutating func fieldName() -> String { 67 | String(cString: _fieldName.advanced()) 68 | } 69 | } 70 | } 71 | 72 | // MARK: - Pointers 73 | 74 | private struct Buffer { 75 | var element: Element 76 | 77 | mutating func pointer() -> UnsafeMutablePointer { 78 | withUnsafePointer(to: &self) { 79 | UnsafeMutableRawPointer(mutating: UnsafeRawPointer($0)).assumingMemoryBound(to: Element.self) 80 | } 81 | } 82 | } 83 | 84 | private struct RelativePointer { 85 | var offset: Offset 86 | 87 | mutating func advanced() -> UnsafeMutablePointer { 88 | let offset = self.offset 89 | return withUnsafePointer(to: &self) { p in 90 | UnsafeMutableRawPointer(mutating: p) 91 | .advanced(by: numericCast(offset)) 92 | .assumingMemoryBound(to: Pointee.self) 93 | } 94 | } 95 | } 96 | 97 | private struct RelativeBufferPointer { 98 | var strides: Offset 99 | 100 | func buffer(metadata: UnsafeRawPointer, count: Int) -> UnsafeBufferPointer { 101 | let offset = numericCast(strides) * MemoryLayout.size 102 | let ptr = metadata.advanced(by: offset).assumingMemoryBound(to: Pointee.self) 103 | return UnsafeBufferPointer(start: ptr, count: count) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Sources/Primer/Partial.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Constructs a type with all properties of T set to optional. This utility will return a type 4 | /// that represents all subsets of a given type. 5 | /// 6 | /// ``` 7 | /// struct Todo { var title: String; var description: String } 8 | /// 9 | /// var partial = Partial { .success(Todo( 10 | /// title: $0.get(\Todo.title, default: "Untitled"), 11 | /// description: $0.get(\Todo.description, default: "No description"))) 12 | /// } 13 | /// 14 | /// partial.title = "A Title" 15 | /// partial.description = "A Description" 16 | /// var todo = try! partial.build().get() 17 | /// 18 | /// partial.description = "Another Descrition" 19 | /// todo = partial.merge(&todo) 20 | /// ``` 21 | /// 22 | @dynamicMemberLookup 23 | public struct Partial { 24 | /// The construction closure invoked by `build()`. 25 | public let create: (Partial) -> Result 26 | 27 | /// All of the values currently set in this partial. 28 | private var keypathToValueMap: [AnyKeyPath: Any] = [:] 29 | 30 | /// All of the `set` commands that will performed once the object is built. 31 | private var keypathToSetValueMap: [AnyKeyPath: (inout T) -> Void] = [:] 32 | 33 | public init(_ create: @escaping (Partial) -> Result) { 34 | self.create = create 35 | } 36 | 37 | /// Use `@dynamicMemberLookup` keypath subscript to store the object configuration and postpone 38 | /// the object construction. 39 | public subscript(dynamicMember keyPath: KeyPath) -> V? { 40 | get { 41 | get(keyPath) 42 | } 43 | set { 44 | keypathToValueMap[keyPath] = newValue 45 | } 46 | } 47 | /// Use `@dynamicMemberLookup` keypath subscript to store the object configuration and postpone 48 | /// the object construction. 49 | /// - note: `WritableKeyPath` properties are set on the object after construction and used 50 | /// by the `merge(:)` function. 51 | public subscript(dynamicMember keyPath: WritableKeyPath) -> V? { 52 | get { 53 | get(keyPath) 54 | } 55 | set { 56 | guard let value = newValue else { 57 | keypathToValueMap.removeValue(forKey: keyPath) 58 | keypathToSetValueMap.removeValue(forKey: keyPath) 59 | return 60 | } 61 | keypathToValueMap[keyPath] = value 62 | keypathToSetValueMap[keyPath] = { object in 63 | object[keyPath: keyPath] = value 64 | } 65 | } 66 | } 67 | 68 | /// Build the target object by using the `createInstanceClosure` passed to the constructor. 69 | public func build() -> Result { 70 | let result = create(self) 71 | switch result { 72 | case .success(var obj): 73 | for (_, setValueClosure) in keypathToSetValueMap { 74 | setValueClosure(&obj) 75 | } 76 | return result 77 | case .failure(_): 78 | return result 79 | } 80 | } 81 | 82 | /// Merge all of the properties currently set in this Partial with the destination object. 83 | public func merge(_ dest: inout T) -> T { 84 | assign(dest) { 85 | for (_, setValueClosure) in self.keypathToSetValueMap { 86 | setValueClosure(&$0) 87 | } 88 | } 89 | } 90 | 91 | /// Returns the value currently set for the given keyPath or an alternative default value. 92 | public func get(_ keyPath: KeyPath, default: V) -> V { 93 | guard let value = keypathToValueMap[keyPath] as? V else { 94 | return `default` 95 | } 96 | return value 97 | } 98 | 99 | /// Returns the value currently set for the given keyPath or `nil` 100 | public func get(_ keyPath: KeyPath) -> V? { 101 | guard let value = keypathToValueMap[keyPath] as? V else { 102 | return nil 103 | } 104 | return value 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Sources/Primer/Concurrency/Locking.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MARK: - Locking 4 | 5 | public protocol Locking { 6 | /// Create a new lock. 7 | init() 8 | 9 | /// Acquire the lock. 10 | /// 11 | /// Whenever possible, consider using `withLock` instead of this method and 12 | /// `unlock`, to simplify lock handling. 13 | func lock() 14 | 15 | /// Release the lock. 16 | /// 17 | /// Whenver possible, consider using `withLock` instead of this method and 18 | /// `lock`, to simplify lock handling. 19 | func unlock() 20 | } 21 | 22 | extension Locking { 23 | /// Acquire the lock for the duration of the given block. 24 | /// 25 | /// This convenience method should be preferred to `lock` and `unlock` in 26 | /// most situations, as it ensures that the lock will be released regardless 27 | /// of how `body` exits. 28 | @inlinable 29 | public func withLock(_ body: () throws -> T) rethrows -> T { 30 | defer { 31 | unlock() 32 | } 33 | lock() 34 | return try body() 35 | } 36 | 37 | /// Specialise Void return (for performance). 38 | @inlinable 39 | public func withLockVoid(_ body: () throws -> Void) rethrows -> Void { 40 | try self.withLock(body) 41 | } 42 | 43 | /// Async variant. 44 | @inlinable 45 | public func withLock(_ body: () async throws -> T) async rethrows -> T { 46 | defer { 47 | unlock() 48 | } 49 | lock() 50 | return try await body() 51 | } 52 | } 53 | 54 | // MARK: - Foundation Locks 55 | 56 | /// An object that coordinates the operation of multiple threads of execution within the 57 | /// same application. 58 | extension NSLock: Locking { } 59 | 60 | /// A lock that may be acquired multiple times by the same thread without causing a deadlock. 61 | extension NSRecursiveLock: Locking { } 62 | 63 | /// A lock that multiple applications on multiple hosts can use to restrict access to some 64 | /// shared resource, such as a file. 65 | extension NSConditionLock: Locking { } 66 | 67 | // MARK: - Mutex 68 | 69 | /// A mechanism that enforces limits on access to a resource when there are many threads 70 | /// of execution. 71 | public final class Mutex: Locking { 72 | private var mutex: pthread_mutex_t = { 73 | var mutex = pthread_mutex_t() 74 | pthread_mutex_init(&mutex, nil) 75 | return mutex 76 | }() 77 | 78 | public init() {} 79 | 80 | public func lock() { 81 | pthread_mutex_lock(&mutex) 82 | } 83 | 84 | public func unlock() { 85 | pthread_mutex_unlock(&mutex) 86 | } 87 | } 88 | 89 | // MARK: - UnfairLock 90 | 91 | /// A low-level lock that allows waiters to block efficiently on contention. 92 | public final class UnfairLock: Locking { 93 | private var unfairLock = os_unfair_lock_s() 94 | 95 | public init() {} 96 | 97 | public func lock() { 98 | os_unfair_lock_lock(&unfairLock) 99 | } 100 | 101 | public func unlock() { 102 | os_unfair_lock_unlock(&unfairLock) 103 | } 104 | } 105 | 106 | // MARK: - ReadersWriterLock 107 | 108 | /// A readers-writer lock provided by the platform implementation of the POSIX Threads standard. 109 | /// Read more: https://en.wikipedia.org/wiki/POSIX_Threads 110 | public final class ReadersWriterLock: @unchecked Sendable { 111 | private var rwlock: UnsafeMutablePointer 112 | 113 | public init() { 114 | rwlock = UnsafeMutablePointer.allocate(capacity: 1) 115 | assert(pthread_rwlock_init(rwlock, nil) == 0) 116 | } 117 | 118 | deinit { 119 | assert(pthread_rwlock_destroy(rwlock) == 0) 120 | rwlock.deinitialize(count: 1) 121 | rwlock.deallocate() 122 | } 123 | 124 | public func withReadLock(body: () throws -> T) rethrows -> T { 125 | pthread_rwlock_rdlock(rwlock) 126 | defer { 127 | pthread_rwlock_unlock(rwlock) 128 | } 129 | return try body() 130 | } 131 | 132 | public func withWriteLock(body: () throws -> T) rethrows -> T { 133 | pthread_rwlock_wrlock(rwlock) 134 | defer { 135 | pthread_rwlock_unlock(rwlock) 136 | } 137 | return try body() 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /Sources/Primer/Serialization/DictionaryCodable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public typealias EncodedDictionary = [String: Any] 4 | 5 | // MARK: - DictionaryEncoder 6 | 7 | open class DictionaryEncoder: Encoder { 8 | open var codingPath: [CodingKey] = [] 9 | open var userInfo: [CodingUserInfoKey: Any] = [:] 10 | private var storage = Storage() 11 | 12 | public init() {} 13 | 14 | open func container(keyedBy type: Key.Type) -> KeyedEncodingContainer { 15 | KeyedEncodingContainer(KeyedContainer(encoder: self, codingPath: codingPath)) 16 | } 17 | 18 | open func unkeyedContainer() -> UnkeyedEncodingContainer { 19 | UnkeyedContanier(encoder: self, codingPath: codingPath) 20 | } 21 | 22 | open func singleValueContainer() -> SingleValueEncodingContainer { 23 | SingleValueContanier(encoder: self, codingPath: codingPath) 24 | } 25 | 26 | private func box(_ value: T) throws -> Any { 27 | /// @note: This results in a EXC_BAD_ACCESS on XCode 11.2 (works again in XCode 11.3). 28 | try value.encode(to: self) 29 | return storage.popContainer() 30 | } 31 | } 32 | 33 | extension DictionaryEncoder { 34 | open func encode(_ value: T) throws -> [String: Any] { 35 | do { 36 | return try castOrThrow([String: Any].self, try box(value)) 37 | } catch (let error) { 38 | throw EncodingError.invalidValue( 39 | value, 40 | EncodingError.Context( 41 | codingPath: [], 42 | debugDescription: "Top-evel \(T.self) did not encode any values.", 43 | underlyingError: error)) 44 | } 45 | } 46 | } 47 | 48 | extension DictionaryEncoder { 49 | private class KeyedContainer: KeyedEncodingContainerProtocol { 50 | private var encoder: DictionaryEncoder 51 | private(set) var codingPath: [CodingKey] 52 | private var storage: Storage 53 | 54 | init(encoder: DictionaryEncoder, codingPath: [CodingKey]) { 55 | self.encoder = encoder 56 | self.codingPath = codingPath 57 | self.storage = encoder.storage 58 | storage.push(container: [:] as [String: Any]) 59 | } 60 | 61 | deinit { 62 | guard let dictionary = storage.popContainer() as? [String: Any] else { 63 | assertionFailure() 64 | return 65 | } 66 | storage.push(container: dictionary) 67 | } 68 | 69 | private func set(_ value: Any, forKey key: String) { 70 | guard var dictionary = storage.popContainer() as? [String: Any] else { 71 | assertionFailure() 72 | return 73 | } 74 | dictionary[key] = value 75 | storage.push(container: dictionary) 76 | } 77 | 78 | func encodeNil(forKey key: Key) throws {} 79 | 80 | func encode( 81 | _ value: Bool, 82 | forKey key: Key 83 | ) throws { set(value, forKey: key.stringValue) } 84 | 85 | func encode( 86 | _ value: Int, 87 | forKey key: Key 88 | ) throws { set(value, forKey: key.stringValue) } 89 | 90 | func encode( 91 | _ value: Int8, 92 | forKey key: Key 93 | ) throws { set(value, forKey: key.stringValue) } 94 | 95 | func encode( 96 | _ value: Int16, 97 | forKey key: Key 98 | ) throws { set(value, forKey: key.stringValue) } 99 | 100 | func encode( 101 | _ value: Int32, 102 | forKey key: Key 103 | ) throws { set(value, forKey: key.stringValue) } 104 | 105 | func encode( 106 | _ value: Int64, 107 | forKey key: Key 108 | ) throws { set(value, forKey: key.stringValue) } 109 | 110 | func encode( 111 | _ value: UInt, 112 | forKey key: Key 113 | ) throws { set(value, forKey: key.stringValue) } 114 | 115 | func encode( 116 | _ value: UInt8, 117 | forKey key: Key 118 | ) throws { set(value, forKey: key.stringValue) } 119 | 120 | func encode( 121 | _ value: UInt16, 122 | forKey key: Key 123 | ) throws { set(value, forKey: key.stringValue) } 124 | 125 | func encode( 126 | _ value: UInt32, 127 | forKey key: Key 128 | ) throws { set(value, forKey: key.stringValue) } 129 | 130 | func encode( 131 | _ value: UInt64, 132 | forKey key: Key 133 | ) throws { set(value, forKey: key.stringValue) } 134 | 135 | func encode( 136 | _ value: Float, 137 | forKey key: Key 138 | ) throws { set(value, forKey: key.stringValue) } 139 | 140 | func encode( 141 | _ value: Double, 142 | forKey key: Key 143 | ) throws { set(value, forKey: key.stringValue) } 144 | 145 | func encode( 146 | value: String, 147 | forKey key: Key 148 | ) throws { set(value, forKey: key.stringValue) } 149 | 150 | func encode( 151 | _ value: T, 152 | forKey key: Key 153 | ) throws { 154 | encoder.codingPath.append(key) 155 | defer { encoder.codingPath.removeLast() } 156 | set(try encoder.box(value), forKey: key.stringValue) 157 | } 158 | 159 | func nestedContainer( 160 | keyedBy keyType: NestedKey.Type, 161 | forKey key: Key 162 | ) -> KeyedEncodingContainer { 163 | codingPath.append(key) 164 | defer { codingPath.removeLast() } 165 | return KeyedEncodingContainer( 166 | KeyedContainer( 167 | encoder: encoder, 168 | codingPath: codingPath)) 169 | } 170 | 171 | func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { 172 | codingPath.append(key) 173 | defer { codingPath.removeLast() } 174 | return UnkeyedContanier(encoder: encoder, codingPath: codingPath) 175 | } 176 | 177 | func superEncoder() -> Encoder { encoder } 178 | 179 | func superEncoder(forKey key: Key) -> Encoder { encoder } 180 | } 181 | 182 | private class UnkeyedContanier: UnkeyedEncodingContainer { 183 | var encoder: DictionaryEncoder 184 | private(set) var codingPath: [CodingKey] 185 | private var storage: Storage 186 | var count: Int { return storage.count } 187 | 188 | init(encoder: DictionaryEncoder, codingPath: [CodingKey]) { 189 | self.encoder = encoder 190 | self.codingPath = codingPath 191 | self.storage = encoder.storage 192 | storage.push(container: [] as [Any]) 193 | } 194 | 195 | deinit { 196 | guard let array = storage.popContainer() as? [Any] else { 197 | assertionFailure() 198 | return 199 | } 200 | storage.push(container: array) 201 | } 202 | 203 | private func push(_ value: Any) { 204 | guard var array = storage.popContainer() as? [Any] else { 205 | assertionFailure() 206 | return 207 | } 208 | array.append(value) 209 | storage.push(container: array) 210 | } 211 | 212 | func encodeNil() throws {} 213 | 214 | func encode( 215 | _ value: Bool 216 | ) throws {} 217 | 218 | func encode( 219 | _ value: Int 220 | ) throws { push(try encoder.box(value)) } 221 | 222 | func encode( 223 | _ value: Int8 224 | ) throws { push(try encoder.box(value)) } 225 | 226 | func encode( 227 | _ value: Int16 228 | ) throws { push(try encoder.box(value)) } 229 | 230 | func encode( 231 | _ value: Int32 232 | ) throws { push(try encoder.box(value)) } 233 | 234 | func encode( 235 | _ value: Int64 236 | ) throws { push(try encoder.box(value)) } 237 | 238 | func encode( 239 | _ value: UInt 240 | ) throws { push(try encoder.box(value)) } 241 | 242 | func encode( 243 | _ value: UInt8 244 | ) throws { push(try encoder.box(value)) } 245 | 246 | func encode( 247 | _ value: UInt16 248 | ) throws { push(try encoder.box(value)) } 249 | 250 | func encode( 251 | _ value: UInt32 252 | ) throws { push(try encoder.box(value)) } 253 | 254 | func encode( 255 | _ value: UInt64 256 | ) throws { push(try encoder.box(value)) } 257 | 258 | func encode( 259 | _ value: Float 260 | ) throws { push(try encoder.box(value)) } 261 | 262 | func encode( 263 | _ value: Double 264 | ) throws { push(try encoder.box(value)) } 265 | 266 | func encode( 267 | _ value: String 268 | ) throws { push(try encoder.box(value)) } 269 | 270 | func encode(_ value: T) throws { 271 | encoder.codingPath.append(AnyCodingKey(index: count)) 272 | defer { encoder.codingPath.removeLast() } 273 | push(try encoder.box(value)) 274 | } 275 | 276 | func nestedContainer( 277 | keyedBy keyType: NestedKey.Type 278 | ) -> KeyedEncodingContainer where NestedKey: CodingKey { 279 | codingPath.append(AnyCodingKey(index: count)) 280 | defer { codingPath.removeLast() } 281 | return KeyedEncodingContainer( 282 | KeyedContainer( 283 | encoder: encoder, 284 | codingPath: codingPath)) 285 | } 286 | 287 | func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { 288 | codingPath.append(AnyCodingKey(index: count)) 289 | defer { codingPath.removeLast() } 290 | return UnkeyedContanier(encoder: encoder, codingPath: codingPath) 291 | } 292 | 293 | func superEncoder() -> Encoder { 294 | return encoder 295 | } 296 | } 297 | 298 | private class SingleValueContanier: SingleValueEncodingContainer { 299 | var encoder: DictionaryEncoder 300 | private(set) var codingPath: [CodingKey] 301 | private var storage: Storage 302 | var count: Int { return storage.count } 303 | 304 | init(encoder: DictionaryEncoder, codingPath: [CodingKey]) { 305 | self.encoder = encoder 306 | self.codingPath = codingPath 307 | self.storage = encoder.storage 308 | } 309 | 310 | private func push(_ value: Any) { 311 | guard var array = storage.popContainer() as? [Any] else { 312 | assertionFailure() 313 | return 314 | } 315 | array.append(value) 316 | storage.push(container: array) 317 | } 318 | 319 | func encodeNil() throws {} 320 | 321 | func encode( 322 | _ value: Bool 323 | ) throws { storage.push(container: value) } 324 | 325 | func encode( 326 | _ value: Int 327 | ) throws { storage.push(container: value) } 328 | 329 | func encode( 330 | _ value: Int8 331 | ) throws { storage.push(container: value) } 332 | 333 | func encode( 334 | _ value: Int16 335 | ) throws { storage.push(container: value) } 336 | 337 | func encode( 338 | _ value: Int32 339 | ) throws { storage.push(container: value) } 340 | 341 | func encode( 342 | _ value: Int64 343 | ) throws { storage.push(container: value) } 344 | 345 | func encode( 346 | _ value: UInt 347 | ) throws { storage.push(container: value) } 348 | 349 | func encode( 350 | _ value: UInt8 351 | ) throws { storage.push(container: value) } 352 | 353 | func encode( 354 | _ value: UInt16 355 | ) throws { storage.push(container: value) } 356 | 357 | func encode( 358 | _ value: UInt32 359 | ) throws { storage.push(container: value) } 360 | 361 | func encode( 362 | _ value: UInt64 363 | ) throws { storage.push(container: value) } 364 | 365 | func encode( 366 | _ value: Float 367 | ) throws { storage.push(container: value) } 368 | 369 | func encode( 370 | _ value: Double 371 | ) throws { storage.push(container: value) } 372 | 373 | func encode( 374 | _ value: String 375 | ) throws { storage.push(container: value) } 376 | 377 | func encode(_ value: T) throws { 378 | storage.push(container: try encoder.box(value)) 379 | } 380 | } 381 | } 382 | 383 | // MARK: - DictionaryDecoder 384 | 385 | open class DictionaryDecoder: Decoder { 386 | open var codingPath: [CodingKey] 387 | open var userInfo: [CodingUserInfoKey: Any] = [:] 388 | private var storage = Storage() 389 | 390 | public init() { 391 | codingPath = [] 392 | } 393 | 394 | public init(container: Any, codingPath: [CodingKey] = []) { 395 | storage.push(container: container) 396 | self.codingPath = codingPath 397 | } 398 | 399 | open func container( 400 | keyedBy type: Key.Type 401 | ) throws -> KeyedDecodingContainer { 402 | let container = try lastContainer(forType: [String: Any].self) 403 | return KeyedDecodingContainer( 404 | KeyedContainer( 405 | decoder: self, 406 | codingPath: [], 407 | container: container)) 408 | } 409 | 410 | open func unkeyedContainer() throws -> UnkeyedDecodingContainer { 411 | let container = try lastContainer(forType: [Any].self) 412 | return UnkeyedContanier(decoder: self, container: container) 413 | } 414 | 415 | open func singleValueContainer() throws -> SingleValueDecodingContainer { 416 | return SingleValueContanier(decoder: self) 417 | } 418 | 419 | private func unbox(_ value: Any, as type: T.Type) throws -> T { 420 | return try unbox(value, as: type, codingPath: codingPath) 421 | } 422 | 423 | private func unbox( 424 | _ value: Any, 425 | as type: T.Type, 426 | codingPath: [CodingKey] 427 | ) throws -> T { 428 | let description = "Expected to decode \(type) but found \(Swift.type(of: value)) instead." 429 | let error = DecodingError.typeMismatch( 430 | T.self, 431 | DecodingError.Context(codingPath: codingPath, debugDescription: description)) 432 | return try castOrThrow(T.self, value, error: error) 433 | } 434 | 435 | private func unbox(_ value: Any, as type: T.Type) throws -> T { 436 | return try unbox(value, as: type, codingPath: codingPath) 437 | } 438 | 439 | private func unbox( 440 | _ value: Any, 441 | as type: T.Type, 442 | codingPath: [CodingKey] 443 | ) throws -> T { 444 | let description = "Expected to decode \(type) but found \(Swift.type(of: value)) instead." 445 | let error = DecodingError.typeMismatch( 446 | T.self, 447 | DecodingError.Context(codingPath: codingPath, debugDescription: description)) 448 | do { 449 | return try castOrThrow(T.self, value, error: error) 450 | } catch { 451 | storage.push(container: value) 452 | defer { _ = storage.popContainer() } 453 | return try T(from: self) 454 | } 455 | } 456 | 457 | private func lastContainer(forType type: T.Type) throws -> T { 458 | guard let value = storage.last else { 459 | let description = "Expected \(type) but found nil value instead." 460 | let error = DecodingError.Context(codingPath: codingPath, debugDescription: description) 461 | throw DecodingError.valueNotFound(type, error) 462 | } 463 | return try unbox(value, as: T.self) 464 | } 465 | 466 | private func lastContainer(forType type: T.Type) throws -> T { 467 | guard let value = storage.last else { 468 | let description = "Expected \(type) but found nil value instead." 469 | let error = DecodingError.Context(codingPath: codingPath, debugDescription: description) 470 | throw DecodingError.valueNotFound(type, error) 471 | } 472 | return try unbox(value, as: T.self) 473 | } 474 | 475 | private func notFound(key: CodingKey) -> DecodingError { 476 | let error = DecodingError.Context( 477 | codingPath: codingPath, 478 | debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").") 479 | return DecodingError.keyNotFound(key, error) 480 | } 481 | } 482 | 483 | extension DictionaryDecoder { 484 | open func decode(_ type: T.Type, from container: Any) throws -> T { 485 | storage.push(container: container) 486 | return try unbox(container, as: T.self) 487 | } 488 | } 489 | 490 | extension DictionaryDecoder { 491 | private class KeyedContainer: KeyedDecodingContainerProtocol { 492 | private var decoder: DictionaryDecoder 493 | private(set) var codingPath: [CodingKey] 494 | private var container: [String: Any] 495 | 496 | init(decoder: DictionaryDecoder, codingPath: [CodingKey], container: [String: Any]) { 497 | self.decoder = decoder 498 | self.codingPath = codingPath 499 | self.container = container 500 | } 501 | 502 | var allKeys: [Key] { return container.keys.compactMap { Key(stringValue: $0) } } 503 | func contains(_ key: Key) -> Bool { return container[key.stringValue] != nil } 504 | 505 | private func find(forKey key: CodingKey) throws -> Any { 506 | return try container.tryValue(forKey: key.stringValue, error: decoder.notFound(key: key)) 507 | } 508 | 509 | func _decode(_ type: T.Type, forKey key: Key) throws -> T { 510 | let value = try find(forKey: key) 511 | decoder.codingPath.append(key) 512 | defer { decoder.codingPath.removeLast() } 513 | return try decoder.unbox(value, as: T.self) 514 | } 515 | 516 | func decodeNil( 517 | forKey key: Key 518 | ) throws -> Bool { 519 | // TODO: Verify, this broke in Xcode13b2. 520 | // decoder.notFound(key: key) 521 | return true 522 | } 523 | 524 | func decode( 525 | _ type: Bool.Type, 526 | forKey key: Key 527 | ) throws -> Bool { try _decode(type, forKey: key) } 528 | 529 | func decode( 530 | _ type: Int.Type, 531 | forKey key: Key 532 | ) throws -> Int { try _decode(type, forKey: key) } 533 | 534 | func decode( 535 | _ type: Int8.Type, 536 | forKey key: Key 537 | ) throws -> Int8 { try _decode(type, forKey: key) } 538 | 539 | func decode( 540 | _ type: Int16.Type, 541 | forKey key: Key 542 | ) throws -> Int16 { try _decode(type, forKey: key) } 543 | 544 | func decode( 545 | _ type: Int32.Type, 546 | forKey key: Key 547 | ) throws -> Int32 { try _decode(type, forKey: key) } 548 | 549 | func decode( 550 | _ type: Int64.Type, 551 | forKey key: Key 552 | ) throws -> Int64 { try _decode(type, forKey: key) } 553 | 554 | func decode( 555 | _ type: UInt.Type, 556 | forKey key: Key 557 | ) throws -> UInt { try _decode(type, forKey: key) } 558 | 559 | func decode( 560 | _ type: UInt8.Type, 561 | forKey key: Key 562 | ) throws -> UInt8 { try _decode(type, forKey: key) } 563 | 564 | func decode( 565 | _ type: UInt16.Type, 566 | forKey key: Key 567 | ) throws -> UInt16 { try _decode(type, forKey: key) } 568 | 569 | func decode( 570 | _ type: UInt32.Type, 571 | forKey key: Key 572 | ) throws -> UInt32 { try _decode(type, forKey: key) } 573 | 574 | func decode( 575 | _ type: UInt64.Type, 576 | forKey key: Key 577 | ) throws -> UInt64 { try _decode(type, forKey: key) } 578 | 579 | func decode( 580 | _ type: Float.Type, 581 | forKey key: Key 582 | ) throws -> Float { try _decode(type, forKey: key) } 583 | 584 | func decode( 585 | _ type: Double.Type, 586 | forKey key: Key 587 | ) throws -> Double { try _decode(type, forKey: key) } 588 | 589 | func decode( 590 | _ type: String.Type, 591 | forKey key: Key 592 | ) throws -> String { try _decode(type, forKey: key) } 593 | 594 | func decode( 595 | _ type: T.Type, 596 | forKey key: Key 597 | ) throws -> T { try _decode(type, forKey: key) } 598 | 599 | func nestedContainer( 600 | keyedBy type: NestedKey.Type, 601 | forKey key: Key 602 | ) throws -> KeyedDecodingContainer where NestedKey: CodingKey { 603 | decoder.codingPath.append(key) 604 | defer { decoder.codingPath.removeLast() } 605 | let value = try find(forKey: key) 606 | let dictionary = try decoder.unbox(value, as: [String: Any].self) 607 | return KeyedDecodingContainer( 608 | KeyedContainer( 609 | decoder: decoder, 610 | codingPath: [], 611 | container: dictionary)) 612 | } 613 | 614 | func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { 615 | decoder.codingPath.append(key) 616 | defer { decoder.codingPath.removeLast() } 617 | let value = try find(forKey: key) 618 | let array = try decoder.unbox(value, as: [Any].self) 619 | return UnkeyedContanier(decoder: decoder, container: array) 620 | } 621 | 622 | func _superDecoder(forKey key: CodingKey = AnyCodingKey.super) throws -> Decoder { 623 | decoder.codingPath.append(key) 624 | defer { decoder.codingPath.removeLast() } 625 | 626 | let value = try find(forKey: key) 627 | return DictionaryDecoder(container: value, codingPath: decoder.codingPath) 628 | } 629 | 630 | func superDecoder() throws -> Decoder { 631 | return try _superDecoder() 632 | } 633 | 634 | func superDecoder(forKey key: Key) throws -> Decoder { 635 | return try _superDecoder(forKey: key) 636 | } 637 | } 638 | 639 | private class UnkeyedContanier: UnkeyedDecodingContainer { 640 | private var decoder: DictionaryDecoder 641 | private(set) var codingPath: [CodingKey] 642 | private var container: [Any] 643 | 644 | var count: Int? { return container.count } 645 | var isAtEnd: Bool { return currentIndex >= count! } 646 | 647 | private(set) var currentIndex: Int 648 | 649 | private var currentCodingPath: [CodingKey] { 650 | return decoder.codingPath + [AnyCodingKey(index: currentIndex)] 651 | } 652 | 653 | init(decoder: DictionaryDecoder, container: [Any]) { 654 | self.decoder = decoder 655 | self.codingPath = decoder.codingPath 656 | self.container = container 657 | currentIndex = 0 658 | } 659 | 660 | private func checkIndex(_ type: T.Type) throws { 661 | if isAtEnd { 662 | let error = DecodingError.Context( 663 | codingPath: currentCodingPath, 664 | debugDescription: "container is at end.") 665 | throw DecodingError.valueNotFound(T.self, error) 666 | } 667 | } 668 | 669 | func _decode(_ type: T.Type) throws -> T { 670 | try checkIndex(type) 671 | 672 | decoder.codingPath.append(AnyCodingKey(index: currentIndex)) 673 | defer { 674 | decoder.codingPath.removeLast() 675 | currentIndex += 1 676 | } 677 | return try decoder.unbox(container[currentIndex], as: T.self) 678 | } 679 | 680 | func decodeNil() throws -> Bool { 681 | try checkIndex(Any?.self) 682 | return false 683 | } 684 | 685 | func decode(_ type: Bool.Type) throws -> Bool { return try _decode(type) } 686 | func decode(_ type: Int.Type) throws -> Int { return try _decode(type) } 687 | func decode(_ type: Int8.Type) throws -> Int8 { return try _decode(type) } 688 | func decode(_ type: Int16.Type) throws -> Int16 { return try _decode(type) } 689 | func decode(_ type: Int32.Type) throws -> Int32 { return try _decode(type) } 690 | func decode(_ type: Int64.Type) throws -> Int64 { return try _decode(type) } 691 | func decode(_ type: UInt.Type) throws -> UInt { return try _decode(type) } 692 | func decode(_ type: UInt8.Type) throws -> UInt8 { return try _decode(type) } 693 | func decode(_ type: UInt16.Type) throws -> UInt16 { return try _decode(type) } 694 | func decode(_ type: UInt32.Type) throws -> UInt32 { return try _decode(type) } 695 | func decode(_ type: UInt64.Type) throws -> UInt64 { return try _decode(type) } 696 | func decode(_ type: Float.Type) throws -> Float { return try _decode(type) } 697 | func decode(_ type: Double.Type) throws -> Double { return try _decode(type) } 698 | func decode(_ type: String.Type) throws -> String { return try _decode(type) } 699 | func decode(_ type: T.Type) throws -> T { return try _decode(type) } 700 | 701 | func nestedContainer( 702 | keyedBy type: NestedKey.Type 703 | ) throws -> KeyedDecodingContainer { 704 | decoder.codingPath.append(AnyCodingKey(index: currentIndex)) 705 | defer { decoder.codingPath.removeLast() } 706 | try checkIndex(UnkeyedContanier.self) 707 | let value = container[currentIndex] 708 | let dictionary = try castOrThrow([String: Any].self, value) 709 | currentIndex += 1 710 | return KeyedDecodingContainer( 711 | KeyedContainer( 712 | decoder: decoder, 713 | codingPath: [], 714 | container: dictionary)) 715 | } 716 | 717 | func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { 718 | decoder.codingPath.append(AnyCodingKey(index: currentIndex)) 719 | defer { decoder.codingPath.removeLast() } 720 | try checkIndex(UnkeyedContanier.self) 721 | let value = container[currentIndex] 722 | let array = try castOrThrow([Any].self, value) 723 | currentIndex += 1 724 | return UnkeyedContanier(decoder: decoder, container: array) 725 | } 726 | 727 | func superDecoder() throws -> Decoder { 728 | decoder.codingPath.append(AnyCodingKey(index: currentIndex)) 729 | defer { decoder.codingPath.removeLast() } 730 | try checkIndex(UnkeyedContanier.self) 731 | let value = container[currentIndex] 732 | currentIndex += 1 733 | return DictionaryDecoder(container: value, codingPath: decoder.codingPath) 734 | } 735 | } 736 | 737 | private class SingleValueContanier: SingleValueDecodingContainer { 738 | private var decoder: DictionaryDecoder 739 | private(set) var codingPath: [CodingKey] 740 | 741 | init(decoder: DictionaryDecoder) { 742 | self.decoder = decoder 743 | self.codingPath = decoder.codingPath 744 | } 745 | 746 | func _decode(_ type: T.Type) throws -> T { 747 | return try decoder.lastContainer(forType: type) 748 | } 749 | 750 | func decodeNil() -> Bool { return decoder.storage.last == nil } 751 | func decode(_ type: Bool.Type) throws -> Bool { return try _decode(type) } 752 | func decode(_ type: Int.Type) throws -> Int { return try _decode(type) } 753 | func decode(_ type: Int8.Type) throws -> Int8 { return try _decode(type) } 754 | func decode(_ type: Int16.Type) throws -> Int16 { return try _decode(type) } 755 | func decode(_ type: Int32.Type) throws -> Int32 { return try _decode(type) } 756 | func decode(_ type: Int64.Type) throws -> Int64 { return try _decode(type) } 757 | func decode(_ type: UInt.Type) throws -> UInt { return try _decode(type) } 758 | func decode(_ type: UInt8.Type) throws -> UInt8 { return try _decode(type) } 759 | func decode(_ type: UInt16.Type) throws -> UInt16 { return try _decode(type) } 760 | func decode(_ type: UInt32.Type) throws -> UInt32 { return try _decode(type) } 761 | func decode(_ type: UInt64.Type) throws -> UInt64 { return try _decode(type) } 762 | func decode(_ type: Float.Type) throws -> Float { return try _decode(type) } 763 | func decode(_ type: Double.Type) throws -> Double { return try _decode(type) } 764 | func decode(_ type: String.Type) throws -> String { return try _decode(type) } 765 | func decode(_ type: T.Type) throws -> T { return try _decode(type) } 766 | } 767 | } 768 | 769 | // MARK: - Helpers 770 | 771 | final class Storage { 772 | private(set) var containers: [Any] = [] 773 | 774 | var count: Int { 775 | containers.count 776 | } 777 | 778 | var last: Any? { 779 | containers.last 780 | } 781 | 782 | func push(container: Any) { 783 | containers.append(container) 784 | } 785 | 786 | @discardableResult func popContainer() -> Any { 787 | precondition(containers.count > 0, "Empty container stack.") 788 | return containers.popLast()! 789 | } 790 | } 791 | 792 | public enum DictionaryCodableError: Error { 793 | case cast 794 | case unwrapped 795 | case tryValue 796 | } 797 | 798 | func castOrThrow( 799 | _ resultType: T.Type, 800 | _ object: Any, 801 | error: Error = DictionaryCodableError.cast 802 | ) throws -> T { 803 | guard let returnValue = object as? T else { 804 | throw error 805 | } 806 | return returnValue 807 | } 808 | 809 | extension Optional { 810 | func unwrapOrThrow(error: Error = DictionaryCodableError.unwrapped) throws -> Wrapped { 811 | guard let unwrapped = self else { 812 | throw error 813 | } 814 | return unwrapped 815 | } 816 | } 817 | 818 | extension Dictionary { 819 | func tryValue(forKey key: Key, error: Error = DictionaryCodableError.tryValue) throws -> Value { 820 | guard let value = self[key] else { throw error } 821 | return value 822 | } 823 | } 824 | 825 | struct AnyCodingKey: CodingKey { 826 | public var stringValue: String 827 | public var intValue: Int? 828 | 829 | public init?(stringValue: String) { 830 | self.stringValue = stringValue 831 | self.intValue = nil 832 | } 833 | 834 | public init?(intValue: Int) { 835 | self.stringValue = "\(intValue)" 836 | self.intValue = intValue 837 | } 838 | 839 | init(index: Int) { 840 | self.stringValue = "Index \(index)" 841 | self.intValue = index 842 | } 843 | 844 | static let `super` = AnyCodingKey(stringValue: "super")! 845 | } 846 | 847 | func dynamicEqual(lhs: Any?, rhs: Any?) -> Bool { 848 | if let lhs = lhs as? NSNumber, let rhs = rhs as? NSNumber { 849 | return lhs == rhs 850 | } 851 | if let lhs = lhs as? String, let rhs = rhs as? String { 852 | return lhs == rhs 853 | } 854 | if let lhs = lhs as? NSArray, let rhs = rhs as? NSArray { 855 | return lhs == rhs 856 | } 857 | if let lhs = lhs as? NSDate, let rhs = rhs as? NSDate { 858 | return lhs == rhs 859 | } 860 | return false 861 | } 862 | 863 | func dynamicEncode(value: Any, encoder: Encoder) throws { 864 | // TODO 865 | } 866 | 867 | fileprivate func isEqual(type: T.Type, a: Any, b: Any) -> Bool { 868 | guard let a = a as? T, let b = b as? T else { return false } 869 | return a == b 870 | } 871 | --------------------------------------------------------------------------------