├── 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 [](#)
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 |
--------------------------------------------------------------------------------