├── .gitignore ├── .gitmodules ├── Assets ├── iOSCounterApp.png └── macOSCounterApp.png ├── Example ├── Counter │ ├── Counter.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── Shared │ │ ├── ButtonAction.swift │ │ ├── CounterController.swift │ │ ├── CounterModel.swift │ │ └── NSObject+KeyPathBinder.swift │ ├── iOS │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── ViewController.swift │ └── macOS │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── Base.lproj │ │ └── Main.storyboard │ │ ├── Counter_macOS.entitlements │ │ ├── Info.plist │ │ └── ViewController.swift └── RandomImage │ ├── RandomImage.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ ├── Shared │ ├── ImageController.swift │ ├── ImageModel.swift │ └── Platform.swift │ ├── iOS │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift │ └── macOS │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── Main.storyboard │ ├── Info.plist │ ├── ViewController.swift │ └── macOS.entitlements ├── LICENSE ├── Mvce.podspec ├── Mvce.xcodeproj └── project.pbxproj ├── Mvce ├── Info-macOS.plist ├── Info.plist ├── Mvce.h └── Mvce.swift ├── MvceTests ├── Info.plist └── MiniMVCTests.swift ├── README.md └── README.zh_CN.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/xcode,swift,macos 3 | 4 | ### macOS ### 5 | *.DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | .com.apple.timemachine.donotpresent 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | ### Swift ### 32 | # Xcode 33 | # 34 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 35 | 36 | ## Build generated 37 | build/ 38 | DerivedData/ 39 | 40 | ## Various settings 41 | *.pbxuser 42 | !default.pbxuser 43 | *.mode1v3 44 | !default.mode1v3 45 | *.mode2v3 46 | !default.mode2v3 47 | *.perspectivev3 48 | !default.perspectivev3 49 | xcuserdata/ 50 | 51 | ## Other 52 | *.moved-aside 53 | *.xccheckout 54 | *.xcscmblueprint 55 | 56 | ## Obj-C/Swift specific 57 | *.hmap 58 | *.ipa 59 | *.dSYM.zip 60 | *.dSYM 61 | 62 | ## Playgrounds 63 | timeline.xctimeline 64 | playground.xcworkspace 65 | 66 | # Swift Package Manager 67 | # 68 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 69 | # Packages/ 70 | # Package.pins 71 | .build/ 72 | 73 | # CocoaPods - Refactored to standalone file 74 | 75 | # Carthage - Refactored to standalone file 76 | 77 | # fastlane 78 | # 79 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 80 | # screenshots whenever they are needed. 81 | # For more information about the recommended setup visit: 82 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 83 | 84 | fastlane/report.xml 85 | fastlane/Preview.html 86 | fastlane/screenshots 87 | fastlane/test_output 88 | 89 | ### Xcode ### 90 | # Xcode 91 | # 92 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 93 | 94 | ## Build generated 95 | 96 | ## Various settings 97 | 98 | ## Other 99 | 100 | ### Xcode Patch ### 101 | *.xcodeproj/* 102 | !*.xcodeproj/project.pbxproj 103 | !*.xcodeproj/xcshareddata/ 104 | !*.xcworkspace/contents.xcworkspacedata 105 | /*.gcno 106 | 107 | 108 | # End of https://www.gitignore.io/api/xcode,swift,macos 109 | 110 | .vscode -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Example/RandomImage/ReactiveCocoa"] 2 | path = Example/RandomImage/ReactiveCocoa 3 | url = https://github.com/ReactiveCocoa/ReactiveCocoa.git 4 | branch = 8.0.2 -------------------------------------------------------------------------------- /Assets/iOSCounterApp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cxa/Mvce/8e1a87ab23ae09164233151b1c973b1cfe83bfbc/Assets/iOSCounterApp.png -------------------------------------------------------------------------------- /Assets/macOSCounterApp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cxa/Mvce/8e1a87ab23ae09164233151b1c973b1cfe83bfbc/Assets/macOSCounterApp.png -------------------------------------------------------------------------------- /Example/Counter/Counter.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E051BFF720D81A6F005CDE37 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E051BFF620D81A6F005CDE37 /* AppDelegate.swift */; }; 11 | E051BFF920D81A6F005CDE37 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E051BFF820D81A6F005CDE37 /* ViewController.swift */; }; 12 | E051BFFC20D81A6F005CDE37 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E051BFFA20D81A6F005CDE37 /* Main.storyboard */; }; 13 | E051BFFE20D81A71005CDE37 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E051BFFD20D81A71005CDE37 /* Assets.xcassets */; }; 14 | E051C00120D81A71005CDE37 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E051BFFF20D81A71005CDE37 /* LaunchScreen.storyboard */; }; 15 | E051C01F20D81AEC005CDE37 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E051C01E20D81AEC005CDE37 /* AppDelegate.swift */; }; 16 | E051C02120D81AEC005CDE37 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E051C02020D81AEC005CDE37 /* ViewController.swift */; }; 17 | E051C02320D81AED005CDE37 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E051C02220D81AED005CDE37 /* Assets.xcassets */; }; 18 | E051C02620D81AED005CDE37 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E051C02420D81AED005CDE37 /* Main.storyboard */; }; 19 | E051C02E20D81C44005CDE37 /* CounterModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E051C02D20D81C44005CDE37 /* CounterModel.swift */; }; 20 | E051C02F20D81C44005CDE37 /* CounterModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E051C02D20D81C44005CDE37 /* CounterModel.swift */; }; 21 | E051C03120D81C53005CDE37 /* CounterController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E051C03020D81C53005CDE37 /* CounterController.swift */; }; 22 | E051C03220D81C53005CDE37 /* CounterController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E051C03020D81C53005CDE37 /* CounterController.swift */; }; 23 | E08AE8B720E14FB100C0C242 /* ButtonAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08AE8B620E14FB100C0C242 /* ButtonAction.swift */; }; 24 | E08AE8B820E14FB100C0C242 /* ButtonAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08AE8B620E14FB100C0C242 /* ButtonAction.swift */; }; 25 | E0E2B5322232BD9E002D6AEB /* NSObject+KeyPathBinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0E2B5312232BD9E002D6AEB /* NSObject+KeyPathBinder.swift */; }; 26 | E0E2B5332232BD9E002D6AEB /* NSObject+KeyPathBinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0E2B5312232BD9E002D6AEB /* NSObject+KeyPathBinder.swift */; }; 27 | E0EA515320DD49F400AEA939 /* Mvce.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E04913CB20DD482F004CA5B9 /* Mvce.framework */; }; 28 | E0EA515420DD49FC00AEA939 /* Mvce.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E04913CF20DD482F004CA5B9 /* Mvce.framework */; }; 29 | /* End PBXBuildFile section */ 30 | 31 | /* Begin PBXContainerItemProxy section */ 32 | E04913CA20DD482F004CA5B9 /* PBXContainerItemProxy */ = { 33 | isa = PBXContainerItemProxy; 34 | containerPortal = E04913C420DD482F004CA5B9 /* Mvce.xcodeproj */; 35 | proxyType = 2; 36 | remoteGlobalIDString = E0E7B38620D3B13600E7F6C1; 37 | remoteInfo = Mvce; 38 | }; 39 | E04913CC20DD482F004CA5B9 /* PBXContainerItemProxy */ = { 40 | isa = PBXContainerItemProxy; 41 | containerPortal = E04913C420DD482F004CA5B9 /* Mvce.xcodeproj */; 42 | proxyType = 2; 43 | remoteGlobalIDString = E0E7B38F20D3B13600E7F6C1; 44 | remoteInfo = MvceTests; 45 | }; 46 | E04913CE20DD482F004CA5B9 /* PBXContainerItemProxy */ = { 47 | isa = PBXContainerItemProxy; 48 | containerPortal = E04913C420DD482F004CA5B9 /* Mvce.xcodeproj */; 49 | proxyType = 2; 50 | remoteGlobalIDString = E0707B8020D57B8E0054B059; 51 | remoteInfo = "Mvce-macOS"; 52 | }; 53 | /* End PBXContainerItemProxy section */ 54 | 55 | /* Begin PBXFileReference section */ 56 | E04913C420DD482F004CA5B9 /* Mvce.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Mvce.xcodeproj; path = ../../Mvce.xcodeproj; sourceTree = ""; }; 57 | E051BFF320D81A6F005CDE37 /* Counter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Counter.app; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | E051BFF620D81A6F005CDE37 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 59 | E051BFF820D81A6F005CDE37 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 60 | E051BFFB20D81A6F005CDE37 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 61 | E051BFFD20D81A71005CDE37 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 62 | E051C00020D81A71005CDE37 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 63 | E051C00220D81A71005CDE37 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64 | E051C01C20D81AEC005CDE37 /* Counter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Counter.app; sourceTree = BUILT_PRODUCTS_DIR; }; 65 | E051C01E20D81AEC005CDE37 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 66 | E051C02020D81AEC005CDE37 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 67 | E051C02220D81AED005CDE37 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 68 | E051C02520D81AED005CDE37 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 69 | E051C02720D81AED005CDE37 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 70 | E051C02820D81AED005CDE37 /* Counter_macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Counter_macOS.entitlements; sourceTree = ""; }; 71 | E051C02D20D81C44005CDE37 /* CounterModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterModel.swift; sourceTree = ""; }; 72 | E051C03020D81C53005CDE37 /* CounterController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterController.swift; sourceTree = ""; }; 73 | E08AE8B620E14FB100C0C242 /* ButtonAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonAction.swift; sourceTree = ""; }; 74 | E0E2B5312232BD9E002D6AEB /* NSObject+KeyPathBinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSObject+KeyPathBinder.swift"; sourceTree = ""; }; 75 | /* End PBXFileReference section */ 76 | 77 | /* Begin PBXFrameworksBuildPhase section */ 78 | E051BFF020D81A6F005CDE37 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | E0EA515320DD49F400AEA939 /* Mvce.framework in Frameworks */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | E051C01920D81AEC005CDE37 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | E0EA515420DD49FC00AEA939 /* Mvce.framework in Frameworks */, 91 | ); 92 | runOnlyForDeploymentPostprocessing = 0; 93 | }; 94 | /* End PBXFrameworksBuildPhase section */ 95 | 96 | /* Begin PBXGroup section */ 97 | E04913C520DD482F004CA5B9 /* Products */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | E04913CB20DD482F004CA5B9 /* Mvce.framework */, 101 | E04913CD20DD482F004CA5B9 /* MvceTests.xctest */, 102 | E04913CF20DD482F004CA5B9 /* Mvce.framework */, 103 | ); 104 | name = Products; 105 | sourceTree = ""; 106 | }; 107 | E051BFEA20D81A6F005CDE37 = { 108 | isa = PBXGroup; 109 | children = ( 110 | E04913C420DD482F004CA5B9 /* Mvce.xcodeproj */, 111 | E051C02C20D81B27005CDE37 /* Shared */, 112 | E051BFF520D81A6F005CDE37 /* iOS */, 113 | E051C01D20D81AEC005CDE37 /* macOS */, 114 | E051BFF420D81A6F005CDE37 /* Products */, 115 | E051C03320D81CA3005CDE37 /* Frameworks */, 116 | ); 117 | sourceTree = ""; 118 | }; 119 | E051BFF420D81A6F005CDE37 /* Products */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | E051BFF320D81A6F005CDE37 /* Counter.app */, 123 | E051C01C20D81AEC005CDE37 /* Counter.app */, 124 | ); 125 | name = Products; 126 | sourceTree = ""; 127 | }; 128 | E051BFF520D81A6F005CDE37 /* iOS */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | E051BFF620D81A6F005CDE37 /* AppDelegate.swift */, 132 | E051BFF820D81A6F005CDE37 /* ViewController.swift */, 133 | E051BFFA20D81A6F005CDE37 /* Main.storyboard */, 134 | E051BFFD20D81A71005CDE37 /* Assets.xcassets */, 135 | E051BFFF20D81A71005CDE37 /* LaunchScreen.storyboard */, 136 | E051C00220D81A71005CDE37 /* Info.plist */, 137 | ); 138 | path = iOS; 139 | sourceTree = ""; 140 | }; 141 | E051C01D20D81AEC005CDE37 /* macOS */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | E051C01E20D81AEC005CDE37 /* AppDelegate.swift */, 145 | E051C02020D81AEC005CDE37 /* ViewController.swift */, 146 | E051C02220D81AED005CDE37 /* Assets.xcassets */, 147 | E051C02420D81AED005CDE37 /* Main.storyboard */, 148 | E051C02720D81AED005CDE37 /* Info.plist */, 149 | E051C02820D81AED005CDE37 /* Counter_macOS.entitlements */, 150 | ); 151 | path = macOS; 152 | sourceTree = ""; 153 | }; 154 | E051C02C20D81B27005CDE37 /* Shared */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | E051C02D20D81C44005CDE37 /* CounterModel.swift */, 158 | E051C03020D81C53005CDE37 /* CounterController.swift */, 159 | E08AE8B620E14FB100C0C242 /* ButtonAction.swift */, 160 | E0E2B5312232BD9E002D6AEB /* NSObject+KeyPathBinder.swift */, 161 | ); 162 | path = Shared; 163 | sourceTree = ""; 164 | }; 165 | E051C03320D81CA3005CDE37 /* Frameworks */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | ); 169 | name = Frameworks; 170 | sourceTree = ""; 171 | }; 172 | /* End PBXGroup section */ 173 | 174 | /* Begin PBXNativeTarget section */ 175 | E051BFF220D81A6F005CDE37 /* Counter-iOS */ = { 176 | isa = PBXNativeTarget; 177 | buildConfigurationList = E051C00520D81A71005CDE37 /* Build configuration list for PBXNativeTarget "Counter-iOS" */; 178 | buildPhases = ( 179 | E051BFEF20D81A6F005CDE37 /* Sources */, 180 | E051BFF020D81A6F005CDE37 /* Frameworks */, 181 | E051BFF120D81A6F005CDE37 /* Resources */, 182 | ); 183 | buildRules = ( 184 | ); 185 | dependencies = ( 186 | ); 187 | name = "Counter-iOS"; 188 | productName = Counter; 189 | productReference = E051BFF320D81A6F005CDE37 /* Counter.app */; 190 | productType = "com.apple.product-type.application"; 191 | }; 192 | E051C01B20D81AEC005CDE37 /* Counter-macOS */ = { 193 | isa = PBXNativeTarget; 194 | buildConfigurationList = E051C02920D81AED005CDE37 /* Build configuration list for PBXNativeTarget "Counter-macOS" */; 195 | buildPhases = ( 196 | E051C01820D81AEC005CDE37 /* Sources */, 197 | E051C01920D81AEC005CDE37 /* Frameworks */, 198 | E051C01A20D81AEC005CDE37 /* Resources */, 199 | ); 200 | buildRules = ( 201 | ); 202 | dependencies = ( 203 | ); 204 | name = "Counter-macOS"; 205 | productName = "Counter-macOS"; 206 | productReference = E051C01C20D81AEC005CDE37 /* Counter.app */; 207 | productType = "com.apple.product-type.application"; 208 | }; 209 | /* End PBXNativeTarget section */ 210 | 211 | /* Begin PBXProject section */ 212 | E051BFEB20D81A6F005CDE37 /* Project object */ = { 213 | isa = PBXProject; 214 | attributes = { 215 | LastSwiftUpdateCheck = 0940; 216 | LastUpgradeCheck = 0940; 217 | ORGANIZATIONNAME = realazy; 218 | TargetAttributes = { 219 | E051BFF220D81A6F005CDE37 = { 220 | CreatedOnToolsVersion = 9.4; 221 | LastSwiftMigration = 1000; 222 | }; 223 | E051C01B20D81AEC005CDE37 = { 224 | CreatedOnToolsVersion = 9.4; 225 | LastSwiftMigration = 1000; 226 | }; 227 | }; 228 | }; 229 | buildConfigurationList = E051BFEE20D81A6F005CDE37 /* Build configuration list for PBXProject "Counter" */; 230 | compatibilityVersion = "Xcode 9.3"; 231 | developmentRegion = en; 232 | hasScannedForEncodings = 0; 233 | knownRegions = ( 234 | en, 235 | Base, 236 | ); 237 | mainGroup = E051BFEA20D81A6F005CDE37; 238 | productRefGroup = E051BFF420D81A6F005CDE37 /* Products */; 239 | projectDirPath = ""; 240 | projectReferences = ( 241 | { 242 | ProductGroup = E04913C520DD482F004CA5B9 /* Products */; 243 | ProjectRef = E04913C420DD482F004CA5B9 /* Mvce.xcodeproj */; 244 | }, 245 | ); 246 | projectRoot = ""; 247 | targets = ( 248 | E051BFF220D81A6F005CDE37 /* Counter-iOS */, 249 | E051C01B20D81AEC005CDE37 /* Counter-macOS */, 250 | ); 251 | }; 252 | /* End PBXProject section */ 253 | 254 | /* Begin PBXReferenceProxy section */ 255 | E04913CB20DD482F004CA5B9 /* Mvce.framework */ = { 256 | isa = PBXReferenceProxy; 257 | fileType = wrapper.framework; 258 | path = Mvce.framework; 259 | remoteRef = E04913CA20DD482F004CA5B9 /* PBXContainerItemProxy */; 260 | sourceTree = BUILT_PRODUCTS_DIR; 261 | }; 262 | E04913CD20DD482F004CA5B9 /* MvceTests.xctest */ = { 263 | isa = PBXReferenceProxy; 264 | fileType = wrapper.cfbundle; 265 | path = MvceTests.xctest; 266 | remoteRef = E04913CC20DD482F004CA5B9 /* PBXContainerItemProxy */; 267 | sourceTree = BUILT_PRODUCTS_DIR; 268 | }; 269 | E04913CF20DD482F004CA5B9 /* Mvce.framework */ = { 270 | isa = PBXReferenceProxy; 271 | fileType = wrapper.framework; 272 | path = Mvce.framework; 273 | remoteRef = E04913CE20DD482F004CA5B9 /* PBXContainerItemProxy */; 274 | sourceTree = BUILT_PRODUCTS_DIR; 275 | }; 276 | /* End PBXReferenceProxy section */ 277 | 278 | /* Begin PBXResourcesBuildPhase section */ 279 | E051BFF120D81A6F005CDE37 /* Resources */ = { 280 | isa = PBXResourcesBuildPhase; 281 | buildActionMask = 2147483647; 282 | files = ( 283 | E051C00120D81A71005CDE37 /* LaunchScreen.storyboard in Resources */, 284 | E051BFFE20D81A71005CDE37 /* Assets.xcassets in Resources */, 285 | E051BFFC20D81A6F005CDE37 /* Main.storyboard in Resources */, 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | }; 289 | E051C01A20D81AEC005CDE37 /* Resources */ = { 290 | isa = PBXResourcesBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | E051C02320D81AED005CDE37 /* Assets.xcassets in Resources */, 294 | E051C02620D81AED005CDE37 /* Main.storyboard in Resources */, 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | /* End PBXResourcesBuildPhase section */ 299 | 300 | /* Begin PBXSourcesBuildPhase section */ 301 | E051BFEF20D81A6F005CDE37 /* Sources */ = { 302 | isa = PBXSourcesBuildPhase; 303 | buildActionMask = 2147483647; 304 | files = ( 305 | E051BFF920D81A6F005CDE37 /* ViewController.swift in Sources */, 306 | E051BFF720D81A6F005CDE37 /* AppDelegate.swift in Sources */, 307 | E051C03120D81C53005CDE37 /* CounterController.swift in Sources */, 308 | E051C02E20D81C44005CDE37 /* CounterModel.swift in Sources */, 309 | E0E2B5322232BD9E002D6AEB /* NSObject+KeyPathBinder.swift in Sources */, 310 | E08AE8B720E14FB100C0C242 /* ButtonAction.swift in Sources */, 311 | ); 312 | runOnlyForDeploymentPostprocessing = 0; 313 | }; 314 | E051C01820D81AEC005CDE37 /* Sources */ = { 315 | isa = PBXSourcesBuildPhase; 316 | buildActionMask = 2147483647; 317 | files = ( 318 | E051C02120D81AEC005CDE37 /* ViewController.swift in Sources */, 319 | E051C01F20D81AEC005CDE37 /* AppDelegate.swift in Sources */, 320 | E051C03220D81C53005CDE37 /* CounterController.swift in Sources */, 321 | E051C02F20D81C44005CDE37 /* CounterModel.swift in Sources */, 322 | E0E2B5332232BD9E002D6AEB /* NSObject+KeyPathBinder.swift in Sources */, 323 | E08AE8B820E14FB100C0C242 /* ButtonAction.swift in Sources */, 324 | ); 325 | runOnlyForDeploymentPostprocessing = 0; 326 | }; 327 | /* End PBXSourcesBuildPhase section */ 328 | 329 | /* Begin PBXVariantGroup section */ 330 | E051BFFA20D81A6F005CDE37 /* Main.storyboard */ = { 331 | isa = PBXVariantGroup; 332 | children = ( 333 | E051BFFB20D81A6F005CDE37 /* Base */, 334 | ); 335 | name = Main.storyboard; 336 | sourceTree = ""; 337 | }; 338 | E051BFFF20D81A71005CDE37 /* LaunchScreen.storyboard */ = { 339 | isa = PBXVariantGroup; 340 | children = ( 341 | E051C00020D81A71005CDE37 /* Base */, 342 | ); 343 | name = LaunchScreen.storyboard; 344 | sourceTree = ""; 345 | }; 346 | E051C02420D81AED005CDE37 /* Main.storyboard */ = { 347 | isa = PBXVariantGroup; 348 | children = ( 349 | E051C02520D81AED005CDE37 /* Base */, 350 | ); 351 | name = Main.storyboard; 352 | sourceTree = ""; 353 | }; 354 | /* End PBXVariantGroup section */ 355 | 356 | /* Begin XCBuildConfiguration section */ 357 | E051C00320D81A71005CDE37 /* Debug */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | ALWAYS_SEARCH_USER_PATHS = NO; 361 | CLANG_ANALYZER_NONNULL = YES; 362 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 363 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 364 | CLANG_CXX_LIBRARY = "libc++"; 365 | CLANG_ENABLE_MODULES = YES; 366 | CLANG_ENABLE_OBJC_ARC = YES; 367 | CLANG_ENABLE_OBJC_WEAK = YES; 368 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 369 | CLANG_WARN_BOOL_CONVERSION = YES; 370 | CLANG_WARN_COMMA = YES; 371 | CLANG_WARN_CONSTANT_CONVERSION = YES; 372 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 373 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 374 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 375 | CLANG_WARN_EMPTY_BODY = YES; 376 | CLANG_WARN_ENUM_CONVERSION = YES; 377 | CLANG_WARN_INFINITE_RECURSION = YES; 378 | CLANG_WARN_INT_CONVERSION = YES; 379 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 380 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 381 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 382 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 383 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 384 | CLANG_WARN_STRICT_PROTOTYPES = YES; 385 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 386 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 387 | CLANG_WARN_UNREACHABLE_CODE = YES; 388 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 389 | CODE_SIGN_IDENTITY = "iPhone Developer"; 390 | COPY_PHASE_STRIP = NO; 391 | DEBUG_INFORMATION_FORMAT = dwarf; 392 | ENABLE_STRICT_OBJC_MSGSEND = YES; 393 | ENABLE_TESTABILITY = YES; 394 | GCC_C_LANGUAGE_STANDARD = gnu11; 395 | GCC_DYNAMIC_NO_PIC = NO; 396 | GCC_NO_COMMON_BLOCKS = YES; 397 | GCC_OPTIMIZATION_LEVEL = 0; 398 | GCC_PREPROCESSOR_DEFINITIONS = ( 399 | "DEBUG=1", 400 | "$(inherited)", 401 | ); 402 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 403 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 404 | GCC_WARN_UNDECLARED_SELECTOR = YES; 405 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 406 | GCC_WARN_UNUSED_FUNCTION = YES; 407 | GCC_WARN_UNUSED_VARIABLE = YES; 408 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 409 | MTL_ENABLE_DEBUG_INFO = YES; 410 | ONLY_ACTIVE_ARCH = YES; 411 | SDKROOT = iphoneos; 412 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 413 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 414 | }; 415 | name = Debug; 416 | }; 417 | E051C00420D81A71005CDE37 /* Release */ = { 418 | isa = XCBuildConfiguration; 419 | buildSettings = { 420 | ALWAYS_SEARCH_USER_PATHS = NO; 421 | CLANG_ANALYZER_NONNULL = YES; 422 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 423 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 424 | CLANG_CXX_LIBRARY = "libc++"; 425 | CLANG_ENABLE_MODULES = YES; 426 | CLANG_ENABLE_OBJC_ARC = YES; 427 | CLANG_ENABLE_OBJC_WEAK = YES; 428 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 429 | CLANG_WARN_BOOL_CONVERSION = YES; 430 | CLANG_WARN_COMMA = YES; 431 | CLANG_WARN_CONSTANT_CONVERSION = YES; 432 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 433 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 434 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 435 | CLANG_WARN_EMPTY_BODY = YES; 436 | CLANG_WARN_ENUM_CONVERSION = YES; 437 | CLANG_WARN_INFINITE_RECURSION = YES; 438 | CLANG_WARN_INT_CONVERSION = YES; 439 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 440 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 441 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 442 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 443 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 444 | CLANG_WARN_STRICT_PROTOTYPES = YES; 445 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 446 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 447 | CLANG_WARN_UNREACHABLE_CODE = YES; 448 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 449 | CODE_SIGN_IDENTITY = "iPhone Developer"; 450 | COPY_PHASE_STRIP = NO; 451 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 452 | ENABLE_NS_ASSERTIONS = NO; 453 | ENABLE_STRICT_OBJC_MSGSEND = YES; 454 | GCC_C_LANGUAGE_STANDARD = gnu11; 455 | GCC_NO_COMMON_BLOCKS = YES; 456 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 457 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 458 | GCC_WARN_UNDECLARED_SELECTOR = YES; 459 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 460 | GCC_WARN_UNUSED_FUNCTION = YES; 461 | GCC_WARN_UNUSED_VARIABLE = YES; 462 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 463 | MTL_ENABLE_DEBUG_INFO = NO; 464 | SDKROOT = iphoneos; 465 | SWIFT_COMPILATION_MODE = wholemodule; 466 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 467 | VALIDATE_PRODUCT = YES; 468 | }; 469 | name = Release; 470 | }; 471 | E051C00620D81A71005CDE37 /* Debug */ = { 472 | isa = XCBuildConfiguration; 473 | buildSettings = { 474 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 475 | CODE_SIGN_STYLE = Automatic; 476 | INFOPLIST_FILE = iOS/Info.plist; 477 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 478 | LD_RUNPATH_SEARCH_PATHS = ( 479 | "$(inherited)", 480 | "@executable_path/Frameworks", 481 | ); 482 | PRODUCT_BUNDLE_IDENTIFIER = com.realazy.Counter; 483 | PRODUCT_NAME = Counter; 484 | SWIFT_VERSION = 4.2; 485 | TARGETED_DEVICE_FAMILY = "1,2"; 486 | }; 487 | name = Debug; 488 | }; 489 | E051C00720D81A71005CDE37 /* Release */ = { 490 | isa = XCBuildConfiguration; 491 | buildSettings = { 492 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 493 | CODE_SIGN_STYLE = Automatic; 494 | INFOPLIST_FILE = iOS/Info.plist; 495 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 496 | LD_RUNPATH_SEARCH_PATHS = ( 497 | "$(inherited)", 498 | "@executable_path/Frameworks", 499 | ); 500 | PRODUCT_BUNDLE_IDENTIFIER = com.realazy.Counter; 501 | PRODUCT_NAME = Counter; 502 | SWIFT_VERSION = 4.2; 503 | TARGETED_DEVICE_FAMILY = "1,2"; 504 | }; 505 | name = Release; 506 | }; 507 | E051C02A20D81AED005CDE37 /* Debug */ = { 508 | isa = XCBuildConfiguration; 509 | buildSettings = { 510 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 511 | CODE_SIGN_ENTITLEMENTS = macOS/Counter_macOS.entitlements; 512 | CODE_SIGN_IDENTITY = "-"; 513 | CODE_SIGN_STYLE = Automatic; 514 | COMBINE_HIDPI_IMAGES = YES; 515 | INFOPLIST_FILE = macOS/Info.plist; 516 | LD_RUNPATH_SEARCH_PATHS = ( 517 | "$(inherited)", 518 | "@executable_path/../Frameworks", 519 | ); 520 | MACOSX_DEPLOYMENT_TARGET = 10.13; 521 | PRODUCT_BUNDLE_IDENTIFIER = "com.realazy.Counter-macOS"; 522 | PRODUCT_NAME = Counter; 523 | SDKROOT = macosx; 524 | SWIFT_VERSION = 4.2; 525 | }; 526 | name = Debug; 527 | }; 528 | E051C02B20D81AED005CDE37 /* Release */ = { 529 | isa = XCBuildConfiguration; 530 | buildSettings = { 531 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 532 | CODE_SIGN_ENTITLEMENTS = macOS/Counter_macOS.entitlements; 533 | CODE_SIGN_IDENTITY = "-"; 534 | CODE_SIGN_STYLE = Automatic; 535 | COMBINE_HIDPI_IMAGES = YES; 536 | INFOPLIST_FILE = macOS/Info.plist; 537 | LD_RUNPATH_SEARCH_PATHS = ( 538 | "$(inherited)", 539 | "@executable_path/../Frameworks", 540 | ); 541 | MACOSX_DEPLOYMENT_TARGET = 10.13; 542 | PRODUCT_BUNDLE_IDENTIFIER = "com.realazy.Counter-macOS"; 543 | PRODUCT_NAME = Counter; 544 | SDKROOT = macosx; 545 | SWIFT_VERSION = 4.2; 546 | }; 547 | name = Release; 548 | }; 549 | /* End XCBuildConfiguration section */ 550 | 551 | /* Begin XCConfigurationList section */ 552 | E051BFEE20D81A6F005CDE37 /* Build configuration list for PBXProject "Counter" */ = { 553 | isa = XCConfigurationList; 554 | buildConfigurations = ( 555 | E051C00320D81A71005CDE37 /* Debug */, 556 | E051C00420D81A71005CDE37 /* Release */, 557 | ); 558 | defaultConfigurationIsVisible = 0; 559 | defaultConfigurationName = Release; 560 | }; 561 | E051C00520D81A71005CDE37 /* Build configuration list for PBXNativeTarget "Counter-iOS" */ = { 562 | isa = XCConfigurationList; 563 | buildConfigurations = ( 564 | E051C00620D81A71005CDE37 /* Debug */, 565 | E051C00720D81A71005CDE37 /* Release */, 566 | ); 567 | defaultConfigurationIsVisible = 0; 568 | defaultConfigurationName = Release; 569 | }; 570 | E051C02920D81AED005CDE37 /* Build configuration list for PBXNativeTarget "Counter-macOS" */ = { 571 | isa = XCConfigurationList; 572 | buildConfigurations = ( 573 | E051C02A20D81AED005CDE37 /* Debug */, 574 | E051C02B20D81AED005CDE37 /* Release */, 575 | ); 576 | defaultConfigurationIsVisible = 0; 577 | defaultConfigurationName = Release; 578 | }; 579 | /* End XCConfigurationList section */ 580 | }; 581 | rootObject = E051BFEB20D81A6F005CDE37 /* Project object */; 582 | } 583 | -------------------------------------------------------------------------------- /Example/Counter/Counter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Counter/Counter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Counter/Shared/ButtonAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonAction.swift 3 | // Counter 4 | // 5 | // Created by CHEN Xian-an on 2018/6/26. 6 | // Copyright © 2018 realazy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class ButtonAction: NSObject { 12 | let sendEvent: (CounterEvent) -> Void 13 | 14 | init(sendEvent: @escaping (CounterEvent) -> Void) { 15 | self.sendEvent = sendEvent 16 | } 17 | 18 | @objc func incr(_ sender: Any?) { 19 | sendEvent(.increment) 20 | } 21 | 22 | @objc func decr(_ sender: Any?) { 23 | sendEvent(.decrement) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Example/Counter/Shared/CounterController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CounterController.swift 3 | // Counter 4 | // 5 | // Created by CHEN Xian-an on 2018/6/19. 6 | // Copyright © 2018 realazy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Mvce 11 | 12 | enum CounterEvent { 13 | case increment 14 | case decrement 15 | } 16 | 17 | struct CounterController: Controller { 18 | typealias Model = CounterModel 19 | typealias Event = CounterEvent 20 | 21 | func update(model: Model, for event: Event, dispatcher: Dispatcher) { 22 | switch event { 23 | case .increment: 24 | model.count += 1 25 | case .decrement: 26 | model.count -= 1 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Example/Counter/Shared/CounterModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CounterModel.swift 3 | // Counter 4 | // 5 | // Created by CHEN Xian-an on 2018/6/19. 6 | // Copyright © 2018 realazy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class CounterModel: NSObject { 12 | @objc dynamic var count = 0 13 | } 14 | -------------------------------------------------------------------------------- /Example/Counter/Shared/NSObject+KeyPathBinder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyPathBinder.swift 3 | // Counter 4 | // 5 | // Created by CHEN Xian-an on 2019/3/8. 6 | // Copyright © 2019 realazy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSObjectProtocol where Self: NSObject { 12 | func bind(_ keyPath: KeyPath, skipsInitial: Bool = false, transform: @escaping (V) -> V2, to target: T, using binder: @escaping (T, V2) -> Void) -> NSKeyValueObservation { 13 | return observe(keyPath, options: skipsInitial ? [.new] : [.initial, .new]) { (_, change) in 14 | guard let nv = change.newValue else { return } 15 | DispatchQueue.main.async { binder(target, transform(nv)) } 16 | } 17 | } 18 | 19 | func bind(_ keyPath: KeyPath, skipsInitial: Bool = false, to target: T, using binder: @escaping (T, V) -> Void) -> NSKeyValueObservation { 20 | return bind(keyPath, skipsInitial: skipsInitial, transform: {$0}, to: target, using: binder) 21 | } 22 | 23 | func bind(_ keyPath: KeyPath, skipsInitial: Bool = false, to target: T, at targetKeyPath: ReferenceWritableKeyPath, transform: @escaping (V) -> U) -> NSKeyValueObservation { 24 | return bind(keyPath, skipsInitial: skipsInitial, to: target) { (t, v) in t[keyPath: targetKeyPath] = transform(v) } 25 | } 26 | 27 | func bind(_ keyPath: KeyPath, skipsInitial: Bool = false, to target: T, at targetKeyPath: ReferenceWritableKeyPath) -> NSKeyValueObservation { 28 | return bind(keyPath, skipsInitial: skipsInitial, to: target) { (t, v) in t[keyPath: targetKeyPath] = v } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Example/Counter/iOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Counter 4 | // 5 | // Created by CHEN Xian-an on 2018/6/19. 6 | // Copyright © 2018 realazy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Example/Counter/iOS/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 | } -------------------------------------------------------------------------------- /Example/Counter/iOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/Counter/iOS/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 | -------------------------------------------------------------------------------- /Example/Counter/iOS/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 | 29 | 30 | 31 | 32 | 37 | 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 | -------------------------------------------------------------------------------- /Example/Counter/iOS/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 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example/Counter/iOS/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Counter 4 | // 5 | // Created by CHEN Xian-an on 2018/6/19. 6 | // Copyright © 2018 realazy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Mvce 11 | 12 | final class ViewController: UIViewController { 13 | @IBOutlet weak var label: UILabel! 14 | @IBOutlet weak var incrButton: UIButton! 15 | @IBOutlet weak var decrButton: UIButton! 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | Mvce.glue(model: CounterModel(), view: self, controller: CounterController()) 20 | } 21 | } 22 | 23 | extension ViewController: View { 24 | typealias Model = CounterModel 25 | typealias Event = CounterEvent 26 | 27 | func bind(model: Model, dispatcher: Dispatcher) -> View.BindingDisposer { 28 | let observation = model.bind(\CounterModel.count, to: label, at: \UILabel.text) { String(format: "%d", $0) } 29 | let action = ButtonAction(sendEvent: dispatcher.send(event:)) 30 | incrButton.addTarget(action, action: #selector(action.incr(_:)), for: .touchUpInside) 31 | decrButton.addTarget(action, action: #selector(action.decr(_:)), for: .touchUpInside) 32 | let key: StaticString = #function 33 | objc_setAssociatedObject(self, key.utf8Start, action, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) // Need to retain target 34 | return observation.invalidate 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Example/Counter/macOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Counter-macOS 4 | // 5 | // Created by CHEN Xian-an on 2018/6/19. 6 | // Copyright © 2018 realazy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | 15 | 16 | func applicationDidFinishLaunching(_ aNotification: Notification) { 17 | // Insert code here to initialize your application 18 | } 19 | 20 | func applicationWillTerminate(_ aNotification: Notification) { 21 | // Insert code here to tear down your application 22 | } 23 | 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /Example/Counter/macOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Example/Counter/macOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/Counter/macOS/Counter_macOS.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/Counter/macOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2018 realazy. All rights reserved. 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /Example/Counter/macOS/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Counter-macOS 4 | // 5 | // Created by CHEN Xian-an on 2018/6/19. 6 | // Copyright © 2018 realazy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Mvce 11 | 12 | class ViewController: NSViewController { 13 | @IBOutlet weak var label: NSTextField! 14 | @IBOutlet weak var incrButton: NSButton! 15 | @IBOutlet weak var decrButton: NSButton! 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | Mvce.glue(model: CounterModel(), view: self, controller: CounterController()) 20 | } 21 | } 22 | 23 | extension ViewController: View { 24 | typealias Model = CounterModel 25 | typealias Event = CounterEvent 26 | 27 | func bind(model: Model, dispatcher: Dispatcher) -> View.BindingDisposer { 28 | let observation = model.bind(\.count, to: label, at: \.stringValue) { String(format: "%d", $0) } 29 | let action = ButtonAction(sendEvent: dispatcher.send(event:)) 30 | incrButton.target = action 31 | incrButton.action = #selector(action.incr(_:)) 32 | decrButton.target = action 33 | decrButton.action = #selector(action.decr(_:)) 34 | let key: StaticString = #function 35 | objc_setAssociatedObject(self, key.utf8Start, action, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) // Need to retain target 36 | return observation.invalidate 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/RandomImage/RandomImage.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E025F6962235FDA600FFECA9 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E025F6752235FD5E00FFECA9 /* ReactiveCocoa.framework */; }; 11 | E025F6972235FDA600FFECA9 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E025F6572235FD5600FFECA9 /* ReactiveSwift.framework */; }; 12 | E025F6982235FDA600FFECA9 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E025F63F2235FD4C00FFECA9 /* Result.framework */; }; 13 | E025F6992235FDB500FFECA9 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E025F6712235FD5E00FFECA9 /* ReactiveCocoa.framework */; }; 14 | E025F69A2235FDB500FFECA9 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E025F6532235FD5600FFECA9 /* ReactiveSwift.framework */; }; 15 | E025F69B2235FDB500FFECA9 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E025F63B2235FD4C00FFECA9 /* Result.framework */; }; 16 | E0707B3D20D4C2610054B059 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0707B3C20D4C2610054B059 /* AppDelegate.swift */; }; 17 | E0707B3F20D4C2610054B059 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0707B3E20D4C2610054B059 /* ViewController.swift */; }; 18 | E0707B4220D4C2610054B059 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E0707B4020D4C2610054B059 /* Main.storyboard */; }; 19 | E0707B4420D4C2620054B059 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E0707B4320D4C2620054B059 /* Assets.xcassets */; }; 20 | E0707B4720D4C2620054B059 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E0707B4520D4C2620054B059 /* LaunchScreen.storyboard */; }; 21 | E0707B6120D4C5D90054B059 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0707B6020D4C5D90054B059 /* AppDelegate.swift */; }; 22 | E0707B6320D4C5D90054B059 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0707B6220D4C5D90054B059 /* ViewController.swift */; }; 23 | E0707B6520D4C5DA0054B059 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E0707B6420D4C5DA0054B059 /* Assets.xcassets */; }; 24 | E0707B6820D4C5DA0054B059 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E0707B6620D4C5DA0054B059 /* Main.storyboard */; }; 25 | E0707B7220D55E370054B059 /* ImageController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0707B7120D55E370054B059 /* ImageController.swift */; }; 26 | E0707B7320D55E370054B059 /* ImageController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0707B7120D55E370054B059 /* ImageController.swift */; }; 27 | E0707B7520D55E4E0054B059 /* ImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0707B7420D55E4E0054B059 /* ImageModel.swift */; }; 28 | E0707B7620D55E4E0054B059 /* ImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0707B7420D55E4E0054B059 /* ImageModel.swift */; }; 29 | E0707B7820D579BE0054B059 /* Platform.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0707B7720D579BE0054B059 /* Platform.swift */; }; 30 | E0707B7920D579BE0054B059 /* Platform.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0707B7720D579BE0054B059 /* Platform.swift */; }; 31 | E0EA516220DD83C600AEA939 /* Mvce.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0EA515D20DD83BC00AEA939 /* Mvce.framework */; }; 32 | E0EA516320DD83CB00AEA939 /* Mvce.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0EA516120DD83BC00AEA939 /* Mvce.framework */; }; 33 | /* End PBXBuildFile section */ 34 | 35 | /* Begin PBXContainerItemProxy section */ 36 | E025F63A2235FD4C00FFECA9 /* PBXContainerItemProxy */ = { 37 | isa = PBXContainerItemProxy; 38 | containerPortal = E025F6302235FD4C00FFECA9 /* Result.xcodeproj */; 39 | proxyType = 2; 40 | remoteGlobalIDString = D45480571A9572F5009D7229; 41 | remoteInfo = "Result-Mac"; 42 | }; 43 | E025F63C2235FD4C00FFECA9 /* PBXContainerItemProxy */ = { 44 | isa = PBXContainerItemProxy; 45 | containerPortal = E025F6302235FD4C00FFECA9 /* Result.xcodeproj */; 46 | proxyType = 2; 47 | remoteGlobalIDString = D45480671A9572F5009D7229; 48 | remoteInfo = "Result-MacTests"; 49 | }; 50 | E025F63E2235FD4C00FFECA9 /* PBXContainerItemProxy */ = { 51 | isa = PBXContainerItemProxy; 52 | containerPortal = E025F6302235FD4C00FFECA9 /* Result.xcodeproj */; 53 | proxyType = 2; 54 | remoteGlobalIDString = D454807D1A957361009D7229; 55 | remoteInfo = "Result-iOS"; 56 | }; 57 | E025F6402235FD4C00FFECA9 /* PBXContainerItemProxy */ = { 58 | isa = PBXContainerItemProxy; 59 | containerPortal = E025F6302235FD4C00FFECA9 /* Result.xcodeproj */; 60 | proxyType = 2; 61 | remoteGlobalIDString = D45480871A957362009D7229; 62 | remoteInfo = "Result-iOSTests"; 63 | }; 64 | E025F6422235FD4C00FFECA9 /* PBXContainerItemProxy */ = { 65 | isa = PBXContainerItemProxy; 66 | containerPortal = E025F6302235FD4C00FFECA9 /* Result.xcodeproj */; 67 | proxyType = 2; 68 | remoteGlobalIDString = 57FCDE471BA280DC00130C48; 69 | remoteInfo = "Result-tvOS"; 70 | }; 71 | E025F6442235FD4C00FFECA9 /* PBXContainerItemProxy */ = { 72 | isa = PBXContainerItemProxy; 73 | containerPortal = E025F6302235FD4C00FFECA9 /* Result.xcodeproj */; 74 | proxyType = 2; 75 | remoteGlobalIDString = 57FCDE541BA280E000130C48; 76 | remoteInfo = "Result-tvOSTests"; 77 | }; 78 | E025F6462235FD4C00FFECA9 /* PBXContainerItemProxy */ = { 79 | isa = PBXContainerItemProxy; 80 | containerPortal = E025F6302235FD4C00FFECA9 /* Result.xcodeproj */; 81 | proxyType = 2; 82 | remoteGlobalIDString = D03579A31B2B788F005D26AE; 83 | remoteInfo = "Result-watchOS"; 84 | }; 85 | E025F6522235FD5600FFECA9 /* PBXContainerItemProxy */ = { 86 | isa = PBXContainerItemProxy; 87 | containerPortal = E025F6482235FD5600FFECA9 /* ReactiveSwift.xcodeproj */; 88 | proxyType = 2; 89 | remoteGlobalIDString = D04725EA19E49ED7006002AA; 90 | remoteInfo = "ReactiveSwift-macOS"; 91 | }; 92 | E025F6542235FD5600FFECA9 /* PBXContainerItemProxy */ = { 93 | isa = PBXContainerItemProxy; 94 | containerPortal = E025F6482235FD5600FFECA9 /* ReactiveSwift.xcodeproj */; 95 | proxyType = 2; 96 | remoteGlobalIDString = D04725F519E49ED7006002AA; 97 | remoteInfo = "ReactiveSwift-macOSTests"; 98 | }; 99 | E025F6562235FD5600FFECA9 /* PBXContainerItemProxy */ = { 100 | isa = PBXContainerItemProxy; 101 | containerPortal = E025F6482235FD5600FFECA9 /* ReactiveSwift.xcodeproj */; 102 | proxyType = 2; 103 | remoteGlobalIDString = D047260C19E49F82006002AA; 104 | remoteInfo = "ReactiveSwift-iOS"; 105 | }; 106 | E025F6582235FD5600FFECA9 /* PBXContainerItemProxy */ = { 107 | isa = PBXContainerItemProxy; 108 | containerPortal = E025F6482235FD5600FFECA9 /* ReactiveSwift.xcodeproj */; 109 | proxyType = 2; 110 | remoteGlobalIDString = D047261619E49F82006002AA; 111 | remoteInfo = "ReactiveSwift-iOSTests"; 112 | }; 113 | E025F65A2235FD5600FFECA9 /* PBXContainerItemProxy */ = { 114 | isa = PBXContainerItemProxy; 115 | containerPortal = E025F6482235FD5600FFECA9 /* ReactiveSwift.xcodeproj */; 116 | proxyType = 2; 117 | remoteGlobalIDString = A9B315541B3940610001CB9C; 118 | remoteInfo = "ReactiveSwift-watchOS"; 119 | }; 120 | E025F65C2235FD5600FFECA9 /* PBXContainerItemProxy */ = { 121 | isa = PBXContainerItemProxy; 122 | containerPortal = E025F6482235FD5600FFECA9 /* ReactiveSwift.xcodeproj */; 123 | proxyType = 2; 124 | remoteGlobalIDString = 57A4D2411BA13D7A00F7D4B1; 125 | remoteInfo = "ReactiveSwift-tvOS"; 126 | }; 127 | E025F65E2235FD5600FFECA9 /* PBXContainerItemProxy */ = { 128 | isa = PBXContainerItemProxy; 129 | containerPortal = E025F6482235FD5600FFECA9 /* ReactiveSwift.xcodeproj */; 130 | proxyType = 2; 131 | remoteGlobalIDString = 7DFBED031CDB8C9500EE435B; 132 | remoteInfo = "ReactiveSwift-tvOSTests"; 133 | }; 134 | E025F6702235FD5E00FFECA9 /* PBXContainerItemProxy */ = { 135 | isa = PBXContainerItemProxy; 136 | containerPortal = E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */; 137 | proxyType = 2; 138 | remoteGlobalIDString = D04725EA19E49ED7006002AA; 139 | remoteInfo = "ReactiveCocoa-macOS"; 140 | }; 141 | E025F6722235FD5E00FFECA9 /* PBXContainerItemProxy */ = { 142 | isa = PBXContainerItemProxy; 143 | containerPortal = E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */; 144 | proxyType = 2; 145 | remoteGlobalIDString = D04725F519E49ED7006002AA; 146 | remoteInfo = "ReactiveCocoa-macOSTests"; 147 | }; 148 | E025F6742235FD5E00FFECA9 /* PBXContainerItemProxy */ = { 149 | isa = PBXContainerItemProxy; 150 | containerPortal = E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */; 151 | proxyType = 2; 152 | remoteGlobalIDString = D047260C19E49F82006002AA; 153 | remoteInfo = "ReactiveCocoa-iOS"; 154 | }; 155 | E025F6762235FD5E00FFECA9 /* PBXContainerItemProxy */ = { 156 | isa = PBXContainerItemProxy; 157 | containerPortal = E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */; 158 | proxyType = 2; 159 | remoteGlobalIDString = D047261619E49F82006002AA; 160 | remoteInfo = "ReactiveCocoa-iOSTests"; 161 | }; 162 | E025F6782235FD5E00FFECA9 /* PBXContainerItemProxy */ = { 163 | isa = PBXContainerItemProxy; 164 | containerPortal = E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */; 165 | proxyType = 2; 166 | remoteGlobalIDString = A9B315541B3940610001CB9C; 167 | remoteInfo = "ReactiveCocoa-watchOS"; 168 | }; 169 | E025F67A2235FD5E00FFECA9 /* PBXContainerItemProxy */ = { 170 | isa = PBXContainerItemProxy; 171 | containerPortal = E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */; 172 | proxyType = 2; 173 | remoteGlobalIDString = 57A4D2411BA13D7A00F7D4B1; 174 | remoteInfo = "ReactiveCocoa-tvOS"; 175 | }; 176 | E025F67C2235FD5E00FFECA9 /* PBXContainerItemProxy */ = { 177 | isa = PBXContainerItemProxy; 178 | containerPortal = E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */; 179 | proxyType = 2; 180 | remoteGlobalIDString = 7DFBED031CDB8C9500EE435B; 181 | remoteInfo = "ReactiveCocoa-tvOSTests"; 182 | }; 183 | E025F67E2235FD5E00FFECA9 /* PBXContainerItemProxy */ = { 184 | isa = PBXContainerItemProxy; 185 | containerPortal = E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */; 186 | proxyType = 2; 187 | remoteGlobalIDString = 9AC03A571F7CC3BF00EC33C1; 188 | remoteInfo = "ReactiveMapKit-macOS"; 189 | }; 190 | E025F6802235FD5E00FFECA9 /* PBXContainerItemProxy */ = { 191 | isa = PBXContainerItemProxy; 192 | containerPortal = E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */; 193 | proxyType = 2; 194 | remoteGlobalIDString = 9A73DFF8216D3CEB0069AD76; 195 | remoteInfo = "ReactiveMapKitTests-macOS"; 196 | }; 197 | E025F6822235FD5E00FFECA9 /* PBXContainerItemProxy */ = { 198 | isa = PBXContainerItemProxy; 199 | containerPortal = E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */; 200 | proxyType = 2; 201 | remoteGlobalIDString = 9A73DFBB216D3C550069AD76; 202 | remoteInfo = "ReactiveMapKit-iOS"; 203 | }; 204 | E025F6842235FD5E00FFECA9 /* PBXContainerItemProxy */ = { 205 | isa = PBXContainerItemProxy; 206 | containerPortal = E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */; 207 | proxyType = 2; 208 | remoteGlobalIDString = 9A16753D1F80C35100B63650; 209 | remoteInfo = "ReactiveMapKitTests-iOS"; 210 | }; 211 | E025F6862235FD5E00FFECA9 /* PBXContainerItemProxy */ = { 212 | isa = PBXContainerItemProxy; 213 | containerPortal = E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */; 214 | proxyType = 2; 215 | remoteGlobalIDString = 9A73DFCC216D3C570069AD76; 216 | remoteInfo = "ReactiveMapKit-tvOS"; 217 | }; 218 | E025F6882235FD5E00FFECA9 /* PBXContainerItemProxy */ = { 219 | isa = PBXContainerItemProxy; 220 | containerPortal = E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */; 221 | proxyType = 2; 222 | remoteGlobalIDString = 9A73E013216D3CEE0069AD76; 223 | remoteInfo = "ReactiveMapKitTests-tvOS"; 224 | }; 225 | E025F68A2235FD6B00FFECA9 /* PBXContainerItemProxy */ = { 226 | isa = PBXContainerItemProxy; 227 | containerPortal = E025F6302235FD4C00FFECA9 /* Result.xcodeproj */; 228 | proxyType = 1; 229 | remoteGlobalIDString = D454807C1A957361009D7229; 230 | remoteInfo = "Result-iOS"; 231 | }; 232 | E025F68C2235FD6B00FFECA9 /* PBXContainerItemProxy */ = { 233 | isa = PBXContainerItemProxy; 234 | containerPortal = E025F6482235FD5600FFECA9 /* ReactiveSwift.xcodeproj */; 235 | proxyType = 1; 236 | remoteGlobalIDString = D047260B19E49F82006002AA; 237 | remoteInfo = "ReactiveSwift-iOS"; 238 | }; 239 | E025F68E2235FD6B00FFECA9 /* PBXContainerItemProxy */ = { 240 | isa = PBXContainerItemProxy; 241 | containerPortal = E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */; 242 | proxyType = 1; 243 | remoteGlobalIDString = D047260B19E49F82006002AA; 244 | remoteInfo = "ReactiveCocoa-iOS"; 245 | }; 246 | E025F6902235FD7500FFECA9 /* PBXContainerItemProxy */ = { 247 | isa = PBXContainerItemProxy; 248 | containerPortal = E025F6302235FD4C00FFECA9 /* Result.xcodeproj */; 249 | proxyType = 1; 250 | remoteGlobalIDString = D45480561A9572F5009D7229; 251 | remoteInfo = "Result-Mac"; 252 | }; 253 | E025F6922235FD7500FFECA9 /* PBXContainerItemProxy */ = { 254 | isa = PBXContainerItemProxy; 255 | containerPortal = E025F6482235FD5600FFECA9 /* ReactiveSwift.xcodeproj */; 256 | proxyType = 1; 257 | remoteGlobalIDString = D04725E919E49ED7006002AA; 258 | remoteInfo = "ReactiveSwift-macOS"; 259 | }; 260 | E025F6942235FD7500FFECA9 /* PBXContainerItemProxy */ = { 261 | isa = PBXContainerItemProxy; 262 | containerPortal = E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */; 263 | proxyType = 1; 264 | remoteGlobalIDString = D04725E919E49ED7006002AA; 265 | remoteInfo = "ReactiveCocoa-macOS"; 266 | }; 267 | E0EA515C20DD83BC00AEA939 /* PBXContainerItemProxy */ = { 268 | isa = PBXContainerItemProxy; 269 | containerPortal = E0EA515620DD83BC00AEA939 /* Mvce.xcodeproj */; 270 | proxyType = 2; 271 | remoteGlobalIDString = E0E7B38620D3B13600E7F6C1; 272 | remoteInfo = Mvce; 273 | }; 274 | E0EA515E20DD83BC00AEA939 /* PBXContainerItemProxy */ = { 275 | isa = PBXContainerItemProxy; 276 | containerPortal = E0EA515620DD83BC00AEA939 /* Mvce.xcodeproj */; 277 | proxyType = 2; 278 | remoteGlobalIDString = E0E7B38F20D3B13600E7F6C1; 279 | remoteInfo = MvceTests; 280 | }; 281 | E0EA516020DD83BC00AEA939 /* PBXContainerItemProxy */ = { 282 | isa = PBXContainerItemProxy; 283 | containerPortal = E0EA515620DD83BC00AEA939 /* Mvce.xcodeproj */; 284 | proxyType = 2; 285 | remoteGlobalIDString = E0707B8020D57B8E0054B059; 286 | remoteInfo = "Mvce-macOS"; 287 | }; 288 | /* End PBXContainerItemProxy section */ 289 | 290 | /* Begin PBXFileReference section */ 291 | E025F6302235FD4C00FFECA9 /* Result.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Result.xcodeproj; path = ReactiveCocoa/Carthage/Checkouts/Result/Result.xcodeproj; sourceTree = ""; }; 292 | E025F6482235FD5600FFECA9 /* ReactiveSwift.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactiveSwift.xcodeproj; path = ReactiveCocoa/Carthage/Checkouts/ReactiveSwift/ReactiveSwift.xcodeproj; sourceTree = ""; }; 293 | E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactiveCocoa.xcodeproj; path = ReactiveCocoa/ReactiveCocoa.xcodeproj; sourceTree = ""; }; 294 | E0707B3920D4C2610054B059 /* RandomImage.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RandomImage.app; sourceTree = BUILT_PRODUCTS_DIR; }; 295 | E0707B3C20D4C2610054B059 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 296 | E0707B3E20D4C2610054B059 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 297 | E0707B4120D4C2610054B059 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 298 | E0707B4320D4C2620054B059 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 299 | E0707B4620D4C2620054B059 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 300 | E0707B4820D4C2620054B059 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 301 | E0707B5E20D4C5D90054B059 /* RandomImage.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RandomImage.app; sourceTree = BUILT_PRODUCTS_DIR; }; 302 | E0707B6020D4C5D90054B059 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 303 | E0707B6220D4C5D90054B059 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 304 | E0707B6420D4C5DA0054B059 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 305 | E0707B6720D4C5DA0054B059 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 306 | E0707B6920D4C5DA0054B059 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 307 | E0707B6A20D4C5DA0054B059 /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; }; 308 | E0707B7120D55E370054B059 /* ImageController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageController.swift; sourceTree = ""; }; 309 | E0707B7420D55E4E0054B059 /* ImageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageModel.swift; sourceTree = ""; }; 310 | E0707B7720D579BE0054B059 /* Platform.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Platform.swift; sourceTree = ""; }; 311 | E0EA515620DD83BC00AEA939 /* Mvce.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Mvce.xcodeproj; path = ../../Mvce.xcodeproj; sourceTree = ""; }; 312 | /* End PBXFileReference section */ 313 | 314 | /* Begin PBXFrameworksBuildPhase section */ 315 | E0707B3620D4C2610054B059 /* Frameworks */ = { 316 | isa = PBXFrameworksBuildPhase; 317 | buildActionMask = 2147483647; 318 | files = ( 319 | E025F6962235FDA600FFECA9 /* ReactiveCocoa.framework in Frameworks */, 320 | E025F6972235FDA600FFECA9 /* ReactiveSwift.framework in Frameworks */, 321 | E025F6982235FDA600FFECA9 /* Result.framework in Frameworks */, 322 | E0EA516220DD83C600AEA939 /* Mvce.framework in Frameworks */, 323 | ); 324 | runOnlyForDeploymentPostprocessing = 0; 325 | }; 326 | E0707B5B20D4C5D90054B059 /* Frameworks */ = { 327 | isa = PBXFrameworksBuildPhase; 328 | buildActionMask = 2147483647; 329 | files = ( 330 | E025F6992235FDB500FFECA9 /* ReactiveCocoa.framework in Frameworks */, 331 | E025F69A2235FDB500FFECA9 /* ReactiveSwift.framework in Frameworks */, 332 | E025F69B2235FDB500FFECA9 /* Result.framework in Frameworks */, 333 | E0EA516320DD83CB00AEA939 /* Mvce.framework in Frameworks */, 334 | ); 335 | runOnlyForDeploymentPostprocessing = 0; 336 | }; 337 | /* End PBXFrameworksBuildPhase section */ 338 | 339 | /* Begin PBXGroup section */ 340 | E025F6312235FD4C00FFECA9 /* Products */ = { 341 | isa = PBXGroup; 342 | children = ( 343 | E025F63B2235FD4C00FFECA9 /* Result.framework */, 344 | E025F63D2235FD4C00FFECA9 /* Result-MacTests.xctest */, 345 | E025F63F2235FD4C00FFECA9 /* Result.framework */, 346 | E025F6412235FD4C00FFECA9 /* Result-iOSTests.xctest */, 347 | E025F6432235FD4C00FFECA9 /* Result.framework */, 348 | E025F6452235FD4C00FFECA9 /* Result-tvOSTests.xctest */, 349 | E025F6472235FD4C00FFECA9 /* Result.framework */, 350 | ); 351 | name = Products; 352 | sourceTree = ""; 353 | }; 354 | E025F6492235FD5600FFECA9 /* Products */ = { 355 | isa = PBXGroup; 356 | children = ( 357 | E025F6532235FD5600FFECA9 /* ReactiveSwift.framework */, 358 | E025F6552235FD5600FFECA9 /* ReactiveSwiftTests.xctest */, 359 | E025F6572235FD5600FFECA9 /* ReactiveSwift.framework */, 360 | E025F6592235FD5600FFECA9 /* ReactiveSwiftTests.xctest */, 361 | E025F65B2235FD5600FFECA9 /* ReactiveSwift.framework */, 362 | E025F65D2235FD5600FFECA9 /* ReactiveSwift.framework */, 363 | E025F65F2235FD5600FFECA9 /* ReactiveSwiftTests.xctest */, 364 | ); 365 | name = Products; 366 | sourceTree = ""; 367 | }; 368 | E025F6612235FD5E00FFECA9 /* Products */ = { 369 | isa = PBXGroup; 370 | children = ( 371 | E025F6712235FD5E00FFECA9 /* ReactiveCocoa.framework */, 372 | E025F6732235FD5E00FFECA9 /* ReactiveCocoaTests.xctest */, 373 | E025F6752235FD5E00FFECA9 /* ReactiveCocoa.framework */, 374 | E025F6772235FD5E00FFECA9 /* ReactiveCocoaTests.xctest */, 375 | E025F6792235FD5E00FFECA9 /* ReactiveCocoa.framework */, 376 | E025F67B2235FD5E00FFECA9 /* ReactiveCocoa.framework */, 377 | E025F67D2235FD5E00FFECA9 /* ReactiveCocoaTests.xctest */, 378 | E025F67F2235FD5E00FFECA9 /* ReactiveMapKit.framework */, 379 | E025F6812235FD5E00FFECA9 /* ReactiveMapKitTests.xctest */, 380 | E025F6832235FD5E00FFECA9 /* ReactiveMapKit.framework */, 381 | E025F6852235FD5E00FFECA9 /* ReactiveMapKitTests.xctest */, 382 | E025F6872235FD5E00FFECA9 /* ReactiveMapKit.framework */, 383 | E025F6892235FD5E00FFECA9 /* ReactiveMapKitTests.xctest */, 384 | ); 385 | name = Products; 386 | sourceTree = ""; 387 | }; 388 | E0707B3020D4C2610054B059 = { 389 | isa = PBXGroup; 390 | children = ( 391 | E025F6302235FD4C00FFECA9 /* Result.xcodeproj */, 392 | E025F6482235FD5600FFECA9 /* ReactiveSwift.xcodeproj */, 393 | E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */, 394 | E0EA515620DD83BC00AEA939 /* Mvce.xcodeproj */, 395 | E0707B7020D551320054B059 /* Shared */, 396 | E0707B3B20D4C2610054B059 /* iOS */, 397 | E0707B5F20D4C5D90054B059 /* macOS */, 398 | E0707B3A20D4C2610054B059 /* Products */, 399 | E0707B5720D4C2900054B059 /* Frameworks */, 400 | ); 401 | sourceTree = ""; 402 | }; 403 | E0707B3A20D4C2610054B059 /* Products */ = { 404 | isa = PBXGroup; 405 | children = ( 406 | E0707B3920D4C2610054B059 /* RandomImage.app */, 407 | E0707B5E20D4C5D90054B059 /* RandomImage.app */, 408 | ); 409 | name = Products; 410 | sourceTree = ""; 411 | }; 412 | E0707B3B20D4C2610054B059 /* iOS */ = { 413 | isa = PBXGroup; 414 | children = ( 415 | E0707B3C20D4C2610054B059 /* AppDelegate.swift */, 416 | E0707B3E20D4C2610054B059 /* ViewController.swift */, 417 | E0707B4020D4C2610054B059 /* Main.storyboard */, 418 | E0707B4320D4C2620054B059 /* Assets.xcassets */, 419 | E0707B4520D4C2620054B059 /* LaunchScreen.storyboard */, 420 | E0707B4820D4C2620054B059 /* Info.plist */, 421 | ); 422 | path = iOS; 423 | sourceTree = ""; 424 | }; 425 | E0707B5720D4C2900054B059 /* Frameworks */ = { 426 | isa = PBXGroup; 427 | children = ( 428 | ); 429 | name = Frameworks; 430 | sourceTree = ""; 431 | }; 432 | E0707B5F20D4C5D90054B059 /* macOS */ = { 433 | isa = PBXGroup; 434 | children = ( 435 | E0707B6020D4C5D90054B059 /* AppDelegate.swift */, 436 | E0707B6220D4C5D90054B059 /* ViewController.swift */, 437 | E0707B6420D4C5DA0054B059 /* Assets.xcassets */, 438 | E0707B6620D4C5DA0054B059 /* Main.storyboard */, 439 | E0707B6920D4C5DA0054B059 /* Info.plist */, 440 | E0707B6A20D4C5DA0054B059 /* macOS.entitlements */, 441 | ); 442 | path = macOS; 443 | sourceTree = ""; 444 | }; 445 | E0707B7020D551320054B059 /* Shared */ = { 446 | isa = PBXGroup; 447 | children = ( 448 | E0707B7720D579BE0054B059 /* Platform.swift */, 449 | E0707B7420D55E4E0054B059 /* ImageModel.swift */, 450 | E0707B7120D55E370054B059 /* ImageController.swift */, 451 | ); 452 | path = Shared; 453 | sourceTree = ""; 454 | }; 455 | E0EA515720DD83BC00AEA939 /* Products */ = { 456 | isa = PBXGroup; 457 | children = ( 458 | E0EA515D20DD83BC00AEA939 /* Mvce.framework */, 459 | E0EA515F20DD83BC00AEA939 /* MvceTests.xctest */, 460 | E0EA516120DD83BC00AEA939 /* Mvce.framework */, 461 | ); 462 | name = Products; 463 | sourceTree = ""; 464 | }; 465 | /* End PBXGroup section */ 466 | 467 | /* Begin PBXNativeTarget section */ 468 | E0707B3820D4C2610054B059 /* RandomImage */ = { 469 | isa = PBXNativeTarget; 470 | buildConfigurationList = E0707B4B20D4C2620054B059 /* Build configuration list for PBXNativeTarget "RandomImage" */; 471 | buildPhases = ( 472 | E0707B3520D4C2610054B059 /* Sources */, 473 | E0707B3620D4C2610054B059 /* Frameworks */, 474 | E0707B3720D4C2610054B059 /* Resources */, 475 | ); 476 | buildRules = ( 477 | ); 478 | dependencies = ( 479 | E025F68B2235FD6B00FFECA9 /* PBXTargetDependency */, 480 | E025F68D2235FD6B00FFECA9 /* PBXTargetDependency */, 481 | E025F68F2235FD6B00FFECA9 /* PBXTargetDependency */, 482 | ); 483 | name = RandomImage; 484 | productName = "Hi, MiniMVC"; 485 | productReference = E0707B3920D4C2610054B059 /* RandomImage.app */; 486 | productType = "com.apple.product-type.application"; 487 | }; 488 | E0707B5D20D4C5D90054B059 /* RandomImage-macOS */ = { 489 | isa = PBXNativeTarget; 490 | buildConfigurationList = E0707B6F20D4C5DA0054B059 /* Build configuration list for PBXNativeTarget "RandomImage-macOS" */; 491 | buildPhases = ( 492 | E0707B5A20D4C5D90054B059 /* Sources */, 493 | E0707B5B20D4C5D90054B059 /* Frameworks */, 494 | E0707B5C20D4C5D90054B059 /* Resources */, 495 | ); 496 | buildRules = ( 497 | ); 498 | dependencies = ( 499 | E025F6912235FD7500FFECA9 /* PBXTargetDependency */, 500 | E025F6932235FD7500FFECA9 /* PBXTargetDependency */, 501 | E025F6952235FD7500FFECA9 /* PBXTargetDependency */, 502 | ); 503 | name = "RandomImage-macOS"; 504 | productName = macOS; 505 | productReference = E0707B5E20D4C5D90054B059 /* RandomImage.app */; 506 | productType = "com.apple.product-type.application"; 507 | }; 508 | /* End PBXNativeTarget section */ 509 | 510 | /* Begin PBXProject section */ 511 | E0707B3120D4C2610054B059 /* Project object */ = { 512 | isa = PBXProject; 513 | attributes = { 514 | LastSwiftUpdateCheck = 1000; 515 | LastUpgradeCheck = 1000; 516 | ORGANIZATIONNAME = realazy; 517 | TargetAttributes = { 518 | E0707B3820D4C2610054B059 = { 519 | CreatedOnToolsVersion = 10.0; 520 | }; 521 | E0707B5D20D4C5D90054B059 = { 522 | CreatedOnToolsVersion = 10.0; 523 | }; 524 | }; 525 | }; 526 | buildConfigurationList = E0707B3420D4C2610054B059 /* Build configuration list for PBXProject "RandomImage" */; 527 | compatibilityVersion = "Xcode 9.3"; 528 | developmentRegion = en; 529 | hasScannedForEncodings = 0; 530 | knownRegions = ( 531 | en, 532 | Base, 533 | ); 534 | mainGroup = E0707B3020D4C2610054B059; 535 | productRefGroup = E0707B3A20D4C2610054B059 /* Products */; 536 | projectDirPath = ""; 537 | projectReferences = ( 538 | { 539 | ProductGroup = E0EA515720DD83BC00AEA939 /* Products */; 540 | ProjectRef = E0EA515620DD83BC00AEA939 /* Mvce.xcodeproj */; 541 | }, 542 | { 543 | ProductGroup = E025F6612235FD5E00FFECA9 /* Products */; 544 | ProjectRef = E025F6602235FD5E00FFECA9 /* ReactiveCocoa.xcodeproj */; 545 | }, 546 | { 547 | ProductGroup = E025F6492235FD5600FFECA9 /* Products */; 548 | ProjectRef = E025F6482235FD5600FFECA9 /* ReactiveSwift.xcodeproj */; 549 | }, 550 | { 551 | ProductGroup = E025F6312235FD4C00FFECA9 /* Products */; 552 | ProjectRef = E025F6302235FD4C00FFECA9 /* Result.xcodeproj */; 553 | }, 554 | ); 555 | projectRoot = ""; 556 | targets = ( 557 | E0707B3820D4C2610054B059 /* RandomImage */, 558 | E0707B5D20D4C5D90054B059 /* RandomImage-macOS */, 559 | ); 560 | }; 561 | /* End PBXProject section */ 562 | 563 | /* Begin PBXReferenceProxy section */ 564 | E025F63B2235FD4C00FFECA9 /* Result.framework */ = { 565 | isa = PBXReferenceProxy; 566 | fileType = wrapper.framework; 567 | path = Result.framework; 568 | remoteRef = E025F63A2235FD4C00FFECA9 /* PBXContainerItemProxy */; 569 | sourceTree = BUILT_PRODUCTS_DIR; 570 | }; 571 | E025F63D2235FD4C00FFECA9 /* Result-MacTests.xctest */ = { 572 | isa = PBXReferenceProxy; 573 | fileType = wrapper.cfbundle; 574 | path = "Result-MacTests.xctest"; 575 | remoteRef = E025F63C2235FD4C00FFECA9 /* PBXContainerItemProxy */; 576 | sourceTree = BUILT_PRODUCTS_DIR; 577 | }; 578 | E025F63F2235FD4C00FFECA9 /* Result.framework */ = { 579 | isa = PBXReferenceProxy; 580 | fileType = wrapper.framework; 581 | path = Result.framework; 582 | remoteRef = E025F63E2235FD4C00FFECA9 /* PBXContainerItemProxy */; 583 | sourceTree = BUILT_PRODUCTS_DIR; 584 | }; 585 | E025F6412235FD4C00FFECA9 /* Result-iOSTests.xctest */ = { 586 | isa = PBXReferenceProxy; 587 | fileType = wrapper.cfbundle; 588 | path = "Result-iOSTests.xctest"; 589 | remoteRef = E025F6402235FD4C00FFECA9 /* PBXContainerItemProxy */; 590 | sourceTree = BUILT_PRODUCTS_DIR; 591 | }; 592 | E025F6432235FD4C00FFECA9 /* Result.framework */ = { 593 | isa = PBXReferenceProxy; 594 | fileType = wrapper.framework; 595 | path = Result.framework; 596 | remoteRef = E025F6422235FD4C00FFECA9 /* PBXContainerItemProxy */; 597 | sourceTree = BUILT_PRODUCTS_DIR; 598 | }; 599 | E025F6452235FD4C00FFECA9 /* Result-tvOSTests.xctest */ = { 600 | isa = PBXReferenceProxy; 601 | fileType = wrapper.cfbundle; 602 | path = "Result-tvOSTests.xctest"; 603 | remoteRef = E025F6442235FD4C00FFECA9 /* PBXContainerItemProxy */; 604 | sourceTree = BUILT_PRODUCTS_DIR; 605 | }; 606 | E025F6472235FD4C00FFECA9 /* Result.framework */ = { 607 | isa = PBXReferenceProxy; 608 | fileType = wrapper.framework; 609 | path = Result.framework; 610 | remoteRef = E025F6462235FD4C00FFECA9 /* PBXContainerItemProxy */; 611 | sourceTree = BUILT_PRODUCTS_DIR; 612 | }; 613 | E025F6532235FD5600FFECA9 /* ReactiveSwift.framework */ = { 614 | isa = PBXReferenceProxy; 615 | fileType = wrapper.framework; 616 | path = ReactiveSwift.framework; 617 | remoteRef = E025F6522235FD5600FFECA9 /* PBXContainerItemProxy */; 618 | sourceTree = BUILT_PRODUCTS_DIR; 619 | }; 620 | E025F6552235FD5600FFECA9 /* ReactiveSwiftTests.xctest */ = { 621 | isa = PBXReferenceProxy; 622 | fileType = wrapper.cfbundle; 623 | path = ReactiveSwiftTests.xctest; 624 | remoteRef = E025F6542235FD5600FFECA9 /* PBXContainerItemProxy */; 625 | sourceTree = BUILT_PRODUCTS_DIR; 626 | }; 627 | E025F6572235FD5600FFECA9 /* ReactiveSwift.framework */ = { 628 | isa = PBXReferenceProxy; 629 | fileType = wrapper.framework; 630 | path = ReactiveSwift.framework; 631 | remoteRef = E025F6562235FD5600FFECA9 /* PBXContainerItemProxy */; 632 | sourceTree = BUILT_PRODUCTS_DIR; 633 | }; 634 | E025F6592235FD5600FFECA9 /* ReactiveSwiftTests.xctest */ = { 635 | isa = PBXReferenceProxy; 636 | fileType = wrapper.cfbundle; 637 | path = ReactiveSwiftTests.xctest; 638 | remoteRef = E025F6582235FD5600FFECA9 /* PBXContainerItemProxy */; 639 | sourceTree = BUILT_PRODUCTS_DIR; 640 | }; 641 | E025F65B2235FD5600FFECA9 /* ReactiveSwift.framework */ = { 642 | isa = PBXReferenceProxy; 643 | fileType = wrapper.framework; 644 | path = ReactiveSwift.framework; 645 | remoteRef = E025F65A2235FD5600FFECA9 /* PBXContainerItemProxy */; 646 | sourceTree = BUILT_PRODUCTS_DIR; 647 | }; 648 | E025F65D2235FD5600FFECA9 /* ReactiveSwift.framework */ = { 649 | isa = PBXReferenceProxy; 650 | fileType = wrapper.framework; 651 | path = ReactiveSwift.framework; 652 | remoteRef = E025F65C2235FD5600FFECA9 /* PBXContainerItemProxy */; 653 | sourceTree = BUILT_PRODUCTS_DIR; 654 | }; 655 | E025F65F2235FD5600FFECA9 /* ReactiveSwiftTests.xctest */ = { 656 | isa = PBXReferenceProxy; 657 | fileType = wrapper.cfbundle; 658 | path = ReactiveSwiftTests.xctest; 659 | remoteRef = E025F65E2235FD5600FFECA9 /* PBXContainerItemProxy */; 660 | sourceTree = BUILT_PRODUCTS_DIR; 661 | }; 662 | E025F6712235FD5E00FFECA9 /* ReactiveCocoa.framework */ = { 663 | isa = PBXReferenceProxy; 664 | fileType = wrapper.framework; 665 | path = ReactiveCocoa.framework; 666 | remoteRef = E025F6702235FD5E00FFECA9 /* PBXContainerItemProxy */; 667 | sourceTree = BUILT_PRODUCTS_DIR; 668 | }; 669 | E025F6732235FD5E00FFECA9 /* ReactiveCocoaTests.xctest */ = { 670 | isa = PBXReferenceProxy; 671 | fileType = wrapper.cfbundle; 672 | path = ReactiveCocoaTests.xctest; 673 | remoteRef = E025F6722235FD5E00FFECA9 /* PBXContainerItemProxy */; 674 | sourceTree = BUILT_PRODUCTS_DIR; 675 | }; 676 | E025F6752235FD5E00FFECA9 /* ReactiveCocoa.framework */ = { 677 | isa = PBXReferenceProxy; 678 | fileType = wrapper.framework; 679 | path = ReactiveCocoa.framework; 680 | remoteRef = E025F6742235FD5E00FFECA9 /* PBXContainerItemProxy */; 681 | sourceTree = BUILT_PRODUCTS_DIR; 682 | }; 683 | E025F6772235FD5E00FFECA9 /* ReactiveCocoaTests.xctest */ = { 684 | isa = PBXReferenceProxy; 685 | fileType = wrapper.cfbundle; 686 | path = ReactiveCocoaTests.xctest; 687 | remoteRef = E025F6762235FD5E00FFECA9 /* PBXContainerItemProxy */; 688 | sourceTree = BUILT_PRODUCTS_DIR; 689 | }; 690 | E025F6792235FD5E00FFECA9 /* ReactiveCocoa.framework */ = { 691 | isa = PBXReferenceProxy; 692 | fileType = wrapper.framework; 693 | path = ReactiveCocoa.framework; 694 | remoteRef = E025F6782235FD5E00FFECA9 /* PBXContainerItemProxy */; 695 | sourceTree = BUILT_PRODUCTS_DIR; 696 | }; 697 | E025F67B2235FD5E00FFECA9 /* ReactiveCocoa.framework */ = { 698 | isa = PBXReferenceProxy; 699 | fileType = wrapper.framework; 700 | path = ReactiveCocoa.framework; 701 | remoteRef = E025F67A2235FD5E00FFECA9 /* PBXContainerItemProxy */; 702 | sourceTree = BUILT_PRODUCTS_DIR; 703 | }; 704 | E025F67D2235FD5E00FFECA9 /* ReactiveCocoaTests.xctest */ = { 705 | isa = PBXReferenceProxy; 706 | fileType = wrapper.cfbundle; 707 | path = ReactiveCocoaTests.xctest; 708 | remoteRef = E025F67C2235FD5E00FFECA9 /* PBXContainerItemProxy */; 709 | sourceTree = BUILT_PRODUCTS_DIR; 710 | }; 711 | E025F67F2235FD5E00FFECA9 /* ReactiveMapKit.framework */ = { 712 | isa = PBXReferenceProxy; 713 | fileType = wrapper.framework; 714 | path = ReactiveMapKit.framework; 715 | remoteRef = E025F67E2235FD5E00FFECA9 /* PBXContainerItemProxy */; 716 | sourceTree = BUILT_PRODUCTS_DIR; 717 | }; 718 | E025F6812235FD5E00FFECA9 /* ReactiveMapKitTests.xctest */ = { 719 | isa = PBXReferenceProxy; 720 | fileType = wrapper.cfbundle; 721 | path = ReactiveMapKitTests.xctest; 722 | remoteRef = E025F6802235FD5E00FFECA9 /* PBXContainerItemProxy */; 723 | sourceTree = BUILT_PRODUCTS_DIR; 724 | }; 725 | E025F6832235FD5E00FFECA9 /* ReactiveMapKit.framework */ = { 726 | isa = PBXReferenceProxy; 727 | fileType = wrapper.framework; 728 | path = ReactiveMapKit.framework; 729 | remoteRef = E025F6822235FD5E00FFECA9 /* PBXContainerItemProxy */; 730 | sourceTree = BUILT_PRODUCTS_DIR; 731 | }; 732 | E025F6852235FD5E00FFECA9 /* ReactiveMapKitTests.xctest */ = { 733 | isa = PBXReferenceProxy; 734 | fileType = wrapper.cfbundle; 735 | path = ReactiveMapKitTests.xctest; 736 | remoteRef = E025F6842235FD5E00FFECA9 /* PBXContainerItemProxy */; 737 | sourceTree = BUILT_PRODUCTS_DIR; 738 | }; 739 | E025F6872235FD5E00FFECA9 /* ReactiveMapKit.framework */ = { 740 | isa = PBXReferenceProxy; 741 | fileType = wrapper.framework; 742 | path = ReactiveMapKit.framework; 743 | remoteRef = E025F6862235FD5E00FFECA9 /* PBXContainerItemProxy */; 744 | sourceTree = BUILT_PRODUCTS_DIR; 745 | }; 746 | E025F6892235FD5E00FFECA9 /* ReactiveMapKitTests.xctest */ = { 747 | isa = PBXReferenceProxy; 748 | fileType = wrapper.cfbundle; 749 | path = ReactiveMapKitTests.xctest; 750 | remoteRef = E025F6882235FD5E00FFECA9 /* PBXContainerItemProxy */; 751 | sourceTree = BUILT_PRODUCTS_DIR; 752 | }; 753 | E0EA515D20DD83BC00AEA939 /* Mvce.framework */ = { 754 | isa = PBXReferenceProxy; 755 | fileType = wrapper.framework; 756 | path = Mvce.framework; 757 | remoteRef = E0EA515C20DD83BC00AEA939 /* PBXContainerItemProxy */; 758 | sourceTree = BUILT_PRODUCTS_DIR; 759 | }; 760 | E0EA515F20DD83BC00AEA939 /* MvceTests.xctest */ = { 761 | isa = PBXReferenceProxy; 762 | fileType = wrapper.cfbundle; 763 | path = MvceTests.xctest; 764 | remoteRef = E0EA515E20DD83BC00AEA939 /* PBXContainerItemProxy */; 765 | sourceTree = BUILT_PRODUCTS_DIR; 766 | }; 767 | E0EA516120DD83BC00AEA939 /* Mvce.framework */ = { 768 | isa = PBXReferenceProxy; 769 | fileType = wrapper.framework; 770 | path = Mvce.framework; 771 | remoteRef = E0EA516020DD83BC00AEA939 /* PBXContainerItemProxy */; 772 | sourceTree = BUILT_PRODUCTS_DIR; 773 | }; 774 | /* End PBXReferenceProxy section */ 775 | 776 | /* Begin PBXResourcesBuildPhase section */ 777 | E0707B3720D4C2610054B059 /* Resources */ = { 778 | isa = PBXResourcesBuildPhase; 779 | buildActionMask = 2147483647; 780 | files = ( 781 | E0707B4720D4C2620054B059 /* LaunchScreen.storyboard in Resources */, 782 | E0707B4420D4C2620054B059 /* Assets.xcassets in Resources */, 783 | E0707B4220D4C2610054B059 /* Main.storyboard in Resources */, 784 | ); 785 | runOnlyForDeploymentPostprocessing = 0; 786 | }; 787 | E0707B5C20D4C5D90054B059 /* Resources */ = { 788 | isa = PBXResourcesBuildPhase; 789 | buildActionMask = 2147483647; 790 | files = ( 791 | E0707B6520D4C5DA0054B059 /* Assets.xcassets in Resources */, 792 | E0707B6820D4C5DA0054B059 /* Main.storyboard in Resources */, 793 | ); 794 | runOnlyForDeploymentPostprocessing = 0; 795 | }; 796 | /* End PBXResourcesBuildPhase section */ 797 | 798 | /* Begin PBXSourcesBuildPhase section */ 799 | E0707B3520D4C2610054B059 /* Sources */ = { 800 | isa = PBXSourcesBuildPhase; 801 | buildActionMask = 2147483647; 802 | files = ( 803 | E0707B7820D579BE0054B059 /* Platform.swift in Sources */, 804 | E0707B3F20D4C2610054B059 /* ViewController.swift in Sources */, 805 | E0707B7220D55E370054B059 /* ImageController.swift in Sources */, 806 | E0707B7520D55E4E0054B059 /* ImageModel.swift in Sources */, 807 | E0707B3D20D4C2610054B059 /* AppDelegate.swift in Sources */, 808 | ); 809 | runOnlyForDeploymentPostprocessing = 0; 810 | }; 811 | E0707B5A20D4C5D90054B059 /* Sources */ = { 812 | isa = PBXSourcesBuildPhase; 813 | buildActionMask = 2147483647; 814 | files = ( 815 | E0707B7920D579BE0054B059 /* Platform.swift in Sources */, 816 | E0707B6320D4C5D90054B059 /* ViewController.swift in Sources */, 817 | E0707B7320D55E370054B059 /* ImageController.swift in Sources */, 818 | E0707B7620D55E4E0054B059 /* ImageModel.swift in Sources */, 819 | E0707B6120D4C5D90054B059 /* AppDelegate.swift in Sources */, 820 | ); 821 | runOnlyForDeploymentPostprocessing = 0; 822 | }; 823 | /* End PBXSourcesBuildPhase section */ 824 | 825 | /* Begin PBXTargetDependency section */ 826 | E025F68B2235FD6B00FFECA9 /* PBXTargetDependency */ = { 827 | isa = PBXTargetDependency; 828 | name = "Result-iOS"; 829 | targetProxy = E025F68A2235FD6B00FFECA9 /* PBXContainerItemProxy */; 830 | }; 831 | E025F68D2235FD6B00FFECA9 /* PBXTargetDependency */ = { 832 | isa = PBXTargetDependency; 833 | name = "ReactiveSwift-iOS"; 834 | targetProxy = E025F68C2235FD6B00FFECA9 /* PBXContainerItemProxy */; 835 | }; 836 | E025F68F2235FD6B00FFECA9 /* PBXTargetDependency */ = { 837 | isa = PBXTargetDependency; 838 | name = "ReactiveCocoa-iOS"; 839 | targetProxy = E025F68E2235FD6B00FFECA9 /* PBXContainerItemProxy */; 840 | }; 841 | E025F6912235FD7500FFECA9 /* PBXTargetDependency */ = { 842 | isa = PBXTargetDependency; 843 | name = "Result-Mac"; 844 | targetProxy = E025F6902235FD7500FFECA9 /* PBXContainerItemProxy */; 845 | }; 846 | E025F6932235FD7500FFECA9 /* PBXTargetDependency */ = { 847 | isa = PBXTargetDependency; 848 | name = "ReactiveSwift-macOS"; 849 | targetProxy = E025F6922235FD7500FFECA9 /* PBXContainerItemProxy */; 850 | }; 851 | E025F6952235FD7500FFECA9 /* PBXTargetDependency */ = { 852 | isa = PBXTargetDependency; 853 | name = "ReactiveCocoa-macOS"; 854 | targetProxy = E025F6942235FD7500FFECA9 /* PBXContainerItemProxy */; 855 | }; 856 | /* End PBXTargetDependency section */ 857 | 858 | /* Begin PBXVariantGroup section */ 859 | E0707B4020D4C2610054B059 /* Main.storyboard */ = { 860 | isa = PBXVariantGroup; 861 | children = ( 862 | E0707B4120D4C2610054B059 /* Base */, 863 | ); 864 | name = Main.storyboard; 865 | sourceTree = ""; 866 | }; 867 | E0707B4520D4C2620054B059 /* LaunchScreen.storyboard */ = { 868 | isa = PBXVariantGroup; 869 | children = ( 870 | E0707B4620D4C2620054B059 /* Base */, 871 | ); 872 | name = LaunchScreen.storyboard; 873 | sourceTree = ""; 874 | }; 875 | E0707B6620D4C5DA0054B059 /* Main.storyboard */ = { 876 | isa = PBXVariantGroup; 877 | children = ( 878 | E0707B6720D4C5DA0054B059 /* Base */, 879 | ); 880 | name = Main.storyboard; 881 | sourceTree = ""; 882 | }; 883 | /* End PBXVariantGroup section */ 884 | 885 | /* Begin XCBuildConfiguration section */ 886 | E0707B4920D4C2620054B059 /* Debug */ = { 887 | isa = XCBuildConfiguration; 888 | buildSettings = { 889 | ALWAYS_SEARCH_USER_PATHS = NO; 890 | CLANG_ANALYZER_NONNULL = YES; 891 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 892 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 893 | CLANG_CXX_LIBRARY = "libc++"; 894 | CLANG_ENABLE_MODULES = YES; 895 | CLANG_ENABLE_OBJC_ARC = YES; 896 | CLANG_ENABLE_OBJC_WEAK = YES; 897 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 898 | CLANG_WARN_BOOL_CONVERSION = YES; 899 | CLANG_WARN_COMMA = YES; 900 | CLANG_WARN_CONSTANT_CONVERSION = YES; 901 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 902 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 903 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 904 | CLANG_WARN_EMPTY_BODY = YES; 905 | CLANG_WARN_ENUM_CONVERSION = YES; 906 | CLANG_WARN_INFINITE_RECURSION = YES; 907 | CLANG_WARN_INT_CONVERSION = YES; 908 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 909 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 910 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 911 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 912 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 913 | CLANG_WARN_STRICT_PROTOTYPES = YES; 914 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 915 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 916 | CLANG_WARN_UNREACHABLE_CODE = YES; 917 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 918 | CODE_SIGN_IDENTITY = "iPhone Developer"; 919 | COPY_PHASE_STRIP = NO; 920 | DEBUG_INFORMATION_FORMAT = dwarf; 921 | ENABLE_STRICT_OBJC_MSGSEND = YES; 922 | ENABLE_TESTABILITY = YES; 923 | GCC_C_LANGUAGE_STANDARD = gnu11; 924 | GCC_DYNAMIC_NO_PIC = NO; 925 | GCC_NO_COMMON_BLOCKS = YES; 926 | GCC_OPTIMIZATION_LEVEL = 0; 927 | GCC_PREPROCESSOR_DEFINITIONS = ( 928 | "DEBUG=1", 929 | "$(inherited)", 930 | ); 931 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 932 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 933 | GCC_WARN_UNDECLARED_SELECTOR = YES; 934 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 935 | GCC_WARN_UNUSED_FUNCTION = YES; 936 | GCC_WARN_UNUSED_VARIABLE = YES; 937 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 938 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 939 | ONLY_ACTIVE_ARCH = YES; 940 | SDKROOT = iphoneos; 941 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 942 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 943 | }; 944 | name = Debug; 945 | }; 946 | E0707B4A20D4C2620054B059 /* Release */ = { 947 | isa = XCBuildConfiguration; 948 | buildSettings = { 949 | ALWAYS_SEARCH_USER_PATHS = NO; 950 | CLANG_ANALYZER_NONNULL = YES; 951 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 952 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 953 | CLANG_CXX_LIBRARY = "libc++"; 954 | CLANG_ENABLE_MODULES = YES; 955 | CLANG_ENABLE_OBJC_ARC = YES; 956 | CLANG_ENABLE_OBJC_WEAK = YES; 957 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 958 | CLANG_WARN_BOOL_CONVERSION = YES; 959 | CLANG_WARN_COMMA = YES; 960 | CLANG_WARN_CONSTANT_CONVERSION = YES; 961 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 962 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 963 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 964 | CLANG_WARN_EMPTY_BODY = YES; 965 | CLANG_WARN_ENUM_CONVERSION = YES; 966 | CLANG_WARN_INFINITE_RECURSION = YES; 967 | CLANG_WARN_INT_CONVERSION = YES; 968 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 969 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 970 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 971 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 972 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 973 | CLANG_WARN_STRICT_PROTOTYPES = YES; 974 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 975 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 976 | CLANG_WARN_UNREACHABLE_CODE = YES; 977 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 978 | CODE_SIGN_IDENTITY = "iPhone Developer"; 979 | COPY_PHASE_STRIP = NO; 980 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 981 | ENABLE_NS_ASSERTIONS = NO; 982 | ENABLE_STRICT_OBJC_MSGSEND = YES; 983 | GCC_C_LANGUAGE_STANDARD = gnu11; 984 | GCC_NO_COMMON_BLOCKS = YES; 985 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 986 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 987 | GCC_WARN_UNDECLARED_SELECTOR = YES; 988 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 989 | GCC_WARN_UNUSED_FUNCTION = YES; 990 | GCC_WARN_UNUSED_VARIABLE = YES; 991 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 992 | MTL_ENABLE_DEBUG_INFO = NO; 993 | SDKROOT = iphoneos; 994 | SWIFT_COMPILATION_MODE = wholemodule; 995 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 996 | VALIDATE_PRODUCT = YES; 997 | }; 998 | name = Release; 999 | }; 1000 | E0707B4C20D4C2620054B059 /* Debug */ = { 1001 | isa = XCBuildConfiguration; 1002 | buildSettings = { 1003 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 1004 | CODE_SIGN_STYLE = Automatic; 1005 | INFOPLIST_FILE = iOS/Info.plist; 1006 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 1007 | LD_RUNPATH_SEARCH_PATHS = ( 1008 | "$(inherited)", 1009 | "@executable_path/Frameworks", 1010 | ); 1011 | PRODUCT_BUNDLE_IDENTIFIER = com.realazy.randomImage; 1012 | PRODUCT_NAME = "$(TARGET_NAME)"; 1013 | SWIFT_VERSION = 4.2; 1014 | TARGETED_DEVICE_FAMILY = "1,2"; 1015 | }; 1016 | name = Debug; 1017 | }; 1018 | E0707B4D20D4C2620054B059 /* Release */ = { 1019 | isa = XCBuildConfiguration; 1020 | buildSettings = { 1021 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 1022 | CODE_SIGN_STYLE = Automatic; 1023 | INFOPLIST_FILE = iOS/Info.plist; 1024 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 1025 | LD_RUNPATH_SEARCH_PATHS = ( 1026 | "$(inherited)", 1027 | "@executable_path/Frameworks", 1028 | ); 1029 | PRODUCT_BUNDLE_IDENTIFIER = com.realazy.randomImage; 1030 | PRODUCT_NAME = "$(TARGET_NAME)"; 1031 | SWIFT_VERSION = 4.2; 1032 | TARGETED_DEVICE_FAMILY = "1,2"; 1033 | }; 1034 | name = Release; 1035 | }; 1036 | E0707B6B20D4C5DA0054B059 /* Debug */ = { 1037 | isa = XCBuildConfiguration; 1038 | buildSettings = { 1039 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 1040 | CODE_SIGN_IDENTITY = "-"; 1041 | CODE_SIGN_STYLE = Automatic; 1042 | COMBINE_HIDPI_IMAGES = YES; 1043 | INFOPLIST_FILE = macOS/Info.plist; 1044 | LD_RUNPATH_SEARCH_PATHS = ( 1045 | "$(inherited)", 1046 | "@executable_path/../Frameworks", 1047 | ); 1048 | MACOSX_DEPLOYMENT_TARGET = 10.13; 1049 | PRODUCT_BUNDLE_IDENTIFIER = com.realazy.randomImage; 1050 | PRODUCT_NAME = RandomImage; 1051 | SDKROOT = macosx; 1052 | SWIFT_VERSION = 4.2; 1053 | }; 1054 | name = Debug; 1055 | }; 1056 | E0707B6C20D4C5DA0054B059 /* Release */ = { 1057 | isa = XCBuildConfiguration; 1058 | buildSettings = { 1059 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 1060 | CODE_SIGN_IDENTITY = "-"; 1061 | CODE_SIGN_STYLE = Automatic; 1062 | COMBINE_HIDPI_IMAGES = YES; 1063 | INFOPLIST_FILE = macOS/Info.plist; 1064 | LD_RUNPATH_SEARCH_PATHS = ( 1065 | "$(inherited)", 1066 | "@executable_path/../Frameworks", 1067 | ); 1068 | MACOSX_DEPLOYMENT_TARGET = 10.13; 1069 | PRODUCT_BUNDLE_IDENTIFIER = com.realazy.randomImage; 1070 | PRODUCT_NAME = RandomImage; 1071 | SDKROOT = macosx; 1072 | SWIFT_VERSION = 4.2; 1073 | }; 1074 | name = Release; 1075 | }; 1076 | /* End XCBuildConfiguration section */ 1077 | 1078 | /* Begin XCConfigurationList section */ 1079 | E0707B3420D4C2610054B059 /* Build configuration list for PBXProject "RandomImage" */ = { 1080 | isa = XCConfigurationList; 1081 | buildConfigurations = ( 1082 | E0707B4920D4C2620054B059 /* Debug */, 1083 | E0707B4A20D4C2620054B059 /* Release */, 1084 | ); 1085 | defaultConfigurationIsVisible = 0; 1086 | defaultConfigurationName = Release; 1087 | }; 1088 | E0707B4B20D4C2620054B059 /* Build configuration list for PBXNativeTarget "RandomImage" */ = { 1089 | isa = XCConfigurationList; 1090 | buildConfigurations = ( 1091 | E0707B4C20D4C2620054B059 /* Debug */, 1092 | E0707B4D20D4C2620054B059 /* Release */, 1093 | ); 1094 | defaultConfigurationIsVisible = 0; 1095 | defaultConfigurationName = Release; 1096 | }; 1097 | E0707B6F20D4C5DA0054B059 /* Build configuration list for PBXNativeTarget "RandomImage-macOS" */ = { 1098 | isa = XCConfigurationList; 1099 | buildConfigurations = ( 1100 | E0707B6B20D4C5DA0054B059 /* Debug */, 1101 | E0707B6C20D4C5DA0054B059 /* Release */, 1102 | ); 1103 | defaultConfigurationIsVisible = 0; 1104 | defaultConfigurationName = Release; 1105 | }; 1106 | /* End XCConfigurationList section */ 1107 | }; 1108 | rootObject = E0707B3120D4C2610054B059 /* Project object */; 1109 | } 1110 | -------------------------------------------------------------------------------- /Example/RandomImage/RandomImage.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/RandomImage/RandomImage.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/RandomImage/Shared/ImageController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageController.swift 3 | // RandomImage 4 | // 5 | // Created by CHEN Xian-an on 2018/6/16. 6 | // Copyright © 2018 realazy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Mvce 11 | 12 | enum ImageEvent { 13 | case handleDownload 14 | case requestImage 15 | case cancelRequest 16 | } 17 | 18 | class ImageController { 19 | private var downloadTask: URLSessionDataTask? 20 | } 21 | 22 | extension ImageController: Controller { 23 | typealias Model = ImageModel 24 | typealias Event = ImageEvent 25 | 26 | func update(model: Model, for event: Event, dispatcher: Dispatcher) { 27 | switch event { 28 | case .handleDownload: 29 | let next: Event 30 | if case .downloading(_) = model.imageState.value { next = .cancelRequest } 31 | else { next = .requestImage } 32 | dispatcher.send(event: next) 33 | case .requestImage: 34 | model.imageState.value = .downloading(.undetermined) 35 | downloadTask = downloadImage(model: model) 36 | downloadTask?.resume() 37 | case .cancelRequest: 38 | downloadTask?.cancel() 39 | model.imageState.value = .none 40 | } 41 | } 42 | } 43 | 44 | private extension ImageController { 45 | func downloadImage(model: ImageModel) -> URLSessionDataTask? { 46 | guard let url = URL(string: "https://picsum.photos/2000/1000/?random") else { // Download a large image to show progress 47 | let uinfo = [NSLocalizedDescriptionKey: NSLocalizedString("Image URL is wrong", comment: "")] 48 | let err = NSError(domain: "", code: 0, userInfo:uinfo) 49 | model.imageState.value = .error(err) 50 | return nil 51 | } 52 | let session = URLSession(configuration: .default, delegate: DataTaskDelegate(model), delegateQueue: nil) 53 | return session.dataTask(with: url) 54 | } 55 | } 56 | 57 | private class DataTaskDelegate: NSObject, URLSessionDataDelegate { 58 | let model: ImageModel 59 | var expectLen = NSURLSessionTransferSizeUnknown 60 | var imageData = Data() 61 | 62 | init(_ model: ImageModel) { 63 | self.model = model 64 | super.init() 65 | } 66 | 67 | func isDeterminated() -> Bool { 68 | return expectLen != NSURLSessionTransferSizeUnknown 69 | } 70 | 71 | func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { 72 | expectLen = response.expectedContentLength 73 | if isDeterminated() { imageData = Data(capacity: Int(expectLen)) } 74 | completionHandler(.allow) 75 | } 76 | 77 | func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { 78 | imageData.append(data) 79 | if isDeterminated() { 80 | let fraction = Float(imageData.count) / Float(expectLen) 81 | model.imageState.value = .downloading(.fractionCompleted(fraction)) 82 | } 83 | } 84 | 85 | func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { 86 | session.invalidateAndCancel() 87 | if let err = error { 88 | if (err as NSError).code == NSURLErrorCancelled { return model.imageState.value = .none } 89 | return model.imageState.value = .error(err) 90 | } 91 | guard let image = Image(data: imageData) else { 92 | let uinfo = [NSLocalizedDescriptionKey: NSLocalizedString("Can not convert data to Image", comment: "")] 93 | let err = NSError(domain: "", code: 0, userInfo:uinfo) 94 | return model.imageState.value = .error(err) 95 | } 96 | 97 | model.imageState.value = .finished(image) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Example/RandomImage/Shared/ImageModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageModel.swift 3 | // RandomImage 4 | // 5 | // Created by CHEN Xian-an on 2018/6/16. 6 | // Copyright © 2018 realazy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Mvce 11 | import Result 12 | import ReactiveSwift 13 | 14 | struct ImageModel { 15 | enum ImageState { 16 | enum Progress { 17 | case undetermined 18 | case fractionCompleted(Float) 19 | } 20 | 21 | case none 22 | case downloading(Progress) 23 | case finished(Image) 24 | case error(Error) 25 | } 26 | 27 | let imageState = MutableProperty(ImageState.none) 28 | let isImageHidden: Property 29 | let isIndicatorHidden: Property 30 | let isProgressHidden: Property 31 | let downloadProgress: Property 32 | let downloadedImage: Property 33 | let downloadError: Signal 34 | let downloadButtonTitle: Property 35 | 36 | init() { 37 | isImageHidden = imageState.map { 38 | switch $0 { 39 | case .finished(_): return false 40 | default: return true 41 | } 42 | } 43 | isIndicatorHidden = imageState.map { 44 | switch $0 { 45 | case .downloading(.undetermined): return false 46 | default: return true 47 | } 48 | } 49 | isProgressHidden = imageState.map { 50 | switch $0 { 51 | case .downloading(.fractionCompleted(_)): return false 52 | default: return true 53 | } 54 | } 55 | downloadProgress = imageState.map { 56 | switch $0 { 57 | case .downloading(.fractionCompleted(let p)): return p 58 | default: return 0 59 | } 60 | } 61 | downloadedImage = imageState.map { 62 | switch $0 { 63 | case .finished(let image): return .some(image) 64 | default: return nil 65 | } 66 | } 67 | downloadError = imageState.signal.map { 68 | switch $0 { 69 | case .error(let err): return .some(err) 70 | default: return nil 71 | } 72 | } 73 | downloadButtonTitle = imageState.map { 74 | switch $0 { 75 | case .downloading(_): 76 | return NSLocalizedString("Stop", comment: "") 77 | case .finished(_): 78 | return NSLocalizedString("Download Another Image", comment: "") 79 | default: 80 | return NSLocalizedString("Download Image", comment: "") 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Example/RandomImage/Shared/Platform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Platform.swift 3 | // RandomImage 4 | // 5 | // Created by CHEN Xian-an on 2018/6/17. 6 | // Copyright © 2018 realazy. All rights reserved. 7 | // 8 | 9 | #if canImport(UIkit) 10 | import UIKit.UIImage 11 | typealias Image = UIImage 12 | #elseif canImport(AppKit) 13 | import AppKit.NSImage 14 | typealias Image = NSImage 15 | #endif 16 | -------------------------------------------------------------------------------- /Example/RandomImage/iOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RandomImage 4 | // 5 | // Created by CHEN Xian-an on 2018/6/16. 6 | // Copyright © 2018 realazy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Example/RandomImage/iOS/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 | } -------------------------------------------------------------------------------- /Example/RandomImage/iOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/RandomImage/iOS/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 | -------------------------------------------------------------------------------- /Example/RandomImage/iOS/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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Example/RandomImage/iOS/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 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example/RandomImage/iOS/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // RandomImage 4 | // 5 | // Created by CHEN Xian-an on 2018/6/16. 6 | // Copyright © 2018 realazy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Mvce 11 | import ReactiveSwift 12 | import ReactiveCocoa 13 | 14 | class ViewController: UIViewController { 15 | @IBOutlet weak var indicatorView: UIActivityIndicatorView! 16 | @IBOutlet weak var progressBar: UIProgressView! 17 | @IBOutlet weak var imageView: UIImageView! 18 | @IBOutlet weak var downloadButton: UIButton! 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | Mvce.glue(model: ImageModel(), view: self, controller: ImageController()) 23 | } 24 | 25 | private func alert(error: Error) { 26 | let alert = UIAlertController(title: nil, message: error.localizedDescription, preferredStyle: .alert) 27 | alert.addAction(UIAlertAction(title: NSLocalizedString("Dismiss", comment: ""), style: .cancel, handler: nil)) 28 | present(alert, animated: true, completion: nil) 29 | } 30 | } 31 | 32 | extension ViewController: View { 33 | typealias Model = ImageModel 34 | typealias Event = ImageEvent 35 | 36 | func bind(model: Model, dispatcher: Dispatcher) -> View.BindingDisposer { 37 | // model binding 38 | imageView.reactive.isHidden <~ model.isImageHidden.skipRepeats() 39 | imageView.reactive.image <~ model.downloadedImage.skipRepeats() 40 | indicatorView.reactive.isHidden <~ model.isIndicatorHidden.skipRepeats() 41 | progressBar.reactive.isHidden <~ model.isProgressHidden.skipRepeats() 42 | progressBar.reactive.progress <~ model.downloadProgress.skipRepeats() 43 | downloadButton.reactive.title(for: .normal) <~ model.downloadButtonTitle.skipRepeats() 44 | let disposer = CompositeDisposable() 45 | disposer += model.downloadError.skipNil().observe(on: UIScheduler()).observeValues { [weak self] in self?.alert(error: $0) } 46 | 47 | // event binding 48 | disposer += downloadButton.reactive.controlEvents(.primaryActionTriggered).observeValues { _ in dispatcher.send(event: .handleDownload) } 49 | return disposer.dispose 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Example/RandomImage/macOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // macOS 4 | // 5 | // Created by CHEN Xian-an on 2018/6/16. 6 | // Copyright © 2018 realazy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | 15 | 16 | func applicationDidFinishLaunching(_ aNotification: Notification) { 17 | // Insert code here to initialize your application 18 | } 19 | 20 | func applicationWillTerminate(_ aNotification: Notification) { 21 | // Insert code here to tear down your application 22 | } 23 | 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /Example/RandomImage/macOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Example/RandomImage/macOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/RandomImage/macOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2018 realazy. All rights reserved. 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /Example/RandomImage/macOS/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // macOS 4 | // 5 | // Created by CHEN Xian-an on 2018/6/16. 6 | // Copyright © 2018 realazy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Mvce 11 | import Result 12 | import ReactiveSwift 13 | import ReactiveCocoa 14 | 15 | class ViewController: NSViewController { 16 | @IBOutlet weak var timerLabel: NSTextField! 17 | @IBOutlet weak var imageView: NSImageView! 18 | @IBOutlet weak var progresslessIndicator: NSProgressIndicator! 19 | @IBOutlet weak var progressIndicator: NSProgressIndicator! 20 | @IBOutlet weak var downloadButton: NSButton! 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | Mvce.glue(model: ImageModel(), view: self, controller: ImageController()) 25 | } 26 | 27 | private func alert(error: Error) { 28 | let alert = NSAlert(error: error) 29 | alert.addButton(withTitle: NSLocalizedString("Dismiss", comment: "")) 30 | alert.beginSheetModal(for: view.window!, completionHandler: nil) 31 | } 32 | } 33 | 34 | extension ViewController: View { 35 | typealias Model = ImageModel 36 | typealias Event = ImageEvent 37 | 38 | func bind(model: ImageModel, dispatcher: Dispatcher) -> View.BindingDisposer { 39 | // model binding 40 | imageView.reactive.isHidden <~ model.isImageHidden.skipRepeats() 41 | imageView.reactive.image <~ model.downloadedImage.skipRepeats() 42 | 43 | progresslessIndicator.reactive.isHidden <~ model.isIndicatorHidden.skipRepeats() 44 | progressIndicator.reactive.isHidden <~ model.isProgressHidden.skipRepeats() 45 | progressIndicator.reactive.makeBindingTarget { $0.doubleValue = Double($1) * 100 } <~ model.downloadProgress.skipRepeats() 46 | downloadButton.reactive.stringValue <~ model.downloadButtonTitle.skipRepeats() 47 | let disposer = CompositeDisposable() 48 | disposer += model.isIndicatorHidden.producer.observe(on: UIScheduler()).startWithValues { [weak self] in 49 | if $0 { self?.progresslessIndicator.stopAnimation(nil) } 50 | else { self?.progresslessIndicator.startAnimation(nil) } 51 | } 52 | disposer += model.downloadError.skipNil().observe(on: UIScheduler()).observeValues { [weak self] in self?.alert(error: $0) } 53 | 54 | // event binding 55 | // ReactiveCocoa doesn't exposed `proxy.ivoked` 56 | let action = Action<(), (), NoError> { _ in 57 | SignalProducer { observer, _ in 58 | dispatcher.send(event: .handleDownload) 59 | observer.sendCompleted() 60 | } 61 | } 62 | downloadButton.reactive.pressed = CocoaAction(action) 63 | return disposer.dispose 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Example/RandomImage/macOS/macOS.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 — Present CHEN Xian-an 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Mvce.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Mvce" 3 | s.version = "5.0.0" 4 | s.summary = "A minimal, simple, unobtrusive, and event driven MVC library to glue decoupled Model, View, and Controller for UIKit/AppKit." 5 | s.homepage = "https://github.com/cxa/Mvce" 6 | s.license = "MIT" 7 | s.author = { "CHEN Xian-an" => "xianan.chen@gmail.com" } 8 | s.ios.deployment_target = "8.0" 9 | s.osx.deployment_target = "10.9" 10 | s.source = { :git => "https://github.com/cxa/Mvce.git", :tag => "#{s.version}" } 11 | s.source_files = "Mvce/*.{h,swift}" 12 | s.swift_version = "4.2" 13 | end 14 | -------------------------------------------------------------------------------- /Mvce.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E0707B8820D57C360054B059 /* Mvce.h in Headers */ = {isa = PBXBuildFile; fileRef = E0E7B38920D3B13600E7F6C1 /* Mvce.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | E0707B8920D57C560054B059 /* Mvce.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0E7B3A020D3B15900E7F6C1 /* Mvce.swift */; }; 12 | E0E7B39020D3B13600E7F6C1 /* Mvce.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0E7B38620D3B13600E7F6C1 /* Mvce.framework */; }; 13 | E0E7B39520D3B13600E7F6C1 /* MiniMVCTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0E7B39420D3B13600E7F6C1 /* MiniMVCTests.swift */; }; 14 | E0E7B39720D3B13600E7F6C1 /* Mvce.h in Headers */ = {isa = PBXBuildFile; fileRef = E0E7B38920D3B13600E7F6C1 /* Mvce.h */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | E0E7B3A120D3B15900E7F6C1 /* Mvce.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0E7B3A020D3B15900E7F6C1 /* Mvce.swift */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXContainerItemProxy section */ 19 | E0E7B39120D3B13600E7F6C1 /* PBXContainerItemProxy */ = { 20 | isa = PBXContainerItemProxy; 21 | containerPortal = E0E7B37D20D3B13600E7F6C1 /* Project object */; 22 | proxyType = 1; 23 | remoteGlobalIDString = E0E7B38520D3B13600E7F6C1; 24 | remoteInfo = MiniMVC; 25 | }; 26 | /* End PBXContainerItemProxy section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | E0707B8020D57B8E0054B059 /* Mvce.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Mvce.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | E0E7B38620D3B13600E7F6C1 /* Mvce.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Mvce.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | E0E7B38920D3B13600E7F6C1 /* Mvce.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Mvce.h; sourceTree = ""; }; 32 | E0E7B38A20D3B13600E7F6C1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | E0E7B38F20D3B13600E7F6C1 /* MvceTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MvceTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | E0E7B39420D3B13600E7F6C1 /* MiniMVCTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniMVCTests.swift; sourceTree = ""; }; 35 | E0E7B39620D3B13600E7F6C1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | E0E7B3A020D3B15900E7F6C1 /* Mvce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mvce.swift; sourceTree = ""; }; 37 | E0EFDC0220D57EC300C0BDC8 /* Info-macOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-macOS.plist"; sourceTree = ""; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | E0707B7D20D57B8E0054B059 /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | buildActionMask = 2147483647; 44 | files = ( 45 | ); 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | E0E7B38320D3B13600E7F6C1 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | E0E7B38C20D3B13600E7F6C1 /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | E0E7B39020D3B13600E7F6C1 /* Mvce.framework in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | /* End PBXFrameworksBuildPhase section */ 64 | 65 | /* Begin PBXGroup section */ 66 | E0E7B37C20D3B13600E7F6C1 = { 67 | isa = PBXGroup; 68 | children = ( 69 | E0E7B38820D3B13600E7F6C1 /* Mvce */, 70 | E0E7B39320D3B13600E7F6C1 /* MvceTests */, 71 | E0E7B38720D3B13600E7F6C1 /* Products */, 72 | ); 73 | sourceTree = ""; 74 | }; 75 | E0E7B38720D3B13600E7F6C1 /* Products */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | E0E7B38620D3B13600E7F6C1 /* Mvce.framework */, 79 | E0E7B38F20D3B13600E7F6C1 /* MvceTests.xctest */, 80 | E0707B8020D57B8E0054B059 /* Mvce.framework */, 81 | ); 82 | name = Products; 83 | sourceTree = ""; 84 | }; 85 | E0E7B38820D3B13600E7F6C1 /* Mvce */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | E0E7B38920D3B13600E7F6C1 /* Mvce.h */, 89 | E0E7B3A020D3B15900E7F6C1 /* Mvce.swift */, 90 | E0E7B38A20D3B13600E7F6C1 /* Info.plist */, 91 | E0EFDC0220D57EC300C0BDC8 /* Info-macOS.plist */, 92 | ); 93 | path = Mvce; 94 | sourceTree = ""; 95 | }; 96 | E0E7B39320D3B13600E7F6C1 /* MvceTests */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | E0E7B39420D3B13600E7F6C1 /* MiniMVCTests.swift */, 100 | E0E7B39620D3B13600E7F6C1 /* Info.plist */, 101 | ); 102 | path = MvceTests; 103 | sourceTree = ""; 104 | }; 105 | /* End PBXGroup section */ 106 | 107 | /* Begin PBXHeadersBuildPhase section */ 108 | E0707B7B20D57B8E0054B059 /* Headers */ = { 109 | isa = PBXHeadersBuildPhase; 110 | buildActionMask = 2147483647; 111 | files = ( 112 | E0707B8820D57C360054B059 /* Mvce.h in Headers */, 113 | ); 114 | runOnlyForDeploymentPostprocessing = 0; 115 | }; 116 | E0E7B38120D3B13600E7F6C1 /* Headers */ = { 117 | isa = PBXHeadersBuildPhase; 118 | buildActionMask = 2147483647; 119 | files = ( 120 | E0E7B39720D3B13600E7F6C1 /* Mvce.h in Headers */, 121 | ); 122 | runOnlyForDeploymentPostprocessing = 0; 123 | }; 124 | /* End PBXHeadersBuildPhase section */ 125 | 126 | /* Begin PBXNativeTarget section */ 127 | E0707B7F20D57B8E0054B059 /* Mvce-macOS */ = { 128 | isa = PBXNativeTarget; 129 | buildConfigurationList = E0707B8720D57B8E0054B059 /* Build configuration list for PBXNativeTarget "Mvce-macOS" */; 130 | buildPhases = ( 131 | E0707B7B20D57B8E0054B059 /* Headers */, 132 | E0707B7C20D57B8E0054B059 /* Sources */, 133 | E0707B7D20D57B8E0054B059 /* Frameworks */, 134 | E0707B7E20D57B8E0054B059 /* Resources */, 135 | ); 136 | buildRules = ( 137 | ); 138 | dependencies = ( 139 | ); 140 | name = "Mvce-macOS"; 141 | productName = "MiniMVC-macOS"; 142 | productReference = E0707B8020D57B8E0054B059 /* Mvce.framework */; 143 | productType = "com.apple.product-type.framework"; 144 | }; 145 | E0E7B38520D3B13600E7F6C1 /* Mvce */ = { 146 | isa = PBXNativeTarget; 147 | buildConfigurationList = E0E7B39A20D3B13600E7F6C1 /* Build configuration list for PBXNativeTarget "Mvce" */; 148 | buildPhases = ( 149 | E0E7B38120D3B13600E7F6C1 /* Headers */, 150 | E0E7B38220D3B13600E7F6C1 /* Sources */, 151 | E0E7B38320D3B13600E7F6C1 /* Frameworks */, 152 | E0E7B38420D3B13600E7F6C1 /* Resources */, 153 | ); 154 | buildRules = ( 155 | ); 156 | dependencies = ( 157 | ); 158 | name = Mvce; 159 | productName = MiniMVC; 160 | productReference = E0E7B38620D3B13600E7F6C1 /* Mvce.framework */; 161 | productType = "com.apple.product-type.framework"; 162 | }; 163 | E0E7B38E20D3B13600E7F6C1 /* MvceTests */ = { 164 | isa = PBXNativeTarget; 165 | buildConfigurationList = E0E7B39D20D3B13600E7F6C1 /* Build configuration list for PBXNativeTarget "MvceTests" */; 166 | buildPhases = ( 167 | E0E7B38B20D3B13600E7F6C1 /* Sources */, 168 | E0E7B38C20D3B13600E7F6C1 /* Frameworks */, 169 | E0E7B38D20D3B13600E7F6C1 /* Resources */, 170 | ); 171 | buildRules = ( 172 | ); 173 | dependencies = ( 174 | E0E7B39220D3B13600E7F6C1 /* PBXTargetDependency */, 175 | ); 176 | name = MvceTests; 177 | productName = MiniMVCTests; 178 | productReference = E0E7B38F20D3B13600E7F6C1 /* MvceTests.xctest */; 179 | productType = "com.apple.product-type.bundle.unit-test"; 180 | }; 181 | /* End PBXNativeTarget section */ 182 | 183 | /* Begin PBXProject section */ 184 | E0E7B37D20D3B13600E7F6C1 /* Project object */ = { 185 | isa = PBXProject; 186 | attributes = { 187 | LastSwiftUpdateCheck = 1000; 188 | LastUpgradeCheck = 1000; 189 | ORGANIZATIONNAME = "CHEN Xianan"; 190 | TargetAttributes = { 191 | E0707B7F20D57B8E0054B059 = { 192 | CreatedOnToolsVersion = 10.0; 193 | }; 194 | E0E7B38520D3B13600E7F6C1 = { 195 | CreatedOnToolsVersion = 10.0; 196 | LastSwiftMigration = 1000; 197 | }; 198 | E0E7B38E20D3B13600E7F6C1 = { 199 | CreatedOnToolsVersion = 10.0; 200 | }; 201 | }; 202 | }; 203 | buildConfigurationList = E0E7B38020D3B13600E7F6C1 /* Build configuration list for PBXProject "Mvce" */; 204 | compatibilityVersion = "Xcode 9.3"; 205 | developmentRegion = en; 206 | hasScannedForEncodings = 0; 207 | knownRegions = ( 208 | en, 209 | ); 210 | mainGroup = E0E7B37C20D3B13600E7F6C1; 211 | productRefGroup = E0E7B38720D3B13600E7F6C1 /* Products */; 212 | projectDirPath = ""; 213 | projectRoot = ""; 214 | targets = ( 215 | E0E7B38520D3B13600E7F6C1 /* Mvce */, 216 | E0E7B38E20D3B13600E7F6C1 /* MvceTests */, 217 | E0707B7F20D57B8E0054B059 /* Mvce-macOS */, 218 | ); 219 | }; 220 | /* End PBXProject section */ 221 | 222 | /* Begin PBXResourcesBuildPhase section */ 223 | E0707B7E20D57B8E0054B059 /* Resources */ = { 224 | isa = PBXResourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | ); 228 | runOnlyForDeploymentPostprocessing = 0; 229 | }; 230 | E0E7B38420D3B13600E7F6C1 /* Resources */ = { 231 | isa = PBXResourcesBuildPhase; 232 | buildActionMask = 2147483647; 233 | files = ( 234 | ); 235 | runOnlyForDeploymentPostprocessing = 0; 236 | }; 237 | E0E7B38D20D3B13600E7F6C1 /* Resources */ = { 238 | isa = PBXResourcesBuildPhase; 239 | buildActionMask = 2147483647; 240 | files = ( 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | }; 244 | /* End PBXResourcesBuildPhase section */ 245 | 246 | /* Begin PBXSourcesBuildPhase section */ 247 | E0707B7C20D57B8E0054B059 /* Sources */ = { 248 | isa = PBXSourcesBuildPhase; 249 | buildActionMask = 2147483647; 250 | files = ( 251 | E0707B8920D57C560054B059 /* Mvce.swift in Sources */, 252 | ); 253 | runOnlyForDeploymentPostprocessing = 0; 254 | }; 255 | E0E7B38220D3B13600E7F6C1 /* Sources */ = { 256 | isa = PBXSourcesBuildPhase; 257 | buildActionMask = 2147483647; 258 | files = ( 259 | E0E7B3A120D3B15900E7F6C1 /* Mvce.swift in Sources */, 260 | ); 261 | runOnlyForDeploymentPostprocessing = 0; 262 | }; 263 | E0E7B38B20D3B13600E7F6C1 /* Sources */ = { 264 | isa = PBXSourcesBuildPhase; 265 | buildActionMask = 2147483647; 266 | files = ( 267 | E0E7B39520D3B13600E7F6C1 /* MiniMVCTests.swift in Sources */, 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | }; 271 | /* End PBXSourcesBuildPhase section */ 272 | 273 | /* Begin PBXTargetDependency section */ 274 | E0E7B39220D3B13600E7F6C1 /* PBXTargetDependency */ = { 275 | isa = PBXTargetDependency; 276 | target = E0E7B38520D3B13600E7F6C1 /* Mvce */; 277 | targetProxy = E0E7B39120D3B13600E7F6C1 /* PBXContainerItemProxy */; 278 | }; 279 | /* End PBXTargetDependency section */ 280 | 281 | /* Begin XCBuildConfiguration section */ 282 | E0707B8520D57B8E0054B059 /* Debug */ = { 283 | isa = XCBuildConfiguration; 284 | buildSettings = { 285 | CODE_SIGN_IDENTITY = "-"; 286 | CODE_SIGN_STYLE = Automatic; 287 | COMBINE_HIDPI_IMAGES = YES; 288 | DEFINES_MODULE = YES; 289 | DYLIB_COMPATIBILITY_VERSION = 1; 290 | DYLIB_CURRENT_VERSION = 1; 291 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 292 | FRAMEWORK_VERSION = A; 293 | INFOPLIST_FILE = "Mvce/Info-macOS.plist"; 294 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 295 | LD_RUNPATH_SEARCH_PATHS = ( 296 | "$(inherited)", 297 | "@executable_path/../Frameworks", 298 | "@loader_path/Frameworks", 299 | ); 300 | MACOSX_DEPLOYMENT_TARGET = 10.9; 301 | PRODUCT_BUNDLE_IDENTIFIER = com.realazy.Mvce; 302 | PRODUCT_NAME = Mvce; 303 | SDKROOT = macosx; 304 | SKIP_INSTALL = YES; 305 | SWIFT_VERSION = 4.2; 306 | }; 307 | name = Debug; 308 | }; 309 | E0707B8620D57B8E0054B059 /* Release */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | CODE_SIGN_IDENTITY = "-"; 313 | CODE_SIGN_STYLE = Automatic; 314 | COMBINE_HIDPI_IMAGES = YES; 315 | DEFINES_MODULE = YES; 316 | DYLIB_COMPATIBILITY_VERSION = 1; 317 | DYLIB_CURRENT_VERSION = 1; 318 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 319 | FRAMEWORK_VERSION = A; 320 | INFOPLIST_FILE = "Mvce/Info-macOS.plist"; 321 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 322 | LD_RUNPATH_SEARCH_PATHS = ( 323 | "$(inherited)", 324 | "@executable_path/../Frameworks", 325 | "@loader_path/Frameworks", 326 | ); 327 | MACOSX_DEPLOYMENT_TARGET = 10.9; 328 | PRODUCT_BUNDLE_IDENTIFIER = com.realazy.Mvce; 329 | PRODUCT_NAME = Mvce; 330 | SDKROOT = macosx; 331 | SKIP_INSTALL = YES; 332 | SWIFT_VERSION = 4.2; 333 | }; 334 | name = Release; 335 | }; 336 | E0E7B39820D3B13600E7F6C1 /* Debug */ = { 337 | isa = XCBuildConfiguration; 338 | buildSettings = { 339 | ALWAYS_SEARCH_USER_PATHS = NO; 340 | CLANG_ANALYZER_NONNULL = YES; 341 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 342 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 343 | CLANG_CXX_LIBRARY = "libc++"; 344 | CLANG_ENABLE_MODULES = YES; 345 | CLANG_ENABLE_OBJC_ARC = YES; 346 | CLANG_ENABLE_OBJC_WEAK = YES; 347 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 348 | CLANG_WARN_BOOL_CONVERSION = YES; 349 | CLANG_WARN_COMMA = YES; 350 | CLANG_WARN_CONSTANT_CONVERSION = YES; 351 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 352 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 353 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 354 | CLANG_WARN_EMPTY_BODY = YES; 355 | CLANG_WARN_ENUM_CONVERSION = YES; 356 | CLANG_WARN_INFINITE_RECURSION = YES; 357 | CLANG_WARN_INT_CONVERSION = YES; 358 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 359 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 360 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 361 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 362 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 363 | CLANG_WARN_STRICT_PROTOTYPES = YES; 364 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 365 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 366 | CLANG_WARN_UNREACHABLE_CODE = YES; 367 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 368 | CODE_SIGN_IDENTITY = "iPhone Developer"; 369 | COPY_PHASE_STRIP = NO; 370 | CURRENT_PROJECT_VERSION = 1; 371 | DEBUG_INFORMATION_FORMAT = dwarf; 372 | ENABLE_STRICT_OBJC_MSGSEND = YES; 373 | ENABLE_TESTABILITY = YES; 374 | GCC_C_LANGUAGE_STANDARD = gnu11; 375 | GCC_DYNAMIC_NO_PIC = NO; 376 | GCC_NO_COMMON_BLOCKS = YES; 377 | GCC_OPTIMIZATION_LEVEL = 0; 378 | GCC_PREPROCESSOR_DEFINITIONS = ( 379 | "DEBUG=1", 380 | "$(inherited)", 381 | ); 382 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 383 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 384 | GCC_WARN_UNDECLARED_SELECTOR = YES; 385 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 386 | GCC_WARN_UNUSED_FUNCTION = YES; 387 | GCC_WARN_UNUSED_VARIABLE = YES; 388 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 389 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 390 | ONLY_ACTIVE_ARCH = YES; 391 | SDKROOT = iphoneos; 392 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 393 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 394 | VERSIONING_SYSTEM = "apple-generic"; 395 | VERSION_INFO_PREFIX = ""; 396 | }; 397 | name = Debug; 398 | }; 399 | E0E7B39920D3B13600E7F6C1 /* Release */ = { 400 | isa = XCBuildConfiguration; 401 | buildSettings = { 402 | ALWAYS_SEARCH_USER_PATHS = NO; 403 | CLANG_ANALYZER_NONNULL = YES; 404 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 405 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 406 | CLANG_CXX_LIBRARY = "libc++"; 407 | CLANG_ENABLE_MODULES = YES; 408 | CLANG_ENABLE_OBJC_ARC = YES; 409 | CLANG_ENABLE_OBJC_WEAK = YES; 410 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 411 | CLANG_WARN_BOOL_CONVERSION = YES; 412 | CLANG_WARN_COMMA = YES; 413 | CLANG_WARN_CONSTANT_CONVERSION = YES; 414 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 415 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 416 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 417 | CLANG_WARN_EMPTY_BODY = YES; 418 | CLANG_WARN_ENUM_CONVERSION = YES; 419 | CLANG_WARN_INFINITE_RECURSION = YES; 420 | CLANG_WARN_INT_CONVERSION = YES; 421 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 422 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 423 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 424 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 425 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 426 | CLANG_WARN_STRICT_PROTOTYPES = YES; 427 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 428 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 429 | CLANG_WARN_UNREACHABLE_CODE = YES; 430 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 431 | CODE_SIGN_IDENTITY = "iPhone Developer"; 432 | COPY_PHASE_STRIP = NO; 433 | CURRENT_PROJECT_VERSION = 1; 434 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 435 | ENABLE_NS_ASSERTIONS = NO; 436 | ENABLE_STRICT_OBJC_MSGSEND = YES; 437 | GCC_C_LANGUAGE_STANDARD = gnu11; 438 | GCC_NO_COMMON_BLOCKS = YES; 439 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 440 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 441 | GCC_WARN_UNDECLARED_SELECTOR = YES; 442 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 443 | GCC_WARN_UNUSED_FUNCTION = YES; 444 | GCC_WARN_UNUSED_VARIABLE = YES; 445 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 446 | MTL_ENABLE_DEBUG_INFO = NO; 447 | SDKROOT = iphoneos; 448 | SWIFT_COMPILATION_MODE = wholemodule; 449 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 450 | VALIDATE_PRODUCT = YES; 451 | VERSIONING_SYSTEM = "apple-generic"; 452 | VERSION_INFO_PREFIX = ""; 453 | }; 454 | name = Release; 455 | }; 456 | E0E7B39B20D3B13600E7F6C1 /* Debug */ = { 457 | isa = XCBuildConfiguration; 458 | buildSettings = { 459 | CLANG_ENABLE_MODULES = YES; 460 | CODE_SIGN_IDENTITY = ""; 461 | CODE_SIGN_STYLE = Automatic; 462 | DEFINES_MODULE = YES; 463 | DYLIB_COMPATIBILITY_VERSION = 1; 464 | DYLIB_CURRENT_VERSION = 1; 465 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 466 | INFOPLIST_FILE = Mvce/Info.plist; 467 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 468 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 469 | LD_RUNPATH_SEARCH_PATHS = ( 470 | "$(inherited)", 471 | "@executable_path/Frameworks", 472 | "@loader_path/Frameworks", 473 | ); 474 | PRODUCT_BUNDLE_IDENTIFIER = com.realazy.Mvce; 475 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 476 | SKIP_INSTALL = YES; 477 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 478 | SWIFT_VERSION = 4.2; 479 | TARGETED_DEVICE_FAMILY = "1,2"; 480 | }; 481 | name = Debug; 482 | }; 483 | E0E7B39C20D3B13600E7F6C1 /* Release */ = { 484 | isa = XCBuildConfiguration; 485 | buildSettings = { 486 | CLANG_ENABLE_MODULES = YES; 487 | CODE_SIGN_IDENTITY = ""; 488 | CODE_SIGN_STYLE = Automatic; 489 | DEFINES_MODULE = YES; 490 | DYLIB_COMPATIBILITY_VERSION = 1; 491 | DYLIB_CURRENT_VERSION = 1; 492 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 493 | INFOPLIST_FILE = Mvce/Info.plist; 494 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 495 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 496 | LD_RUNPATH_SEARCH_PATHS = ( 497 | "$(inherited)", 498 | "@executable_path/Frameworks", 499 | "@loader_path/Frameworks", 500 | ); 501 | PRODUCT_BUNDLE_IDENTIFIER = com.realazy.Mvce; 502 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 503 | SKIP_INSTALL = YES; 504 | SWIFT_VERSION = 4.2; 505 | TARGETED_DEVICE_FAMILY = "1,2"; 506 | }; 507 | name = Release; 508 | }; 509 | E0E7B39E20D3B13600E7F6C1 /* Debug */ = { 510 | isa = XCBuildConfiguration; 511 | buildSettings = { 512 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 513 | CODE_SIGN_STYLE = Automatic; 514 | INFOPLIST_FILE = MvceTests/Info.plist; 515 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 516 | LD_RUNPATH_SEARCH_PATHS = ( 517 | "$(inherited)", 518 | "@executable_path/Frameworks", 519 | "@loader_path/Frameworks", 520 | ); 521 | PRODUCT_BUNDLE_IDENTIFIER = com.realazy.MvceTests; 522 | PRODUCT_NAME = "$(TARGET_NAME)"; 523 | SWIFT_VERSION = 4.2; 524 | TARGETED_DEVICE_FAMILY = "1,2"; 525 | }; 526 | name = Debug; 527 | }; 528 | E0E7B39F20D3B13600E7F6C1 /* Release */ = { 529 | isa = XCBuildConfiguration; 530 | buildSettings = { 531 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 532 | CODE_SIGN_STYLE = Automatic; 533 | INFOPLIST_FILE = MvceTests/Info.plist; 534 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 535 | LD_RUNPATH_SEARCH_PATHS = ( 536 | "$(inherited)", 537 | "@executable_path/Frameworks", 538 | "@loader_path/Frameworks", 539 | ); 540 | PRODUCT_BUNDLE_IDENTIFIER = com.realazy.MvceTests; 541 | PRODUCT_NAME = "$(TARGET_NAME)"; 542 | SWIFT_VERSION = 4.2; 543 | TARGETED_DEVICE_FAMILY = "1,2"; 544 | }; 545 | name = Release; 546 | }; 547 | /* End XCBuildConfiguration section */ 548 | 549 | /* Begin XCConfigurationList section */ 550 | E0707B8720D57B8E0054B059 /* Build configuration list for PBXNativeTarget "Mvce-macOS" */ = { 551 | isa = XCConfigurationList; 552 | buildConfigurations = ( 553 | E0707B8520D57B8E0054B059 /* Debug */, 554 | E0707B8620D57B8E0054B059 /* Release */, 555 | ); 556 | defaultConfigurationIsVisible = 0; 557 | defaultConfigurationName = Release; 558 | }; 559 | E0E7B38020D3B13600E7F6C1 /* Build configuration list for PBXProject "Mvce" */ = { 560 | isa = XCConfigurationList; 561 | buildConfigurations = ( 562 | E0E7B39820D3B13600E7F6C1 /* Debug */, 563 | E0E7B39920D3B13600E7F6C1 /* Release */, 564 | ); 565 | defaultConfigurationIsVisible = 0; 566 | defaultConfigurationName = Release; 567 | }; 568 | E0E7B39A20D3B13600E7F6C1 /* Build configuration list for PBXNativeTarget "Mvce" */ = { 569 | isa = XCConfigurationList; 570 | buildConfigurations = ( 571 | E0E7B39B20D3B13600E7F6C1 /* Debug */, 572 | E0E7B39C20D3B13600E7F6C1 /* Release */, 573 | ); 574 | defaultConfigurationIsVisible = 0; 575 | defaultConfigurationName = Release; 576 | }; 577 | E0E7B39D20D3B13600E7F6C1 /* Build configuration list for PBXNativeTarget "MvceTests" */ = { 578 | isa = XCConfigurationList; 579 | buildConfigurations = ( 580 | E0E7B39E20D3B13600E7F6C1 /* Debug */, 581 | E0E7B39F20D3B13600E7F6C1 /* Release */, 582 | ); 583 | defaultConfigurationIsVisible = 0; 584 | defaultConfigurationName = Release; 585 | }; 586 | /* End XCConfigurationList section */ 587 | }; 588 | rootObject = E0E7B37D20D3B13600E7F6C1 /* Project object */; 589 | } 590 | -------------------------------------------------------------------------------- /Mvce/Info-macOS.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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2018 CHEN Xianan. All rights reserved. 23 | 24 | 25 | -------------------------------------------------------------------------------- /Mvce/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Mvce/Mvce.h: -------------------------------------------------------------------------------- 1 | // 2 | // Mvce.h 3 | // Mvce 4 | // 5 | // Created by CHEN Xianan on 6/15/18. 6 | // Copyright © 2018 CHEN Xianan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Mvce. 12 | FOUNDATION_EXPORT double MvceVersionNumber; 13 | 14 | //! Project version string for Mvce. 15 | FOUNDATION_EXPORT const unsigned char MvceVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Mvce/Mvce.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mvce.swift 3 | // Mvce 4 | // 5 | // Created by CHEN Xianan on 6/15/18. 6 | // Copyright © 2018 CHEN Xianan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol Dispatchable { 12 | associatedtype Event 13 | 14 | func send(event: Event) 15 | } 16 | 17 | private extension Dispatchable where Self: AnyObject { 18 | typealias SendEvent = (Event) -> Void 19 | 20 | var _mvce_sendEvent: SendEvent? { 21 | get { 22 | let key: StaticString = #function 23 | return objc_getAssociatedObject(self, key.utf8Start) as? SendEvent 24 | } 25 | set { 26 | let key: StaticString = #function 27 | objc_setAssociatedObject(self, key.utf8Start, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 28 | if let sendEvent = newValue, _mvce_eventsBeforeGlue.count > 0 { 29 | _mvce_eventsBeforeGlue.forEach { sendEvent(($0 as! Box).value) } 30 | _mvce_eventsBeforeGlue.removeAllObjects() 31 | } 32 | } 33 | } 34 | 35 | var _mvce_eventsBeforeGlue: NSMutableArray { 36 | let key: StaticString = #function 37 | var list = objc_getAssociatedObject(self, key.utf8Start) as? NSMutableArray 38 | if (list == nil) { 39 | list = NSMutableArray(capacity: 1) 40 | objc_setAssociatedObject(self, key.utf8Start, list, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 41 | } 42 | 43 | return list! 44 | } 45 | } 46 | 47 | public extension Dispatchable where Self: AnyObject { 48 | func send(event: Event) { 49 | if let sendEvent = _mvce_sendEvent { sendEvent(event) } 50 | else { _mvce_eventsBeforeGlue.add(Box(event)) } 51 | } 52 | } 53 | 54 | public struct Dispatcher: Dispatchable { 55 | public typealias Event = E 56 | typealias SendEvent = (Event) -> Void 57 | 58 | private let sendEvent: SendEvent 59 | init(sendEvent: @escaping SendEvent) { self.sendEvent = sendEvent } 60 | public func send(event: Event) { sendEvent(event) } 61 | } 62 | 63 | public protocol Controller { 64 | associatedtype Model 65 | associatedtype Event 66 | 67 | func update(model: Model, for event: Event, dispatcher: Dispatcher) -> Void 68 | } 69 | 70 | public protocol View { 71 | associatedtype Model 72 | associatedtype Event 73 | typealias BindingDisposer = () -> Void 74 | 75 | func bind(model: Model, dispatcher: Dispatcher) -> BindingDisposer 76 | } 77 | 78 | public struct Mvce { 79 | private static func _glue(model: Model, view: V, controller: C) -> EventLoop 80 | where 81 | V.Model == Model, V.Event == Event, 82 | C.Model == Model, C.Event == Event 83 | { 84 | let loop = EventLoop(model: model, view: view, controller: controller) 85 | let key: StaticString = #function 86 | objc_setAssociatedObject(view, key.utf8Start, loop, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 87 | return loop 88 | } 89 | 90 | static public func glue(model: Model, view: V, controller: C) 91 | where 92 | V.Model == Model, V.Event == Event, 93 | C.Model == Model, C.Event == Event 94 | { 95 | let _ = _glue(model: model, view: view, controller: controller) 96 | } 97 | 98 | static public func glue(model: Model, view: V, controller: C) 99 | where 100 | V.Model == Model, V.Event == Event, 101 | C.Model == Model, C.Event == Event 102 | { 103 | let loop = _glue(model: model, view: view, controller: controller) 104 | var v = view 105 | v._mvce_sendEvent = loop.sendEvent 106 | } 107 | 108 | static public func glue(model: Model, view: V, controller: C) 109 | where 110 | V.Model == Model, V.Event == Event, 111 | C.Model == Model, C.Event == Event 112 | { 113 | let loop = _glue(model: model, view: view, controller: controller) 114 | var c = controller 115 | c._mvce_sendEvent = loop.sendEvent 116 | } 117 | 118 | static public func glue(model: Model, view: V, controller: C) 119 | where 120 | V.Model == Model, V.Event == Event, 121 | C.Model == Model, C.Event == Event 122 | { 123 | let loop = _glue(model: model, view: view, controller: controller) 124 | var v = view 125 | var c = controller 126 | v._mvce_sendEvent = loop.sendEvent 127 | c._mvce_sendEvent = loop.sendEvent 128 | } 129 | } 130 | 131 | private class EventLoop { 132 | let dispose: View.BindingDisposer 133 | let obsever: NSObjectProtocol 134 | let sendEvent: Dispatcher.SendEvent 135 | 136 | init(model: Model, view: V, controller: C) 137 | where V.Model == Model, V.Event == Event, 138 | C.Model == Model, C.Event == Event 139 | { 140 | let notiName = Notification.Name(UUID.init().uuidString) 141 | sendEvent = { event in NotificationCenter.default.post(name: notiName, object: Box(event)) } 142 | let dispatcher = Dispatcher(sendEvent: sendEvent) 143 | dispose = view.bind(model: model, dispatcher: dispatcher) 144 | obsever = NotificationCenter.default.addObserver(forName: notiName, object: nil, queue: nil) { notification in 145 | guard let event = notification.object as? Box else { return } 146 | controller.update(model: model, for: event.value, dispatcher: dispatcher) 147 | } 148 | } 149 | 150 | deinit { 151 | NotificationCenter.default.removeObserver(obsever) 152 | dispose() 153 | } 154 | } 155 | 156 | private class Box { 157 | let value: T 158 | init(_ value: T) { self.value = value } 159 | } 160 | -------------------------------------------------------------------------------- /MvceTests/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 | -------------------------------------------------------------------------------- /MvceTests/MiniMVCTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MvceTests.swift 3 | // MvceTests 4 | // 5 | // Created by CHEN Xianan on 6/15/18. 6 | // Copyright © 2018 CHEN Xianan. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Mvce 11 | 12 | class SampleModel: NSObject { 13 | @objc dynamic var counter = 0 14 | } 15 | 16 | enum SampleEvent { 17 | case increment 18 | case decrement 19 | } 20 | 21 | struct SampleController: Mvce.Controller { 22 | typealias Model = SampleModel 23 | typealias Event = SampleEvent 24 | 25 | func update(model: SampleModel, for event: SampleEvent, eventEmitter: @escaping (SampleEvent) -> Void) { 26 | switch event { 27 | case .increment: 28 | model.counter += 1 29 | case .decrement: 30 | model.counter -= 1 31 | } 32 | } 33 | } 34 | 35 | class SampleView: NSObject, Mvce.EventEmitter, Mvce.View { 36 | typealias Model = SampleModel 37 | typealias Event = SampleEvent 38 | 39 | var counter = -1000 40 | 41 | func bind(model: SampleModel) -> Invalidator { 42 | return Mvce.flatKVObservations([ 43 | model.observe(\SampleModel.counter, options: [.initial, .new]) { [weak self] (_, change) in 44 | if let c = change.newValue { 45 | self?.counter = c 46 | } 47 | } 48 | ]) 49 | } 50 | 51 | func bind(eventEmitter: (SampleEvent) -> Void) { 52 | 53 | } 54 | } 55 | 56 | class MvceTests: XCTestCase { 57 | let model = SampleModel() 58 | var view = SampleView() 59 | let controller = SampleController() 60 | 61 | override func setUp() { 62 | super.setUp() 63 | Mvce.glue(model: model, view: view, controller: controller) 64 | } 65 | 66 | override func tearDown() { 67 | super.tearDown() 68 | } 69 | 70 | func testEmittingEvent() { 71 | XCTAssertEqual(view.counter, 0) 72 | view.emit(event: .increment) 73 | XCTAssertEqual(model.counter, 1) 74 | XCTAssertEqual(view.counter, 1) 75 | view.emit(event: .increment) 76 | XCTAssertEqual(model.counter, 2) 77 | XCTAssertEqual(view.counter, 2) 78 | view.emit(event: .decrement) 79 | XCTAssertEqual(model.counter, 1) 80 | XCTAssertEqual(view.counter, 1) 81 | view.emit(event: .decrement) 82 | XCTAssertEqual(model.counter, 0) 83 | XCTAssertEqual(view.counter, 0) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mvce — Event driven MVC 2 | 3 | Mvce can be pronounced as **/myo͞oz/**. 4 | 5 | An event driven MVC library to glue decoupled Model, View, and Controller for UIKit/AppKit. Minimal, simple, and unobtrusive. 6 | 7 | 本文档同时提供[简体中文版](README.zh_CN.md)。 8 | 9 | ## Why 10 | 11 | UIKit/AppKit is mainly about view. Don't be misled by the `Controller` in `UIViewController`/`NSViewController` and descendants, they are all views, should be avoided things that belong to a real controller, such as networking, model updating. 12 | 13 | How to glue view, model, and controller is upon to you, UIKit/AppKit has no strong options on that. Typically, as the (bad) official examples show to us, we define a model, refer it inside `UIViewController`/`NSViewController`s, and manipulate the model directly. It works like a...charm? 14 | 15 | No, it's M-VC without C, it's strong coupling, it's not reusable(for crossing UIKit and AppKit), if you care, it's also untestable. 16 | 17 | ## How 18 | 19 | The key idea of MVC is the separation of Model, View, and Controller. To glue 'em, Mvce provides an alternative way. 20 | 21 | Let's take a taste of Mvce first, here is a simple counter app: 22 | 23 | ![iOS Sample App](Assets/iOSCounterApp.png) 24 | 25 | All code shows below (whole project [here](Example/Counter)): 26 | 27 | ```swift 28 | // CounterModel.swift: 29 | // Model to represent count 30 | final class CounterModel: NSObject { 31 | @objc dynamic var count = 0 32 | } 33 | 34 | // CounterController.swift: 35 | // Event to represent behavior for button ++ and -- 36 | enum CounterEvent { 37 | case increment 38 | case decrement 39 | } 40 | 41 | // Controller to represent how to update model 42 | struct CounterController: Mvce.Controller { 43 | typealias Model = CounterModel 44 | typealias Event = CounterEvent 45 | 46 | func update(model: Model, for event: Event, dispatcher: Dispatcher) { 47 | switch event { 48 | case .increment: 49 | model.count += 1 50 | case .decrement: 51 | model.count -= 1 52 | } 53 | } 54 | } 55 | 56 | // ViewContorller.swift: 57 | // View to represent model state, and emit event to notify controller to update model 58 | final class ViewController: UIViewController { 59 | @IBOutlet weak var label: UILabel! 60 | @IBOutlet weak var incrButton: UIButton! 61 | @IBOutlet weak var decrButton: UIButton! 62 | 63 | override func viewDidLoad() { 64 | super.viewDidLoad() 65 | Mvce.glue(model: CounterModel(), view: self, controller: CounterController()) 66 | } 67 | } 68 | 69 | extension ViewController: View { 70 | typealias Model = CounterModel 71 | typealias Event = CounterEvent 72 | 73 | func bind(model: Model, dispatcher: Dispatcher) -> View.BindingDisposer { 74 | let observation = model.bind(\CounterModel.count, to: label, at: \UILabel.text) { String(format: "%d", $0) } 75 | let action = ButtonAction(sendEvent: dispatcher.send(event:)) 76 | incrButton.addTarget(action, action: #selector(action.incr(_:)), for: .touchUpInside) 77 | decrButton.addTarget(action, action: #selector(action.decr(_:)), for: .touchUpInside) 78 | let key: StaticString = #function 79 | objc_setAssociatedObject(self, key.utf8Start, action, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) // Need to retain target 80 | return observation.invalidate 81 | } 82 | } 83 | 84 | // ButtonAction.swift: 85 | // Helper for button actions 86 | final class ButtonAction: NSObject { 87 | let sendEvent: (CounterEvent) -> Void 88 | 89 | init(sendEvent: @escaping (CounterEvent) -> Void) { 90 | self.sendEvent = sendEvent 91 | } 92 | 93 | @objc func incr(_ sender: Any?) { 94 | sendEvent(.increment) 95 | } 96 | 97 | @objc func decr(_ sender: Any?) { 98 | sendEvent(.decrement) 99 | } 100 | } 101 | ``` 102 | 103 | ### Decouple View and Model 104 | 105 | Take a careful look at our `ViewController`, there's no any reference to model! Just adopt `View` protocol and bind model's count to label inside `func bind(model: Model, dispatcher: Dispatcher) -> View.BindingDisposer`. And bind event dispatcher to the increment and decrement buttons. 106 | 107 | You can use KVO (this example) or other binding framework/library e.g. ReactiveCocoa w/ ReactiveSwift, RxSwift to bind model to view. 108 | 109 | Check [Example/RandomImage](Example/RandomImage), which uses ReactiveCocoa for binding. 110 | 111 | ### Decouple View and Controller 112 | 113 | There is no any reference to controller inside view too! `View` protocol also requires you bind event dispatcher. What's an event dispatcher? Just a wrapper for `(Event) -> Void`, you can use it to send event, Mvce will dispatch event to controller and inform it to update model. 114 | 115 | ### Glue Model, View, and Controller together 116 | 117 | Glue 'em all with `Mvce.glue(model:view:controller:)`, inject to `loadView` or `viewDidLoad` in `UIViewController`/`NSViewController`. And lifetime is managed by Mvce. 118 | 119 | ### Cross-Platform (iOS & macOS)? 120 | 121 | Sure, that's _REAL_ MVC's advantage! Model and Controller can be shared, only platform-independent view is required to rewrite. 122 | 123 | ```swift 124 | // macOS/viewController.swift 125 | class ViewController: NSViewController { 126 | @IBOutlet weak var label: NSTextField! 127 | @IBOutlet weak var incrButton: NSButton! 128 | @IBOutlet weak var decrButton: NSButton! 129 | 130 | override func viewDidLoad() { 131 | super.viewDidLoad() 132 | Mvce.glue(model: CounterModel(), view: self, controller: CounterController()) 133 | } 134 | } 135 | 136 | extension ViewController: View { 137 | typealias Model = CounterModel 138 | typealias Event = CounterEvent 139 | 140 | func bind(model: Model, dispatcher: Dispatcher) -> View.BindingDisposer { 141 | let observation = model.bind(\.count, to: label, at: \.stringValue) { String(format: "%d", $0) } 142 | let action = ButtonAction(sendEvent: dispatcher.send(event:)) 143 | incrButton.target = action 144 | incrButton.action = #selector(action.incr(_:)) 145 | decrButton.target = action 146 | decrButton.action = #selector(action.decr(_:)) 147 | let key: StaticString = #function 148 | objc_setAssociatedObject(self, key.utf8Start, action, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) // Need to retain target 149 | return observation.invalidate 150 | } 151 | } 152 | ``` 153 | 154 | ![macOS Sample App](Assets/macOSCounterApp.png) 155 | 156 | That's it! Remember to check out [Example](Example) directory for a more complex one. 157 | 158 | Don't forget to run `git submodule update --init --recursive` in order to install 3rd dependencies if you want to run the `RandomImage` sample project. 159 | 160 | ### `Dispatchable` protocol 161 | 162 | If you really, really need to access event dispatcher anywhere in View or Controller, just adopt `Dispatchable`. This's last resort, I don't recommend this way, it's easily to mess up code, violate MVC rules. 163 | 164 | ## License 165 | 166 | MIT 167 | 168 | ## Author 169 | 170 | - Blog: [realazy.com](https://realazy.com) (Chinese) 171 | - Github: [@cxa](https://github.com/cxa) 172 | - Twitter: [@\_cxa](https://twitter.com/_cxa) (Chinese mainly) 173 | -------------------------------------------------------------------------------- /README.zh_CN.md: -------------------------------------------------------------------------------- 1 | # Mvce — 事件驱动的 MVC 库 2 | 3 | Mvce 可读作 **/缪斯/**。 4 | 5 | Mvce 是一个事件驱动的 MVC 辅助库,用于组合模块、视图和控制器。适用于 UIKit 和 AppKit。 6 | 7 | Also available in [English](README.md). 8 | 9 | ## 缘由 10 | 11 | UIKit/AppKit 提供的主要是视图。不要被 `UIViewController`/`NSViewController` 及其子类中的 `Controller` 误导,它们都属于视图,永远不该有属于控制器的逻辑,比如网络请求、更新模块等。 12 | 13 | UIKit/AppKit 没有强制规定组合模型、视图和控制器的方法,如何去做取决于你。通常做法是,就像官方提供的那些(坏)例子,定义模型,在 `UIViewController`/`NSViewController` 引用,直接操作模型。如丝般……柔滑? 14 | 15 | 并不!这只不过是没有 C 的 M-VC,强耦合、无重用、难测试! 16 | 17 | ## 大法 18 | 19 | MVC 模式强调的是模型、视图和控制器分离,如何组合它们,Mvce 提供一套以事件为核心的方法。 20 | 21 | 以这款长这样的计数程序为例, 22 | 23 | ![iOS Sample App](Assets/iOSCounterApp.png) 24 | 25 | 先品品 Mvce 的风味,有码奉上: 26 | 27 | ```swift 28 | // CounterModel.swift: 29 | // 表示计数的模型 30 | final class CounterModel: NSObject { 31 | @objc dynamic var count = 0 32 | } 33 | 34 | // CounterController.swift: 35 | // 按钮 ++ 和 -- 的事件 36 | enum CounterEvent { 37 | case increment 38 | case decrement 39 | } 40 | 41 | // 处理不同的事件,更新模型的控制器 42 | struct CounterController: Mvce.Controller { 43 | typealias Model = CounterModel 44 | typealias Event = CounterEvent 45 | 46 | func update(model: Model, for event: Event, dispatcher: Dispatcher) { 47 | switch event { 48 | case .increment: 49 | model.count += 1 50 | case .decrement: 51 | model.count -= 1 52 | } 53 | } 54 | } 55 | 56 | // ViewContorller.swift: 57 | // 呈现模型状态的视图,用「事件传送器」(event dispatcher)传送事件通知控制器更新模型 58 | final class ViewController: UIViewController { 59 | @IBOutlet weak var label: UILabel! 60 | @IBOutlet weak var incrButton: UIButton! 61 | @IBOutlet weak var decrButton: UIButton! 62 | 63 | override func viewDidLoad() { 64 | super.viewDidLoad() 65 | // 别忘了在这里组合模型、视图和控制器,别担心,Mvce 管理它们生命周期 66 | Mvce.glue(model: CounterModel(), view: self, controller: CounterController()) 67 | } 68 | } 69 | 70 | extension ViewController: View { 71 | typealias Model = CounterModel 72 | typealias Event = CounterEvent 73 | 74 | func bind(model: Model, dispatcher: Dispatcher) -> View.BindingDisposer { 75 | let observation = model.bind(\CounterModel.count, to: label, at: \UILabel.text) { String(format: "%d", $0) } 76 | let action = ButtonAction(sendEvent: dispatcher.send(event:)) 77 | incrButton.addTarget(action, action: #selector(action.incr(_:)), for: .touchUpInside) 78 | decrButton.addTarget(action, action: #selector(action.decr(_:)), for: .touchUpInside) 79 | let key: StaticString = #function 80 | objc_setAssociatedObject(self, key.utf8Start, action, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) // Need to retain target 81 | return observation.invalidate 82 | } 83 | } 84 | 85 | // ButtonAction.swift: 86 | // 按钮事件处理器 87 | final class ButtonAction: NSObject { 88 | let sendEvent: (CounterEvent) -> Void 89 | 90 | init(sendEvent: @escaping (CounterEvent) -> Void) { 91 | self.sendEvent = sendEvent 92 | } 93 | 94 | @objc func incr(_ sender: Any?) { 95 | sendEvent(.increment) 96 | } 97 | 98 | @objc func decr(_ sender: Any?) { 99 | sendEvent(.decrement) 100 | } 101 | } 102 | 103 | ``` 104 | 105 | ### 分离模型和视图 106 | 107 | 仔细看视图 `ViewController` 的代码,你会发现没有任何模型的引用!只需遵循 `View` 协议,并在 `func bind(model: Model, dispatcher: Dispatcher) -> View.BindingDisposer` 里绑定模型到视图上。这个协议还提供事件传送器,让按钮可以发送相应的事件。 108 | 109 | 一旦你返回 `() -> Void`(Mvce 另名为 `View.BindingDisposer`),Mvce 会帮你管理这些绑定的生命周期。 110 | 111 | ### 分离视图和控制器 112 | 113 | 视图中也没有控制器的任何引用!`View` 协议除了要求绑定模型,还要求绑定事件传送器。何为事件传送器?只不过是一个 `(Event) -> Void` 函数包装器。用它来传送事件,Mvce 会分发给控制器,通知控制器更新模型。 114 | 115 | ### 组合模型、视图和控制器 116 | 117 | 在 `UIViewController`/`NSViewController` 的 `loadView` or `viewDidLoad` 里使用 `Mvce.glue(model:view:controller:)` 来组合它们。Mvce 会管理它们的生命周期。 118 | 119 | ### 跨平台(iOS & macOS)? 120 | 121 | 开玩笑,这可是「真·MVC」的最大好处之一。模型和控制器可共用,重写平台特定的视图就行了: 122 | 123 | ```swift 124 | // macOS/viewController.swift 125 | class ViewController: NSViewController { 126 | @IBOutlet weak var label: NSTextField! 127 | @IBOutlet weak var incrButton: NSButton! 128 | @IBOutlet weak var decrButton: NSButton! 129 | 130 | override func viewDidLoad() { 131 | super.viewDidLoad() 132 | Mvce.glue(model: CounterModel(), view: self, controller: CounterController()) 133 | } 134 | } 135 | 136 | extension ViewController: View { 137 | typealias Model = CounterModel 138 | typealias Event = CounterEvent 139 | 140 | func bind(model: Model, dispatcher: Dispatcher) -> View.BindingDisposer { 141 | let observation = model.bind(\.count, to: label, at: \.stringValue) { String(format: "%d", $0) } 142 | let action = ButtonAction(sendEvent: dispatcher.send(event:)) 143 | incrButton.target = action 144 | incrButton.action = #selector(action.incr(_:)) 145 | decrButton.target = action 146 | decrButton.action = #selector(action.decr(_:)) 147 | let key: StaticString = #function 148 | objc_setAssociatedObject(self, key.utf8Start, action, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) // Need to retain target 149 | return observation.invalidate 150 | } 151 | } 152 | ``` 153 | 154 | ![macOS Sample App](Assets/macOSCounterApp.png) 155 | 156 | 就这么多![Example](Example) 目录下还有复杂点儿的例子,不妨看看。需要注意的是,`RandomImage` 例子用到 ReactiveCocoa 来做绑定,需运行 `git submodule update --init --recursive` 来拉取。 157 | 158 | ### `Dispatchable` 协议 159 | 160 | 如果真的非常需要在 View 和 Controller 的任何角落里使用事件传送器,只需遵循让它们 `Dispatchable`。然而我不建议你这么做,这非常容易导致混乱的代码,破坏 MVC 的法则。 161 | 162 | ## 授权 163 | 164 | MIT 165 | 166 | ## 作者 167 | 168 | - Blog: [realazy.com](https://realazy.com) 169 | - Github: [@cxa](https://github.com/cxa) 170 | - Twitter: [@\_cxa](https://twitter.com/_cxa) 171 | --------------------------------------------------------------------------------