├── .gitignore ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── TwoWayMirror.playground ├── Contents.swift ├── Sources │ └── TwoWayMirror.swift └── contents.xcplayground ├── TwoWayMirror.podspec ├── TwoWayMirror.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── TwoWayMirror.xcworkspace └── contents.xcworkspacedata ├── TwoWayMirror ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── DetailViewController.swift ├── Info.plist └── MasterViewController.swift └── TwoWayMirrorTests ├── Info.plist └── TwoWayMirrorTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | *Library/* 3 | *xcuserdata* 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 John Holdsworth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by John Holdsworth on 14/03/2017. 3 | // 4 | 5 | import PackageDescription 6 | 7 | let package = Package( 8 | name: "TwoWayMirror" 9 | ) 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TwoWayMirror - bidirectional Swift Mirror 2 | 3 | *** [Recent changes to Swift master](https://github.com/apple/swift/pull/14678) indicate this is unlikely to work after Swift 5 *** 4 | 5 | It's a frustrating limitation of Swift reflection that the [Mirror](http://nshipster.com/mirrortype/) type 6 | can be only used in one direction for reading values from Swift data structures. This project leverages 7 | Swift's internal implementation to remove this limitation by falling back to the original underlying 8 | [RefelectionLegacy.swift](https://github.com/apple/swift/blob/master/stdlib/public/core/ReflectionLegacy.swift#L86) 9 | functionality. Think runtime typed keypaths on steroids. 10 | 11 | The [basic api](TwoWayMirror.playground/Sources/TwoWayMirror.swift) declares the following entry point: 12 | 13 | ```Swift 14 | public func reflect(object: AnyObject, path: String, type: T.Type) -> UnsafeMutablePointer 15 | ``` 16 | This will return a typed pointer to any ivar of a class object or it's containing structs, enums, collections 17 | that can be read or assigned to as if you were using a typed keypath. 18 | A subscript is defined on any class derived from NSObject for a Swift valueForKey: replacement. 19 | 20 | ```Swift 21 | public protocol SubScriptReflectable: AnyObject {} 22 | extension NSObject: SubScriptReflectable {} 23 | public extension SubScriptReflectable { 24 | public subscript (path: String, type: T.Type) -> T { 25 | get { 26 | return TwoWayMirror.reflect(object: self, path: path, type: T.self).pointee 27 | } 28 | set(newValue) { 29 | TwoWayMirror.reflect(object: self, path: path, type: T.self).pointee = newValue 30 | } 31 | } 32 | } 33 | ``` 34 | 35 | Example usage: 36 | 37 | ```Swift 38 | enum ExampleEnum: TwoWayEnum { 39 | case one, two(str: String), three(int: Int), four(int: Int, int2: Int) 40 | 41 | static func twDecode(data: inout TwoWayMirror, from: [String: Any]) throws { 42 | let ptr = data.pointer(type: ExampleEnum.self) 43 | switch from["case"] as! String { 44 | case "one": 45 | ptr.pointee = .one 46 | case "two": 47 | ptr.pointee = .two(str: from["let"] as! String) 48 | case "three": 49 | ptr.pointee = .three(int: from["let"] as! Int) 50 | case "four": 51 | ptr.pointee = .four(int: from["int"] as! Int, 52 | int2: from["int2"] as! Int) 53 | default: 54 | throw NSError(domain: "ExampleEnum", code: -1, 55 | userInfo: [NSLocalizedDescriptionKey: 56 | "Invalid case in: \(from)"]) 57 | } 58 | } 59 | } 60 | struct ExampleStruct { 61 | let i = 123 62 | } 63 | struct ContainableStruct: TwoWayContainable { 64 | var a1 = 0, a2 = 1 65 | } 66 | final class ExampleClass: NSObject, TwoWayContainable { 67 | let a = [98.0] 68 | let b = 199.0 69 | let c = "Hello" 70 | let d = ExampleStruct() 71 | let e = ExampleEnum.four(int: 1, int2: 9) 72 | let f = Date() 73 | let g = ["A", "B"] 74 | let h: [ContainableStruct]? = nil 75 | let i = [Int]() 76 | let j: [Int]? = nil 77 | let k: ContainableStruct? = nil 78 | let l = [[123, 123], [234, 234]] 79 | let m = ["a": [123, 123], "b": [234, 234]] 80 | let n = ["a": ContainableStruct(), "b": ContainableStruct()] 81 | let o = [["a": [123, 123], "b": [234, 234]], ["a": [123, 123], "b": [234, 234]]] 82 | deinit { 83 | print("deinit") 84 | } 85 | } 86 | 87 | if true { 88 | let instance = ExampleClass() 89 | 90 | print(TwoWayMirror.reflectKeys(any: instance)) 91 | print(TwoWayMirror.reflectKeys(any: instance, path: "d")) 92 | 93 | TwoWayMirror.reflect(object: instance, path: "a", type: [Double].self).pointee += [11.0] 94 | print(instance["a", [Double].self]) 95 | 96 | instance["b", Double.self] += 100.0 97 | print(instance.b) 98 | 99 | instance["c", String.self] += " String" 100 | print(instance.c) 101 | 102 | instance["d.i", Int.self] += 345 103 | print(instance.d.i) 104 | 105 | instance["e", ExampleEnum.self] = .two(str: "TWO") 106 | print(instance.e) 107 | 108 | instance["f", Date.self] = Date() 109 | print(instance["f", Date.self]) 110 | 111 | let data = """ 112 | [ 113 | { 114 | "a1": 11, "a2": 22 115 | }, 116 | { 117 | "a1": 111, "a2": 222 118 | } 119 | ] 120 | """.data(using: .utf8)! 121 | 122 | let array = try! TwoWayMirror.decode([ContainableStruct].self, from: data) 123 | dump(array) 124 | } 125 | ``` 126 | 127 | This has been used to produce an alternative implementation of Codable for working with JSON. 128 | 129 | ```Swift 130 | let data = """ 131 | { 132 | "a": [77.0, 88.0], 133 | "b": 999.0, 134 | "c": "hello", 135 | "d": { 136 | "i": 789 137 | }, 138 | "f": "2018-02-14 06:39:41 +0000", 139 | "g": ["Hello", "World"], 140 | "h": [ 141 | { 142 | "a1": 11, "a2": 22 143 | }, 144 | { 145 | "a1": 111, "a2": 222 146 | } 147 | ], 148 | "i": [12345, 67890], 149 | "j": [99, 101], 150 | "k": { 151 | "a1": 1111, "a2": 2222 152 | }, 153 | "m" : { 154 | "b" : [ 155 | 111, 156 | 222 157 | ], 158 | "a" : [ 159 | 333, 160 | 444 161 | ] 162 | }, 163 | "n" : { 164 | "b" : { 165 | "a2" : 1, 166 | "a1" : 2 167 | }, 168 | "a" : { 169 | "a2" : 3, 170 | "a1" : 4 171 | } 172 | }, 173 | } 174 | """.data(using: .utf8)! 175 | 176 | let start = Date.timeIntervalSinceReferenceDate 177 | for _ in 0..<10 { 178 | let i1 = ExampleClass() 179 | try! TwoWayMirror.decode(object: i1, json: data) 180 | dump(i1) 181 | let json = try! TwoWayMirror.encode(object: i1, options: [.prettyPrinted]) 182 | print(String(data: json, encoding: .utf8)!) 183 | let i2 = try! TwoWayMirror.decode(ExampleClass.self, from: json) 184 | dump(i2) 185 | } 186 | print(Date.timeIntervalSinceReferenceDate-start) 187 | ``` 188 | 189 | The JSON implementation will decode and encode composed structs and class instances, 190 | Ints, Doubles and String along with Arrays or Optionals of these and Arrays or Optionals of 191 | structs or class instances which implement the `TwoWayContainable` protocol (which just 192 | requires they have an init() methhod.) For writing using reflection to work (decoding) the 193 | top level object must be an instance of a class. Otherwise, a copy is taken when the object 194 | is reflected and any changes will be lost. 195 | 196 | Automatic encoding of enums is possible but for decoding you must opt-in to the `TwoWayEnum` 197 | protocol and supply an implementation to initialise an enum from a dictionary. 198 | 199 | While this approach bends a few rules it has proven to be robust making very few 200 | assumptions about the Swift reflection implementation. 201 | -------------------------------------------------------------------------------- /Sources: -------------------------------------------------------------------------------- 1 | TwoWayMirror.playground/Sources -------------------------------------------------------------------------------- /TwoWayMirror.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | 3 | import Foundation 4 | 5 | enum ExampleEnum: TwoWayEnum { 6 | case one, two(str: String), three(int: Int), four(int: Int, int2: Int) 7 | 8 | static func twDecode(data: inout TwoWayMirror, from: [String: Any]) throws { 9 | let ptr = data.pointer(type: ExampleEnum.self) 10 | switch from["case"] as! String { 11 | case "one": 12 | ptr.pointee = .one 13 | case "two": 14 | ptr.pointee = .two(str: from["let"] as! String) 15 | case "three": 16 | ptr.pointee = .three(int: from["let"] as! Int) 17 | case "four": 18 | ptr.pointee = .four(int: from["int"] as! Int, 19 | int2: from["int2"] as! Int) 20 | default: 21 | throw NSError(domain: "ExampleEnum", code: -1, 22 | userInfo: [NSLocalizedDescriptionKey: 23 | "Invalid case in: \(from)"]) 24 | } 25 | } 26 | } 27 | struct ExampleStruct { 28 | let i = 123 29 | } 30 | struct ContainableStruct: TwoWayContainable { 31 | var a1 = 0, a2 = 1 32 | } 33 | final class ExampleClass: NSObject, TwoWayContainable { 34 | let a = [98.0] 35 | let b = 199.0 36 | let c = "Hello" 37 | let d = ExampleStruct() 38 | let e = ExampleEnum.four(int: 1, int2: 9) 39 | let f = Date() 40 | let g = ["A", "B"] 41 | let h: [ContainableStruct]? = nil 42 | let i = [Int]() 43 | let j: [Int]? = nil 44 | let k: ContainableStruct? = nil 45 | let l = [[123, 123], [234, 234]] 46 | let m = ["a": [123, 123], "b": [234, 234]] 47 | let n = ["a": ContainableStruct(), "b": ContainableStruct()] 48 | let o = [["a": [123, 123], "b": [234, 234]], ["a": [123, 123], "b": [234, 234]]] 49 | deinit { 50 | print("deinit") 51 | } 52 | } 53 | 54 | if true { 55 | let instance = ExampleClass() 56 | 57 | print(TwoWayMirror.reflectKeys(any: instance)) 58 | print(TwoWayMirror.reflectKeys(any: instance, path: "d")) 59 | 60 | TwoWayMirror.reflect(object: instance, path: "a", type: [Double].self).pointee += [11.0] 61 | print(instance["a", [Double].self]) 62 | 63 | instance["b", Double.self] += 100.0 64 | print(instance.b) 65 | 66 | instance["c", String.self] += " String" 67 | print(instance.c) 68 | 69 | instance["d.i", Int.self] += 345 70 | print(instance.d.i) 71 | 72 | instance["e", ExampleEnum.self] = .two(str: "TWO") 73 | print(instance.e) 74 | 75 | instance["f", Date.self] = Date() 76 | print(instance["f", Date.self]) 77 | 78 | let data = """ 79 | [ 80 | { 81 | "a1": 11, "a2": 22 82 | }, 83 | { 84 | "a1": 111, "a2": 222 85 | } 86 | ] 87 | """.data(using: .utf8)! 88 | 89 | let array = try! TwoWayMirror.decode([ContainableStruct].self, from: data) 90 | dump(array) 91 | } 92 | 93 | let data = """ 94 | { 95 | "a": [77.0, 88.0], 96 | "b": 999.0, 97 | "c": "hello", 98 | "d": { 99 | "i": 789 100 | }, 101 | "f": "2018-02-14 06:39:41 +0000", 102 | "g": ["Hello", "World"], 103 | "h": [ 104 | { 105 | "a1": 11, "a2": 22 106 | }, 107 | { 108 | "a1": 111, "a2": 222 109 | } 110 | ], 111 | "i": [12345, 67890], 112 | "j": [99, 101], 113 | "k": { 114 | "a1": 1111, "a2": 2222 115 | }, 116 | "m" : { 117 | "b" : [ 118 | 111, 119 | 222 120 | ], 121 | "a" : [ 122 | 333, 123 | 444 124 | ] 125 | }, 126 | "n" : { 127 | "b" : { 128 | "a2" : 1, 129 | "a1" : 2 130 | }, 131 | "a" : { 132 | "a2" : 3, 133 | "a1" : 4 134 | } 135 | }, 136 | } 137 | """.data(using: .utf8)! 138 | 139 | let start = Date.timeIntervalSinceReferenceDate 140 | for _ in 0..<10 { 141 | let i1 = ExampleClass() 142 | try! TwoWayMirror.decode(object: i1, json: data) 143 | dump(i1) 144 | let json = try! TwoWayMirror.encode(object: i1, options: [.prettyPrinted]) 145 | print(String(data: json, encoding: .utf8)!) 146 | let i2 = try! TwoWayMirror.decode(ExampleClass.self, from: json) 147 | dump(i2) 148 | } 149 | print(Date.timeIntervalSinceReferenceDate-start) 150 | -------------------------------------------------------------------------------- /TwoWayMirror.playground/Sources/TwoWayMirror.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TwoWayMirror.swift 3 | // TwoWayMirror 4 | // 5 | // Created by John Holdsworth on 13/02/2018. 6 | // Copyright © 2018 John Holdsworth. All rights reserved. 7 | // 8 | // $Id: //depot/TwoWayMirror/TwoWayMirror.playground/Sources/TwoWayMirror.swift#76 $ 9 | // 10 | 11 | import Foundation 12 | 13 | // MARK: Assumptions... 14 | // https://medium.com/@vhart/protocols-generics-and-existential-containers-wait-what-e2e698262ab1 15 | // https://github.com/apple/swift/blob/master/stdlib/public/core/ReflectionLegacy.swift#L86 16 | 17 | @_silgen_name("swift_reflectAny") 18 | internal func _reflect(_ x: T) -> _Mirror 19 | 20 | @_silgen_name("swift_EnumMirror_caseName") 21 | internal func _enumMirror_caseName( 22 | _ data: _MagicMirrorData) -> UnsafePointer 23 | 24 | // _Mirror is a protocol with concrete implementations _StructMirror, _ClassMirror etc 25 | // which fit into the 3 slots available in the "existential" data structure representing 26 | // the protocol so we can coerce a pointer to _Mirror to the internal "_MagicMirrorData" 27 | 28 | // MARK: Basic reflection API 29 | 30 | @_fixed_layout 31 | public struct TwoWayMirror { 32 | let owner: UnsafeRawPointer 33 | /// pointer to memory containing ivar 34 | public let ptr: UnsafeMutableRawPointer 35 | /// type represented at that memory location 36 | public let metadata: Any.Type 37 | // let protocolType: Any.Type 38 | // let protocolWitness: UnsafeRawPointer 39 | 40 | /// Cast data pointer to specific type 41 | /// 42 | /// - Parameters: 43 | /// - type: assumed type of ivar 44 | /// - Returns: typed pointer to ivar 45 | public func pointer(type: T.Type, file: StaticString = #file, line: UInt = #line) 46 | -> UnsafeMutablePointer { 47 | guard metadata == T.self else { 48 | fatalError("TwoWayMirror type mismatch: \(metadata) != \(T.self) at \(file)#\(line)") 49 | } 50 | return ptr.assumingMemoryBound(to: T.self) 51 | } 52 | 53 | public subscript(type: T.Type) -> T { 54 | get { 55 | return pointer(type: T.self).pointee 56 | } 57 | set(newValue) { 58 | pointer(type: T.self).pointee = newValue 59 | } 60 | } 61 | 62 | /// Get TwoWayMirror information for a mirrored ivar 63 | /// 64 | /// - Parameters: 65 | /// - mirror: pointer to Swift reflection information 66 | /// - path: dotted keypath to ivar of interest 67 | /// - Returns: TwoWyayMirror information 68 | public static func reflect(mirror: UnsafePointer<_Mirror>, path: [String]? = nil) -> TwoWayMirror { 69 | if var path = path, !path.isEmpty { 70 | let key = path.removeFirst() 71 | for index in 0 ..< mirror.pointee.count { 72 | var (name, submirror) = mirror.pointee[index] 73 | if name == key { 74 | return reflect(mirror: &submirror, path: path) 75 | } 76 | } 77 | fatalError("TwoWayMirror could not find path component: \(key)") 78 | } 79 | 80 | return mirror.withMemoryRebound(to: TwoWayMirror.self, capacity: 1) { 81 | $0.pointee 82 | } 83 | } 84 | 85 | /// Reflect a typed pointer to a class instance ivar 86 | /// 87 | /// - Parameters: 88 | /// - object: pointer to class instance 89 | /// - path: dotted path to ivar of interest 90 | /// - type: assumed type of instance 91 | /// - Returns: typed pointer to memory of ivar 92 | public static func reflect(object: AnyObject, path: String, type: T.Type, 93 | file: StaticString = #file, line: UInt = #line) -> UnsafeMutablePointer { 94 | var mirror = _reflect(object) 95 | let data = reflect(mirror: &mirror, path: path.components(separatedBy: ".")) 96 | return data.pointer(type: T.self, file: file, line: line) 97 | } 98 | 99 | /// List ivars of class struct of interest 100 | /// 101 | /// - Parameters: 102 | /// - any: class instance/struct 103 | /// - path: dotted path to ivar of interest 104 | /// - Returns: list of ivars in memory order 105 | public static func reflectKeys(any: Any, path: String? = nil) -> [String] { 106 | var mirror = _reflect(any) 107 | if let path = path { 108 | for key in path.components(separatedBy: ".") { 109 | for index in 0 ..< mirror.count { 110 | let (name, submirror) = mirror[index] 111 | if name == key { 112 | mirror = submirror 113 | break 114 | } 115 | } 116 | } 117 | } 118 | return (0 ..< mirror.count).map { mirror[$0].0 } 119 | } 120 | } 121 | 122 | /// conformance for subscript equivalent of keypaths 123 | public protocol SubScriptReflectable: AnyObject {} 124 | extension NSObject: SubScriptReflectable {} 125 | 126 | public extension SubScriptReflectable { 127 | public subscript(path: String, type: T.Type) -> T { 128 | get { 129 | return TwoWayMirror.reflect(object: self, path: path, type: T.self).pointee 130 | } 131 | set(newValue) { 132 | TwoWayMirror.reflect(object: self, path: path, type: T.self).pointee = newValue 133 | } 134 | } 135 | } 136 | 137 | public func TWError(_ string: String) -> Error { 138 | return NSError(domain: "TwoWayMirror", code: -1, 139 | userInfo: [NSLocalizedDescriptionKey: "TwoWayMirror \(string)"]) 140 | } 141 | 142 | // MARK: JSON encoding / decoding as reflection example 143 | 144 | extension TwoWayMirror { 145 | 146 | /// Create and decode object from JSON data 147 | /// 148 | /// - Parameters: 149 | /// - instanceType: Type conforming to TwoWayContainable 150 | /// - json: JSON format instance Data 151 | /// - options: JSON reading options 152 | /// - Returns: New initialised instance of instanceType 153 | /// - Throws: Any error encountered during encoding 154 | public static func decode(_ containableType: T.Type, from json: Data, 155 | options: JSONSerialization.ReadingOptions = []) throws -> T { 156 | let any = try JSONSerialization.jsonObject(with: json, options: options) 157 | return try containableType.decodeElement(from: any) 158 | } 159 | 160 | /// Decode ivar values from JSON onto object 161 | /// 162 | /// - Parameters: 163 | /// - object: class instance to take values 164 | /// - json: JSON Data packet 165 | /// - options: JSON reading options 166 | /// - Throws: JSON/type conversion errors 167 | public static func decode(object: AnyObject, json: Data, 168 | options: JSONSerialization.ReadingOptions = []) throws { 169 | try decode(object: object, 170 | any: try JSONSerialization.jsonObject(with: json, options: options)) 171 | } 172 | 173 | /// Decode ivar values from foundation class representation 174 | /// 175 | /// - Parameters: 176 | /// - object: class instance to take values 177 | /// - any: foundation representation of values 178 | /// - Throws: type conversion errors 179 | public static func decode(object: AnyObject, any: Any) throws { 180 | var mirror = _reflect(object) 181 | try decode(mirror: &mirror, any: any) 182 | } 183 | 184 | static func decode(mirror: UnsafePointer<_Mirror>, any: Any) throws { 185 | var data = reflect(mirror: mirror) 186 | if let codableType = data.metadata as? TwoWayCodable.Type { 187 | try codableType.twDecode(data: &data, any: any) 188 | } 189 | else if let enumType = data.metadata as? TwoWayEnum.Type { 190 | try enumType.twDecode(data: &data, from: try cast(any, to: [String: Any].self)) 191 | } 192 | else if mirror.pointee.count != 0, let dict = any as? [String: Any] { 193 | for index in 0 ..< mirror.pointee.count { 194 | var (name, submirror) = mirror.pointee[index] 195 | if let value = dict[name] { 196 | try decode(mirror: &submirror, any: value) 197 | } 198 | } 199 | } 200 | } 201 | 202 | /// encode a class instance/struct to JSON 203 | /// 204 | /// - Parameters: 205 | /// - object: class instance/struct 206 | /// - options: JSON writing options 207 | /// - Returns: JSON Data packet 208 | /// - Throws: JSON errors 209 | public static func encode(object: Any, 210 | options: JSONSerialization.WritingOptions) throws -> Data { 211 | return try JSONSerialization.data(withJSONObject: encode(object: object), 212 | options: options) 213 | } 214 | 215 | /// encode class instance/struct to representation as foundation objects 216 | /// 217 | /// - Parameter object: class instance/struct 218 | /// - Returns: NSDictionary 219 | public static func encode(object: Any) -> Any { 220 | var mirror = _reflect(object) 221 | return encode(mirror: &mirror) 222 | } 223 | 224 | static func encode(mirror: UnsafePointer<_Mirror>) -> Any { 225 | let data = reflect(mirror: mirror) 226 | if let codableType = data.metadata as? TwoWayCodable.Type { 227 | return codableType.twEncode(data: data) 228 | } 229 | else if data.metadata is TwoWayEnum.Type { 230 | var dict = [String: Any]() 231 | dict["case"] = 232 | mirror.withMemoryRebound(to: _MagicMirrorData.self, capacity: 1) { 233 | String(utf8String: _enumMirror_caseName($0.pointee)) 234 | } 235 | if mirror.pointee.count != 0 { 236 | var (_, submirror) = mirror.pointee[0] 237 | if submirror.count == 1 { 238 | dict["let"] = encode(mirror: &submirror) 239 | } else { 240 | for index in 0 ..< submirror.count { 241 | var (name, letmirror) = submirror[index] 242 | dict[name] = encode(mirror: &letmirror) 243 | } 244 | } 245 | } 246 | return dict 247 | } 248 | else { 249 | var dict = [String: Any]() 250 | for index in 0 ..< mirror.pointee.count { 251 | var (name, submirror) = mirror.pointee[index] 252 | if name == "super" { continue } 253 | dict[name] = encode(mirror: &submirror) 254 | } 255 | return dict 256 | } 257 | } 258 | 259 | public static func cast(_ any: Any, to type: T.Type, 260 | file: StaticString = #file, line: UInt = #line) throws -> T { 261 | guard let cast = any as? T else { 262 | throw TWError("invalid cast of \(any) to \(T.self) at \(file)#\(line)") 263 | } 264 | return cast 265 | } 266 | 267 | public static var dateFormatter: DateFormatter = { 268 | let dateFormatter = DateFormatter() 269 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ssZZZZZ" 270 | return dateFormatter 271 | }() 272 | } 273 | 274 | // MARK: Codable conformances 275 | 276 | /// Conform to be codable 277 | public protocol TwoWayCodable { 278 | static func twDecode(data: inout TwoWayMirror, any: Any) throws 279 | static func twEncode(data: TwoWayMirror) -> Any 280 | } 281 | 282 | /// Conformance for decoding enums 283 | public protocol TwoWayEnum { 284 | static func twDecode(data: inout TwoWayMirror, from dict: [String: Any]) throws 285 | } 286 | 287 | /// Objects that can be created decoding containers or optionals 288 | public protocol TwoWayContainable { 289 | init() 290 | } 291 | 292 | private class Crucible { 293 | var instance = T() 294 | } 295 | extension TwoWayContainable { 296 | static func decodeElement(from: Any) throws -> Self { 297 | let instanceHolder = Crucible() 298 | var (_, mirror) = _reflect(instanceHolder)[0] 299 | try TwoWayMirror.decode(mirror: &mirror, any: from) 300 | return instanceHolder.instance 301 | } 302 | } 303 | 304 | public protocol TwoWayCastable: TwoWayContainable {} 305 | extension TwoWayCastable { 306 | public static func twDecode(data: inout TwoWayMirror, any: Any) throws { 307 | #if os(Linux) 308 | if Self.self == Double.self { 309 | data[Double.self] = try (any as? Int).flatMap { Double($0) } ?? 310 | TwoWayMirror.cast(any, to: Double.self) 311 | return 312 | } 313 | #endif 314 | data[Self.self] = try TwoWayMirror.cast(any, to: Self.self) 315 | } 316 | public static func twEncode(data: TwoWayMirror) -> Any { 317 | return data[Self.self] 318 | } 319 | } 320 | 321 | extension Int: TwoWayCodable, TwoWayCastable {} 322 | extension Double: TwoWayCodable, TwoWayCastable {} 323 | extension String: TwoWayCodable, TwoWayCastable {} 324 | 325 | extension Date: TwoWayCodable, TwoWayContainable { 326 | public static func twDecode(data: inout TwoWayMirror, any: Any) throws { 327 | let string = try TwoWayMirror.cast(any, to: String.self) 328 | guard let date = TwoWayMirror.dateFormatter.date(from: string) else { 329 | throw TWError("unable to parse date: '\(string)'") 330 | } 331 | data[Date.self] = date 332 | } 333 | public static func twEncode(data: TwoWayMirror) -> Any { 334 | return TwoWayMirror.dateFormatter.string(from: data[Date.self]) 335 | } 336 | } 337 | 338 | extension Data: TwoWayCodable, TwoWayContainable { 339 | public static func twDecode(data: inout TwoWayMirror, any: Any) throws { 340 | let string = try TwoWayMirror.cast(any, to: String.self) 341 | guard let base64 = Data(base64Encoded: string) else { 342 | throw TWError("unable to decode base64: '\(string)'") 343 | } 344 | data[Data.self] = base64 345 | } 346 | public static func twEncode(data: TwoWayMirror) -> Any { 347 | return data[Data.self].base64EncodedString() 348 | } 349 | } 350 | 351 | extension URL: TwoWayCodable, TwoWayContainable { 352 | public init() { 353 | self.init(string: "http://void.org")! 354 | } 355 | public static func twDecode(data: inout TwoWayMirror, any: Any) throws { 356 | let string = try TwoWayMirror.cast(any, to: String.self) 357 | guard let url = URL(string: string) else { 358 | throw TWError("unable to parse url: '\(string)'") 359 | } 360 | data[URL.self] = url 361 | } 362 | public static func twEncode(data: TwoWayMirror) -> Any { 363 | return data[URL.self].absoluteString 364 | } 365 | } 366 | 367 | extension Array: TwoWayCodable, TwoWayContainable { 368 | public static func twDecode(data: inout TwoWayMirror, any: Any) throws { 369 | guard let containableType = Element.self as? TwoWayContainable.Type else { 370 | throw TWError("unable to decode array containing \(Element.self)") 371 | } 372 | data[[Element].self] = 373 | try TwoWayMirror.cast(any, to: [Any].self) 374 | .map { try containableType.decodeElement(from: $0) as! Element } 375 | } 376 | public static func twEncode(data: TwoWayMirror) -> Any { 377 | return data[[Element].self].map { 378 | (e: Element) -> Any in 379 | var mirror = _reflect(e) 380 | return TwoWayMirror.encode(mirror: &mirror) 381 | } 382 | } 383 | } 384 | 385 | extension Dictionary: TwoWayCodable, TwoWayContainable { 386 | public static func twDecode(data: inout TwoWayMirror, any: Any) throws { 387 | guard let containableType = Value.self as? TwoWayContainable.Type else { 388 | throw TWError("unable to decode dictionary containing \(Value.self)") 389 | } 390 | let dictPtr = data.pointer(type: [Key: Value].self) 391 | dictPtr.pointee.removeAll() 392 | for (key, value) in try TwoWayMirror.cast(any, to: [Key: Any].self) { 393 | dictPtr.pointee[key] = try containableType.decodeElement(from: value) as? Value 394 | } 395 | } 396 | public static func twEncode(data: TwoWayMirror) -> Any { 397 | var dict = [Key: Any]() 398 | for (key, value) in data[[Key: Value].self] { 399 | var mirror = _reflect(value) 400 | dict[key] = TwoWayMirror.encode(mirror: &mirror) 401 | } 402 | return dict 403 | } 404 | } 405 | 406 | extension Optional: TwoWayCodable { 407 | public static func twDecode(data: inout TwoWayMirror, any: Any) throws { 408 | let optionalPtr = data.pointer(type: Optional.self) 409 | if any is NSNull { 410 | optionalPtr.pointee = nil 411 | } 412 | else { 413 | guard let subtype = Wrapped.self as? TwoWayContainable.Type else { 414 | throw TWError("unable to decode optional of \(Wrapped.self)") 415 | } 416 | let instance = try subtype.decodeElement(from: any) 417 | optionalPtr.pointee = .some(try TwoWayMirror.cast(instance, to: Wrapped.self)) 418 | } 419 | } 420 | public static func twEncode(data: TwoWayMirror) -> Any { 421 | guard let some = data[Optional.self] else { 422 | return NSNull() 423 | } 424 | var mirror = _reflect(some) 425 | return TwoWayMirror.encode(mirror: &mirror) 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /TwoWayMirror.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /TwoWayMirror.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "TwoWayMirror" 3 | s.version = "1.2.0" 4 | s.summary = "Bi-directional reflection for Swift" 5 | s.homepage = "https://github.com/johnno1962/TwoWayMirror" 6 | s.social_media_url = "https://twitter.com/Injection4Xcode" 7 | s.documentation_url = "https://github.com/johnno1962/TwoWayMirror/blob/master/README.md" 8 | s.license = { :type => "MIT" } 9 | s.authors = { "johnno1962" => "mirror@johnholdsworth.com" } 10 | 11 | s.ios.deployment_target = "9.0" 12 | s.osx.deployment_target = "10.11" 13 | s.source = { :git => "https://github.com/johnno1962/TwoWayMirror.git", :tag => s.version } 14 | s.source_files = "TwoWayMirror.playground/Sources/*.swift" 15 | end 16 | -------------------------------------------------------------------------------- /TwoWayMirror.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | BB765361203476E200BBD463 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB765360203476E200BBD463 /* AppDelegate.swift */; }; 11 | BB765363203476E200BBD463 /* MasterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB765362203476E200BBD463 /* MasterViewController.swift */; }; 12 | BB765365203476E200BBD463 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB765364203476E200BBD463 /* DetailViewController.swift */; }; 13 | BB765368203476E200BBD463 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BB765366203476E200BBD463 /* Main.storyboard */; }; 14 | BB76536A203476E300BBD463 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BB765369203476E300BBD463 /* Assets.xcassets */; }; 15 | BB76536D203476E300BBD463 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BB76536B203476E300BBD463 /* LaunchScreen.storyboard */; }; 16 | BB765378203476E300BBD463 /* TwoWayMirrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB765377203476E300BBD463 /* TwoWayMirrorTests.swift */; }; 17 | BB765385203477AD00BBD463 /* TwoWayMirror.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB765384203477AC00BBD463 /* TwoWayMirror.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXContainerItemProxy section */ 21 | BB765374203476E300BBD463 /* PBXContainerItemProxy */ = { 22 | isa = PBXContainerItemProxy; 23 | containerPortal = BB765355203476E200BBD463 /* Project object */; 24 | proxyType = 1; 25 | remoteGlobalIDString = BB76535C203476E200BBD463; 26 | remoteInfo = TwoWayMirror; 27 | }; 28 | /* End PBXContainerItemProxy section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | BB76535D203476E200BBD463 /* TwoWayMirror.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TwoWayMirror.app; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | BB765360203476E200BBD463 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33 | BB765362203476E200BBD463 /* MasterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterViewController.swift; sourceTree = ""; }; 34 | BB765364203476E200BBD463 /* DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; 35 | BB765367203476E200BBD463 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 36 | BB765369203476E300BBD463 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 37 | BB76536C203476E300BBD463 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 38 | BB76536E203476E300BBD463 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | BB765373203476E300BBD463 /* TwoWayMirrorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TwoWayMirrorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | BB765377203476E300BBD463 /* TwoWayMirrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoWayMirrorTests.swift; sourceTree = ""; }; 41 | BB765379203476E300BBD463 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | BB765384203477AC00BBD463 /* TwoWayMirror.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TwoWayMirror.swift; path = TwoWayMirror.playground/Sources/TwoWayMirror.swift; sourceTree = SOURCE_ROOT; }; 43 | /* End PBXFileReference section */ 44 | 45 | /* Begin PBXFrameworksBuildPhase section */ 46 | BB76535A203476E200BBD463 /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | BB765370203476E300BBD463 /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | ); 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | /* End PBXFrameworksBuildPhase section */ 61 | 62 | /* Begin PBXGroup section */ 63 | BB765354203476E200BBD463 = { 64 | isa = PBXGroup; 65 | children = ( 66 | BB76535F203476E200BBD463 /* TwoWayMirror */, 67 | BB765376203476E300BBD463 /* TwoWayMirrorTests */, 68 | BB76535E203476E200BBD463 /* Products */, 69 | ); 70 | sourceTree = ""; 71 | }; 72 | BB76535E203476E200BBD463 /* Products */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | BB76535D203476E200BBD463 /* TwoWayMirror.app */, 76 | BB765373203476E300BBD463 /* TwoWayMirrorTests.xctest */, 77 | ); 78 | name = Products; 79 | sourceTree = ""; 80 | }; 81 | BB76535F203476E200BBD463 /* TwoWayMirror */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | BB765384203477AC00BBD463 /* TwoWayMirror.swift */, 85 | BB765360203476E200BBD463 /* AppDelegate.swift */, 86 | BB765362203476E200BBD463 /* MasterViewController.swift */, 87 | BB765364203476E200BBD463 /* DetailViewController.swift */, 88 | BB765366203476E200BBD463 /* Main.storyboard */, 89 | BB765369203476E300BBD463 /* Assets.xcassets */, 90 | BB76536B203476E300BBD463 /* LaunchScreen.storyboard */, 91 | BB76536E203476E300BBD463 /* Info.plist */, 92 | ); 93 | path = TwoWayMirror; 94 | sourceTree = ""; 95 | }; 96 | BB765376203476E300BBD463 /* TwoWayMirrorTests */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | BB765377203476E300BBD463 /* TwoWayMirrorTests.swift */, 100 | BB765379203476E300BBD463 /* Info.plist */, 101 | ); 102 | path = TwoWayMirrorTests; 103 | sourceTree = ""; 104 | }; 105 | /* End PBXGroup section */ 106 | 107 | /* Begin PBXNativeTarget section */ 108 | BB76535C203476E200BBD463 /* TwoWayMirror */ = { 109 | isa = PBXNativeTarget; 110 | buildConfigurationList = BB76537C203476E300BBD463 /* Build configuration list for PBXNativeTarget "TwoWayMirror" */; 111 | buildPhases = ( 112 | BB765359203476E200BBD463 /* Sources */, 113 | BB76535A203476E200BBD463 /* Frameworks */, 114 | BB76535B203476E200BBD463 /* Resources */, 115 | ); 116 | buildRules = ( 117 | ); 118 | dependencies = ( 119 | ); 120 | name = TwoWayMirror; 121 | productName = TwoWayMirror; 122 | productReference = BB76535D203476E200BBD463 /* TwoWayMirror.app */; 123 | productType = "com.apple.product-type.application"; 124 | }; 125 | BB765372203476E300BBD463 /* TwoWayMirrorTests */ = { 126 | isa = PBXNativeTarget; 127 | buildConfigurationList = BB76537F203476E300BBD463 /* Build configuration list for PBXNativeTarget "TwoWayMirrorTests" */; 128 | buildPhases = ( 129 | BB76536F203476E300BBD463 /* Sources */, 130 | BB765370203476E300BBD463 /* Frameworks */, 131 | BB765371203476E300BBD463 /* Resources */, 132 | ); 133 | buildRules = ( 134 | ); 135 | dependencies = ( 136 | BB765375203476E300BBD463 /* PBXTargetDependency */, 137 | ); 138 | name = TwoWayMirrorTests; 139 | productName = TwoWayMirrorTests; 140 | productReference = BB765373203476E300BBD463 /* TwoWayMirrorTests.xctest */; 141 | productType = "com.apple.product-type.bundle.unit-test"; 142 | }; 143 | /* End PBXNativeTarget section */ 144 | 145 | /* Begin PBXProject section */ 146 | BB765355203476E200BBD463 /* Project object */ = { 147 | isa = PBXProject; 148 | attributes = { 149 | LastSwiftUpdateCheck = 0930; 150 | LastUpgradeCheck = 0930; 151 | ORGANIZATIONNAME = "John Holdsworth"; 152 | TargetAttributes = { 153 | BB76535C203476E200BBD463 = { 154 | CreatedOnToolsVersion = 9.3; 155 | LastSwiftMigration = 0930; 156 | ProvisioningStyle = Automatic; 157 | }; 158 | BB765372203476E300BBD463 = { 159 | CreatedOnToolsVersion = 9.3; 160 | LastSwiftMigration = 0930; 161 | ProvisioningStyle = Automatic; 162 | TestTargetID = BB76535C203476E200BBD463; 163 | }; 164 | }; 165 | }; 166 | buildConfigurationList = BB765358203476E200BBD463 /* Build configuration list for PBXProject "TwoWayMirror" */; 167 | compatibilityVersion = "Xcode 8.0"; 168 | developmentRegion = en; 169 | hasScannedForEncodings = 0; 170 | knownRegions = ( 171 | en, 172 | Base, 173 | ); 174 | mainGroup = BB765354203476E200BBD463; 175 | productRefGroup = BB76535E203476E200BBD463 /* Products */; 176 | projectDirPath = ""; 177 | projectRoot = ""; 178 | targets = ( 179 | BB76535C203476E200BBD463 /* TwoWayMirror */, 180 | BB765372203476E300BBD463 /* TwoWayMirrorTests */, 181 | ); 182 | }; 183 | /* End PBXProject section */ 184 | 185 | /* Begin PBXResourcesBuildPhase section */ 186 | BB76535B203476E200BBD463 /* Resources */ = { 187 | isa = PBXResourcesBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | BB76536D203476E300BBD463 /* LaunchScreen.storyboard in Resources */, 191 | BB76536A203476E300BBD463 /* Assets.xcassets in Resources */, 192 | BB765368203476E200BBD463 /* Main.storyboard in Resources */, 193 | ); 194 | runOnlyForDeploymentPostprocessing = 0; 195 | }; 196 | BB765371203476E300BBD463 /* Resources */ = { 197 | isa = PBXResourcesBuildPhase; 198 | buildActionMask = 2147483647; 199 | files = ( 200 | ); 201 | runOnlyForDeploymentPostprocessing = 0; 202 | }; 203 | /* End PBXResourcesBuildPhase section */ 204 | 205 | /* Begin PBXSourcesBuildPhase section */ 206 | BB765359203476E200BBD463 /* Sources */ = { 207 | isa = PBXSourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | BB765365203476E200BBD463 /* DetailViewController.swift in Sources */, 211 | BB765363203476E200BBD463 /* MasterViewController.swift in Sources */, 212 | BB765361203476E200BBD463 /* AppDelegate.swift in Sources */, 213 | BB765385203477AD00BBD463 /* TwoWayMirror.swift in Sources */, 214 | ); 215 | runOnlyForDeploymentPostprocessing = 0; 216 | }; 217 | BB76536F203476E300BBD463 /* Sources */ = { 218 | isa = PBXSourcesBuildPhase; 219 | buildActionMask = 2147483647; 220 | files = ( 221 | BB765378203476E300BBD463 /* TwoWayMirrorTests.swift in Sources */, 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | }; 225 | /* End PBXSourcesBuildPhase section */ 226 | 227 | /* Begin PBXTargetDependency section */ 228 | BB765375203476E300BBD463 /* PBXTargetDependency */ = { 229 | isa = PBXTargetDependency; 230 | target = BB76535C203476E200BBD463 /* TwoWayMirror */; 231 | targetProxy = BB765374203476E300BBD463 /* PBXContainerItemProxy */; 232 | }; 233 | /* End PBXTargetDependency section */ 234 | 235 | /* Begin PBXVariantGroup section */ 236 | BB765366203476E200BBD463 /* Main.storyboard */ = { 237 | isa = PBXVariantGroup; 238 | children = ( 239 | BB765367203476E200BBD463 /* Base */, 240 | ); 241 | name = Main.storyboard; 242 | sourceTree = ""; 243 | }; 244 | BB76536B203476E300BBD463 /* LaunchScreen.storyboard */ = { 245 | isa = PBXVariantGroup; 246 | children = ( 247 | BB76536C203476E300BBD463 /* Base */, 248 | ); 249 | name = LaunchScreen.storyboard; 250 | sourceTree = ""; 251 | }; 252 | /* End PBXVariantGroup section */ 253 | 254 | /* Begin XCBuildConfiguration section */ 255 | BB76537A203476E300BBD463 /* Debug */ = { 256 | isa = XCBuildConfiguration; 257 | buildSettings = { 258 | ALWAYS_SEARCH_USER_PATHS = NO; 259 | CLANG_ANALYZER_NONNULL = YES; 260 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 261 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 262 | CLANG_CXX_LIBRARY = "libc++"; 263 | CLANG_ENABLE_MODULES = YES; 264 | CLANG_ENABLE_OBJC_ARC = YES; 265 | CLANG_ENABLE_OBJC_WEAK = YES; 266 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 267 | CLANG_WARN_BOOL_CONVERSION = YES; 268 | CLANG_WARN_COMMA = YES; 269 | CLANG_WARN_CONSTANT_CONVERSION = YES; 270 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 271 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 272 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 273 | CLANG_WARN_EMPTY_BODY = YES; 274 | CLANG_WARN_ENUM_CONVERSION = YES; 275 | CLANG_WARN_INFINITE_RECURSION = YES; 276 | CLANG_WARN_INT_CONVERSION = YES; 277 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 278 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 279 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 280 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 281 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 282 | CLANG_WARN_STRICT_PROTOTYPES = YES; 283 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 284 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 285 | CLANG_WARN_UNREACHABLE_CODE = YES; 286 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 287 | CODE_SIGN_IDENTITY = "iPhone Developer"; 288 | COPY_PHASE_STRIP = NO; 289 | DEBUG_INFORMATION_FORMAT = dwarf; 290 | ENABLE_STRICT_OBJC_MSGSEND = YES; 291 | ENABLE_TESTABILITY = YES; 292 | GCC_C_LANGUAGE_STANDARD = gnu11; 293 | GCC_DYNAMIC_NO_PIC = NO; 294 | GCC_NO_COMMON_BLOCKS = YES; 295 | GCC_OPTIMIZATION_LEVEL = 0; 296 | GCC_PREPROCESSOR_DEFINITIONS = ( 297 | "DEBUG=1", 298 | "$(inherited)", 299 | ); 300 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 301 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 302 | GCC_WARN_UNDECLARED_SELECTOR = YES; 303 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 304 | GCC_WARN_UNUSED_FUNCTION = YES; 305 | GCC_WARN_UNUSED_VARIABLE = YES; 306 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 307 | MTL_ENABLE_DEBUG_INFO = YES; 308 | ONLY_ACTIVE_ARCH = YES; 309 | SDKROOT = iphoneos; 310 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 311 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 312 | SWIFT_VERSION = 3.0; 313 | }; 314 | name = Debug; 315 | }; 316 | BB76537B203476E300BBD463 /* Release */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | ALWAYS_SEARCH_USER_PATHS = NO; 320 | CLANG_ANALYZER_NONNULL = YES; 321 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 322 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 323 | CLANG_CXX_LIBRARY = "libc++"; 324 | CLANG_ENABLE_MODULES = YES; 325 | CLANG_ENABLE_OBJC_ARC = YES; 326 | CLANG_ENABLE_OBJC_WEAK = YES; 327 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 328 | CLANG_WARN_BOOL_CONVERSION = YES; 329 | CLANG_WARN_COMMA = YES; 330 | CLANG_WARN_CONSTANT_CONVERSION = YES; 331 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 332 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 333 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 334 | CLANG_WARN_EMPTY_BODY = YES; 335 | CLANG_WARN_ENUM_CONVERSION = YES; 336 | CLANG_WARN_INFINITE_RECURSION = YES; 337 | CLANG_WARN_INT_CONVERSION = YES; 338 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 339 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 340 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 341 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 342 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 343 | CLANG_WARN_STRICT_PROTOTYPES = YES; 344 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 345 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 346 | CLANG_WARN_UNREACHABLE_CODE = YES; 347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 348 | CODE_SIGN_IDENTITY = "iPhone Developer"; 349 | COPY_PHASE_STRIP = NO; 350 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 351 | ENABLE_NS_ASSERTIONS = NO; 352 | ENABLE_STRICT_OBJC_MSGSEND = YES; 353 | GCC_C_LANGUAGE_STANDARD = gnu11; 354 | GCC_NO_COMMON_BLOCKS = YES; 355 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 356 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 357 | GCC_WARN_UNDECLARED_SELECTOR = YES; 358 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 359 | GCC_WARN_UNUSED_FUNCTION = YES; 360 | GCC_WARN_UNUSED_VARIABLE = YES; 361 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 362 | MTL_ENABLE_DEBUG_INFO = NO; 363 | SDKROOT = iphoneos; 364 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 365 | SWIFT_VERSION = 3.0; 366 | VALIDATE_PRODUCT = YES; 367 | }; 368 | name = Release; 369 | }; 370 | BB76537D203476E300BBD463 /* Debug */ = { 371 | isa = XCBuildConfiguration; 372 | buildSettings = { 373 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 374 | CODE_SIGN_STYLE = Automatic; 375 | DEVELOPMENT_TEAM = 9V5A8WE85E; 376 | INFOPLIST_FILE = TwoWayMirror/Info.plist; 377 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 378 | PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.TwoWayMirror; 379 | PRODUCT_NAME = "$(TARGET_NAME)"; 380 | TARGETED_DEVICE_FAMILY = "1,2"; 381 | }; 382 | name = Debug; 383 | }; 384 | BB76537E203476E300BBD463 /* Release */ = { 385 | isa = XCBuildConfiguration; 386 | buildSettings = { 387 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 388 | CODE_SIGN_STYLE = Automatic; 389 | DEVELOPMENT_TEAM = 9V5A8WE85E; 390 | INFOPLIST_FILE = TwoWayMirror/Info.plist; 391 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 392 | PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.TwoWayMirror; 393 | PRODUCT_NAME = "$(TARGET_NAME)"; 394 | TARGETED_DEVICE_FAMILY = "1,2"; 395 | }; 396 | name = Release; 397 | }; 398 | BB765380203476E300BBD463 /* Debug */ = { 399 | isa = XCBuildConfiguration; 400 | buildSettings = { 401 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 402 | BUNDLE_LOADER = "$(TEST_HOST)"; 403 | CODE_SIGN_STYLE = Automatic; 404 | DEVELOPMENT_TEAM = 9V5A8WE85E; 405 | INFOPLIST_FILE = TwoWayMirrorTests/Info.plist; 406 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 407 | PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.TwoWayMirrorTests; 408 | PRODUCT_NAME = "$(TARGET_NAME)"; 409 | TARGETED_DEVICE_FAMILY = "1,2"; 410 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TwoWayMirror.app/TwoWayMirror"; 411 | }; 412 | name = Debug; 413 | }; 414 | BB765381203476E300BBD463 /* Release */ = { 415 | isa = XCBuildConfiguration; 416 | buildSettings = { 417 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 418 | BUNDLE_LOADER = "$(TEST_HOST)"; 419 | CODE_SIGN_STYLE = Automatic; 420 | DEVELOPMENT_TEAM = 9V5A8WE85E; 421 | INFOPLIST_FILE = TwoWayMirrorTests/Info.plist; 422 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 423 | PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.TwoWayMirrorTests; 424 | PRODUCT_NAME = "$(TARGET_NAME)"; 425 | TARGETED_DEVICE_FAMILY = "1,2"; 426 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TwoWayMirror.app/TwoWayMirror"; 427 | }; 428 | name = Release; 429 | }; 430 | /* End XCBuildConfiguration section */ 431 | 432 | /* Begin XCConfigurationList section */ 433 | BB765358203476E200BBD463 /* Build configuration list for PBXProject "TwoWayMirror" */ = { 434 | isa = XCConfigurationList; 435 | buildConfigurations = ( 436 | BB76537A203476E300BBD463 /* Debug */, 437 | BB76537B203476E300BBD463 /* Release */, 438 | ); 439 | defaultConfigurationIsVisible = 0; 440 | defaultConfigurationName = Release; 441 | }; 442 | BB76537C203476E300BBD463 /* Build configuration list for PBXNativeTarget "TwoWayMirror" */ = { 443 | isa = XCConfigurationList; 444 | buildConfigurations = ( 445 | BB76537D203476E300BBD463 /* Debug */, 446 | BB76537E203476E300BBD463 /* Release */, 447 | ); 448 | defaultConfigurationIsVisible = 0; 449 | defaultConfigurationName = Release; 450 | }; 451 | BB76537F203476E300BBD463 /* Build configuration list for PBXNativeTarget "TwoWayMirrorTests" */ = { 452 | isa = XCConfigurationList; 453 | buildConfigurations = ( 454 | BB765380203476E300BBD463 /* Debug */, 455 | BB765381203476E300BBD463 /* Release */, 456 | ); 457 | defaultConfigurationIsVisible = 0; 458 | defaultConfigurationName = Release; 459 | }; 460 | /* End XCConfigurationList section */ 461 | }; 462 | rootObject = BB765355203476E200BBD463 /* Project object */; 463 | } 464 | -------------------------------------------------------------------------------- /TwoWayMirror.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TwoWayMirror.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /TwoWayMirror/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TwoWayMirror 4 | // 5 | // Created by John Holdsworth on 14/02/2018. 6 | // Copyright © 2018 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | let splitViewController = window!.rootViewController as! UISplitViewController 20 | let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController 21 | navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem 22 | splitViewController.delegate = self 23 | return true 24 | } 25 | 26 | func applicationWillResignActive(_ application: UIApplication) { 27 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 28 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 29 | } 30 | 31 | func applicationDidEnterBackground(_ application: UIApplication) { 32 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 33 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 34 | } 35 | 36 | func applicationWillEnterForeground(_ application: UIApplication) { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | func applicationDidBecomeActive(_ application: UIApplication) { 41 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 42 | } 43 | 44 | func applicationWillTerminate(_ application: UIApplication) { 45 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 46 | } 47 | 48 | // MARK: - Split view 49 | 50 | func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool { 51 | guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false } 52 | guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false } 53 | if topAsDetailController.detailItem == nil { 54 | // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded. 55 | return true 56 | } 57 | return false 58 | } 59 | 60 | } 61 | 62 | -------------------------------------------------------------------------------- /TwoWayMirror/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /TwoWayMirror/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TwoWayMirror/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /TwoWayMirror/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /TwoWayMirror/DetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailViewController.swift 3 | // TwoWayMirror 4 | // 5 | // Created by John Holdsworth on 14/02/2018. 6 | // Copyright © 2018 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DetailViewController: UIViewController { 12 | 13 | @IBOutlet weak var detailDescriptionLabel: UILabel! 14 | 15 | 16 | func configureView() { 17 | // Update the user interface for the detail item. 18 | if let detail = detailItem { 19 | if let label = detailDescriptionLabel { 20 | label.text = detail.description 21 | } 22 | } 23 | } 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | // Do any additional setup after loading the view, typically from a nib. 28 | configureView() 29 | } 30 | 31 | override func didReceiveMemoryWarning() { 32 | super.didReceiveMemoryWarning() 33 | // Dispose of any resources that can be recreated. 34 | } 35 | 36 | var detailItem: NSDate? { 37 | didSet { 38 | // Update the view. 39 | configureView() 40 | } 41 | } 42 | 43 | 44 | } 45 | 46 | -------------------------------------------------------------------------------- /TwoWayMirror/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | arm64 30 | 31 | UIStatusBarTintParameters 32 | 33 | UINavigationBar 34 | 35 | Style 36 | UIBarStyleDefault 37 | Translucent 38 | 39 | 40 | 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | UISupportedInterfaceOrientations~ipad 48 | 49 | UIInterfaceOrientationPortrait 50 | UIInterfaceOrientationPortraitUpsideDown 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /TwoWayMirror/MasterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MasterViewController.swift 3 | // TwoWayMirror 4 | // 5 | // Created by John Holdsworth on 14/02/2018. 6 | // Copyright © 2018 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MasterViewController: UITableViewController { 12 | 13 | var detailViewController: DetailViewController? = nil 14 | var objects = [Any]() 15 | 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | // Do any additional setup after loading the view, typically from a nib. 20 | navigationItem.leftBarButtonItem = editButtonItem 21 | 22 | let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:))) 23 | navigationItem.rightBarButtonItem = addButton 24 | if let split = splitViewController { 25 | let controllers = split.viewControllers 26 | detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController 27 | } 28 | } 29 | 30 | override func viewWillAppear(_ animated: Bool) { 31 | clearsSelectionOnViewWillAppear = splitViewController!.isCollapsed 32 | super.viewWillAppear(animated) 33 | } 34 | 35 | override func didReceiveMemoryWarning() { 36 | super.didReceiveMemoryWarning() 37 | // Dispose of any resources that can be recreated. 38 | } 39 | 40 | @objc 41 | func insertNewObject(_ sender: Any) { 42 | objects.insert(NSDate(), at: 0) 43 | let indexPath = IndexPath(row: 0, section: 0) 44 | tableView.insertRows(at: [indexPath], with: .automatic) 45 | } 46 | 47 | // MARK: - Segues 48 | 49 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 50 | if segue.identifier == "showDetail" { 51 | if let indexPath = tableView.indexPathForSelectedRow { 52 | let object = objects[indexPath.row] as! NSDate 53 | let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController 54 | controller.detailItem = object 55 | controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem 56 | controller.navigationItem.leftItemsSupplementBackButton = true 57 | } 58 | } 59 | } 60 | 61 | // MARK: - Table View 62 | 63 | override func numberOfSections(in tableView: UITableView) -> Int { 64 | return 1 65 | } 66 | 67 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 68 | return objects.count 69 | } 70 | 71 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 72 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) 73 | 74 | let object = objects[indexPath.row] as! NSDate 75 | cell.textLabel!.text = object.description 76 | return cell 77 | } 78 | 79 | override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { 80 | // Return false if you do not want the specified item to be editable. 81 | return true 82 | } 83 | 84 | override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { 85 | if editingStyle == .delete { 86 | objects.remove(at: indexPath.row) 87 | tableView.deleteRows(at: [indexPath], with: .fade) 88 | } else if editingStyle == .insert { 89 | // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view. 90 | } 91 | } 92 | 93 | 94 | } 95 | 96 | -------------------------------------------------------------------------------- /TwoWayMirrorTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TwoWayMirrorTests/TwoWayMirrorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TwoWayMirrorTests.swift 3 | // TwoWayMirrorTests 4 | // 5 | // Created by John Holdsworth on 14/02/2018. 6 | // Copyright © 2018 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import TwoWayMirror 11 | 12 | class TwoWayMirrorTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | enum ExampleEnum: TwoWayEnum { 28 | case one, two(str: String), three(int: Int), four(int: Int, int2: Int) 29 | 30 | static func twDecode(data: inout TwoWayMirror, from: [String: Any]) throws { 31 | let ptr = data.pointer(type: ExampleEnum.self) 32 | switch from["case"] as! String { 33 | case "one": 34 | ptr.pointee = .one 35 | case "two": 36 | ptr.pointee = .two(str: from["let"] as! String) 37 | case "three": 38 | ptr.pointee = .three(int: from["let"] as! Int) 39 | case "four": 40 | ptr.pointee = .four(int: from["int"] as! Int, 41 | int2: from["int2"] as! Int) 42 | default: 43 | throw NSError(domain: "ExampleEnum", code: -1, 44 | userInfo: [NSLocalizedDescriptionKey: 45 | "Invalid case in: \(from)"]) 46 | } 47 | } 48 | } 49 | struct ExampleStruct { 50 | let i = 123 51 | } 52 | struct ContainableStruct: TwoWayContainable { 53 | var a1 = 0, a2 = 0 54 | } 55 | class ExampleClass: NSObject { 56 | let a = [98.0] 57 | let b = 199.0 58 | let c = "Hello" 59 | let d = ExampleStruct() 60 | let e = ExampleEnum.four(int: 1, int2: 9) 61 | let f = Date() 62 | let g = ["A", "B"] 63 | let h = [ContainableStruct]() 64 | let i = [Int]() 65 | let j: [Int]? = nil 66 | let k: ContainableStruct? = nil 67 | let l = [[123, 123], [234, 234]] 68 | let m = ["a": [123, 123], "b": [234, 234]] 69 | let n = ["a": ContainableStruct(), "b": ContainableStruct()] 70 | let o = [["a": [123, 123], "b": [234, 234]], ["a": [123, 123], "b": [234, 234]]] 71 | let p = URL(string: "https://apple.com") 72 | let q = "123".data(using: .utf8)! 73 | deinit { 74 | print("deinit") 75 | } 76 | } 77 | 78 | if true { 79 | let instance = ExampleClass() 80 | 81 | print(TwoWayMirror.reflectKeys(any: instance)) 82 | print(TwoWayMirror.reflectKeys(any: instance, path: "d")) 83 | 84 | TwoWayMirror.reflect(object: instance, path: "a", type: [Double].self).pointee += [11.0] 85 | print(instance["a", [Double].self]) 86 | 87 | instance["b", Double.self] += 100.0 88 | print(instance.b) 89 | 90 | instance["c", String.self] += " String" 91 | print(instance.c) 92 | 93 | instance["d.i", Int.self] += 345 94 | print(instance.d.i) 95 | 96 | print(TwoWayMirror.reflectKeys(any: instance, path: "e")) 97 | instance["e", ExampleEnum.self] = .two(str: "FFF") 98 | print(instance.e) 99 | print(TwoWayMirror.reflectKeys(any: instance, path: "e")) 100 | print(TwoWayMirror.reflectKeys(any: instance, path: "e.two")) 101 | print(MemoryLayout.size) 102 | 103 | instance["f", Date.self] = Date() 104 | print(instance["f", Date.self]) 105 | } 106 | 107 | let data = """ 108 | { 109 | "a": [77.0, 88.0], 110 | "b": 999.0, 111 | "c": "hello", 112 | "d": { 113 | "i": 789 114 | }, 115 | "f": "2018-02-14 06:39:41 +0000", 116 | "g": ["Hello", "World"], 117 | "h": [ 118 | { 119 | "a1": 11, "a2": 22 120 | }, 121 | { 122 | "a1": 111, "a2": 222 123 | } 124 | ], 125 | "i": [12345, 67890], 126 | "j": [99, 101], 127 | "k": { 128 | "a1": 1111, "a2": 2222 129 | }, 130 | "p" : "https:\\/\\/apple.com", 131 | } 132 | """.data(using: .utf8)! 133 | 134 | for _ in 0..<10 { 135 | let i1 = ExampleClass() 136 | try! TwoWayMirror.decode(object: i1, json: data) 137 | dump(i1) 138 | let json = try! TwoWayMirror.encode(object: i1, options: [.prettyPrinted]) 139 | print(String(data: json, encoding: .utf8)!) 140 | let i2 = ExampleClass() 141 | try! TwoWayMirror.decode(object: i2, json: json) 142 | dump(i2) 143 | } 144 | } 145 | 146 | func testPerformanceExample() { 147 | // This is an example of a performance test case. 148 | self.measure { 149 | // Put the code you want to measure the time of here. 150 | self.testExample() 151 | } 152 | } 153 | 154 | } 155 | --------------------------------------------------------------------------------