├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── CombineStorable.podspec ├── CombineStorable.xcodeproj ├── CombineStorableTests_Info.plist ├── CombineStorable_Info.plist ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ └── CombineStorable-Package.xcscheme ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── CombineStorable │ ├── NSObject+Storable.swift │ ├── Publisher+Storable.swift │ └── Storable.swift └── Tests ├── CombineStorableTests ├── StorableTests.swift └── XCTestManifests.swift └── LinuxMain.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | .build/ 39 | 40 | # CocoaPods 41 | # Pods/ 42 | # *.xcworkspace 43 | 44 | # Carthage 45 | Carthage/Build/ 46 | 47 | # Accio dependency management 48 | Dependencies/ 49 | .accio/ 50 | 51 | # fastlane 52 | fastlane/report.xml 53 | fastlane/Preview.html 54 | fastlane/screenshots/**/*.png 55 | fastlane/test_output 56 | 57 | # Code Injection 58 | iOSInjectionProject/ 59 | 60 | # MAC 61 | .DS_Store 62 | /.build 63 | /Packages 64 | xcuserdata/ 65 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CombineStorable.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "CombineStorable" 3 | spec.version = "1.0.0" 4 | spec.summary = "Handy Combine extensions on NSObject, including Set." 5 | spec.homepage = "https://github.com/crane-hiromu/CombineStorable" 6 | spec.license = { :type => 'MIT', :file => 'LICENSE' } 7 | spec.author = "h.crane" 8 | spec.platform = :ios, "13.0" 9 | spec.swift_versions = "5.0" 10 | spec.source = { :git => "https://github.com/crane-hiromu/CombineStorable.git", :tag => "#{spec.version}" } 11 | spec.source_files = "Sources/**/*.{swift}" 12 | end -------------------------------------------------------------------------------- /CombineStorable.xcodeproj/CombineStorableTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /CombineStorable.xcodeproj/CombineStorable_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /CombineStorable.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = "1"; 4 | objectVersion = "46"; 5 | objects = { 6 | "CombineStorable::CombineStorable" = { 7 | isa = "PBXNativeTarget"; 8 | buildConfigurationList = "OBJ_23"; 9 | buildPhases = ( 10 | "OBJ_26", 11 | "OBJ_30" 12 | ); 13 | dependencies = ( 14 | ); 15 | name = "CombineStorable"; 16 | productName = "CombineStorable"; 17 | productReference = "CombineStorable::CombineStorable::Product"; 18 | productType = "com.apple.product-type.framework"; 19 | }; 20 | "CombineStorable::CombineStorable::Product" = { 21 | isa = "PBXFileReference"; 22 | path = "CombineStorable.framework"; 23 | sourceTree = "BUILT_PRODUCTS_DIR"; 24 | }; 25 | "CombineStorable::CombineStorablePackageTests::ProductTarget" = { 26 | isa = "PBXAggregateTarget"; 27 | buildConfigurationList = "OBJ_38"; 28 | buildPhases = ( 29 | ); 30 | dependencies = ( 31 | "OBJ_41" 32 | ); 33 | name = "CombineStorablePackageTests"; 34 | productName = "CombineStorablePackageTests"; 35 | }; 36 | "CombineStorable::CombineStorableTests" = { 37 | isa = "PBXNativeTarget"; 38 | buildConfigurationList = "OBJ_43"; 39 | buildPhases = ( 40 | "OBJ_46", 41 | "OBJ_49" 42 | ); 43 | dependencies = ( 44 | "OBJ_51" 45 | ); 46 | name = "CombineStorableTests"; 47 | productName = "CombineStorableTests"; 48 | productReference = "CombineStorable::CombineStorableTests::Product"; 49 | productType = "com.apple.product-type.bundle.unit-test"; 50 | }; 51 | "CombineStorable::CombineStorableTests::Product" = { 52 | isa = "PBXFileReference"; 53 | path = "CombineStorableTests.xctest"; 54 | sourceTree = "BUILT_PRODUCTS_DIR"; 55 | }; 56 | "CombineStorable::SwiftPMPackageDescription" = { 57 | isa = "PBXNativeTarget"; 58 | buildConfigurationList = "OBJ_32"; 59 | buildPhases = ( 60 | "OBJ_35" 61 | ); 62 | dependencies = ( 63 | ); 64 | name = "CombineStorablePackageDescription"; 65 | productName = "CombineStorablePackageDescription"; 66 | productType = "com.apple.product-type.framework"; 67 | }; 68 | "OBJ_1" = { 69 | isa = "PBXProject"; 70 | attributes = { 71 | LastSwiftMigration = "9999"; 72 | LastUpgradeCheck = "9999"; 73 | }; 74 | buildConfigurationList = "OBJ_2"; 75 | compatibilityVersion = "Xcode 3.2"; 76 | developmentRegion = "en"; 77 | hasScannedForEncodings = "0"; 78 | knownRegions = ( 79 | "en" 80 | ); 81 | mainGroup = "OBJ_5"; 82 | productRefGroup = "OBJ_16"; 83 | projectDirPath = "."; 84 | targets = ( 85 | "CombineStorable::CombineStorable", 86 | "CombineStorable::SwiftPMPackageDescription", 87 | "CombineStorable::CombineStorablePackageTests::ProductTarget", 88 | "CombineStorable::CombineStorableTests" 89 | ); 90 | }; 91 | "OBJ_10" = { 92 | isa = "PBXFileReference"; 93 | path = "Publisher+Storable.swift"; 94 | sourceTree = ""; 95 | }; 96 | "OBJ_11" = { 97 | isa = "PBXFileReference"; 98 | path = "Storable.swift"; 99 | sourceTree = ""; 100 | }; 101 | "OBJ_12" = { 102 | isa = "PBXGroup"; 103 | children = ( 104 | "OBJ_13" 105 | ); 106 | name = "Tests"; 107 | path = ""; 108 | sourceTree = "SOURCE_ROOT"; 109 | }; 110 | "OBJ_13" = { 111 | isa = "PBXGroup"; 112 | children = ( 113 | "OBJ_14", 114 | "OBJ_15" 115 | ); 116 | name = "CombineStorableTests"; 117 | path = "Tests/CombineStorableTests"; 118 | sourceTree = "SOURCE_ROOT"; 119 | }; 120 | "OBJ_14" = { 121 | isa = "PBXFileReference"; 122 | path = "StorableTests.swift"; 123 | sourceTree = ""; 124 | }; 125 | "OBJ_15" = { 126 | isa = "PBXFileReference"; 127 | path = "XCTestManifests.swift"; 128 | sourceTree = ""; 129 | }; 130 | "OBJ_16" = { 131 | isa = "PBXGroup"; 132 | children = ( 133 | "CombineStorable::CombineStorableTests::Product", 134 | "CombineStorable::CombineStorable::Product" 135 | ); 136 | name = "Products"; 137 | path = ""; 138 | sourceTree = "BUILT_PRODUCTS_DIR"; 139 | }; 140 | "OBJ_19" = { 141 | isa = "PBXFileReference"; 142 | path = "Carthage"; 143 | sourceTree = "SOURCE_ROOT"; 144 | }; 145 | "OBJ_2" = { 146 | isa = "XCConfigurationList"; 147 | buildConfigurations = ( 148 | "OBJ_3", 149 | "OBJ_4" 150 | ); 151 | defaultConfigurationIsVisible = "0"; 152 | defaultConfigurationName = "Release"; 153 | }; 154 | "OBJ_20" = { 155 | isa = "PBXFileReference"; 156 | path = "LICENSE"; 157 | sourceTree = ""; 158 | }; 159 | "OBJ_21" = { 160 | isa = "PBXFileReference"; 161 | path = "README.md"; 162 | sourceTree = ""; 163 | }; 164 | "OBJ_23" = { 165 | isa = "XCConfigurationList"; 166 | buildConfigurations = ( 167 | "OBJ_24", 168 | "OBJ_25" 169 | ); 170 | defaultConfigurationIsVisible = "0"; 171 | defaultConfigurationName = "Release"; 172 | }; 173 | "OBJ_24" = { 174 | isa = "XCBuildConfiguration"; 175 | buildSettings = { 176 | ENABLE_TESTABILITY = "YES"; 177 | FRAMEWORK_SEARCH_PATHS = ( 178 | "$(inherited)", 179 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 180 | ); 181 | HEADER_SEARCH_PATHS = ( 182 | "$(inherited)" 183 | ); 184 | INFOPLIST_FILE = "CombineStorable.xcodeproj/CombineStorable_Info.plist"; 185 | IPHONEOS_DEPLOYMENT_TARGET = "8.0"; 186 | LD_RUNPATH_SEARCH_PATHS = ( 187 | "$(inherited)", 188 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx" 189 | ); 190 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 191 | OTHER_CFLAGS = ( 192 | "$(inherited)" 193 | ); 194 | OTHER_LDFLAGS = ( 195 | "$(inherited)" 196 | ); 197 | OTHER_SWIFT_FLAGS = ( 198 | "$(inherited)" 199 | ); 200 | PRODUCT_BUNDLE_IDENTIFIER = "CombineStorable"; 201 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 202 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 203 | SKIP_INSTALL = "YES"; 204 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 205 | "$(inherited)" 206 | ); 207 | SWIFT_VERSION = "5.0"; 208 | TARGET_NAME = "CombineStorable"; 209 | TVOS_DEPLOYMENT_TARGET = "9.0"; 210 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 211 | }; 212 | name = "Debug"; 213 | }; 214 | "OBJ_25" = { 215 | isa = "XCBuildConfiguration"; 216 | buildSettings = { 217 | ENABLE_TESTABILITY = "YES"; 218 | FRAMEWORK_SEARCH_PATHS = ( 219 | "$(inherited)", 220 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 221 | ); 222 | HEADER_SEARCH_PATHS = ( 223 | "$(inherited)" 224 | ); 225 | INFOPLIST_FILE = "CombineStorable.xcodeproj/CombineStorable_Info.plist"; 226 | IPHONEOS_DEPLOYMENT_TARGET = "8.0"; 227 | LD_RUNPATH_SEARCH_PATHS = ( 228 | "$(inherited)", 229 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx" 230 | ); 231 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 232 | OTHER_CFLAGS = ( 233 | "$(inherited)" 234 | ); 235 | OTHER_LDFLAGS = ( 236 | "$(inherited)" 237 | ); 238 | OTHER_SWIFT_FLAGS = ( 239 | "$(inherited)" 240 | ); 241 | PRODUCT_BUNDLE_IDENTIFIER = "CombineStorable"; 242 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 243 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 244 | SKIP_INSTALL = "YES"; 245 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 246 | "$(inherited)" 247 | ); 248 | SWIFT_VERSION = "5.0"; 249 | TARGET_NAME = "CombineStorable"; 250 | TVOS_DEPLOYMENT_TARGET = "9.0"; 251 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 252 | }; 253 | name = "Release"; 254 | }; 255 | "OBJ_26" = { 256 | isa = "PBXSourcesBuildPhase"; 257 | files = ( 258 | "OBJ_27", 259 | "OBJ_28", 260 | "OBJ_29" 261 | ); 262 | }; 263 | "OBJ_27" = { 264 | isa = "PBXBuildFile"; 265 | fileRef = "OBJ_9"; 266 | }; 267 | "OBJ_28" = { 268 | isa = "PBXBuildFile"; 269 | fileRef = "OBJ_10"; 270 | }; 271 | "OBJ_29" = { 272 | isa = "PBXBuildFile"; 273 | fileRef = "OBJ_11"; 274 | }; 275 | "OBJ_3" = { 276 | isa = "XCBuildConfiguration"; 277 | buildSettings = { 278 | CLANG_ENABLE_OBJC_ARC = "YES"; 279 | COMBINE_HIDPI_IMAGES = "YES"; 280 | COPY_PHASE_STRIP = "NO"; 281 | DEBUG_INFORMATION_FORMAT = "dwarf"; 282 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 283 | ENABLE_NS_ASSERTIONS = "YES"; 284 | GCC_OPTIMIZATION_LEVEL = "0"; 285 | GCC_PREPROCESSOR_DEFINITIONS = ( 286 | "$(inherited)", 287 | "SWIFT_PACKAGE=1", 288 | "DEBUG=1" 289 | ); 290 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 291 | ONLY_ACTIVE_ARCH = "YES"; 292 | OTHER_SWIFT_FLAGS = ( 293 | "$(inherited)", 294 | "-DXcode" 295 | ); 296 | PRODUCT_NAME = "$(TARGET_NAME)"; 297 | SDKROOT = "macosx"; 298 | SUPPORTED_PLATFORMS = ( 299 | "macosx", 300 | "iphoneos", 301 | "iphonesimulator", 302 | "appletvos", 303 | "appletvsimulator", 304 | "watchos", 305 | "watchsimulator" 306 | ); 307 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 308 | "$(inherited)", 309 | "SWIFT_PACKAGE", 310 | "DEBUG" 311 | ); 312 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 313 | USE_HEADERMAP = "NO"; 314 | }; 315 | name = "Debug"; 316 | }; 317 | "OBJ_30" = { 318 | isa = "PBXFrameworksBuildPhase"; 319 | files = ( 320 | ); 321 | }; 322 | "OBJ_32" = { 323 | isa = "XCConfigurationList"; 324 | buildConfigurations = ( 325 | "OBJ_33", 326 | "OBJ_34" 327 | ); 328 | defaultConfigurationIsVisible = "0"; 329 | defaultConfigurationName = "Release"; 330 | }; 331 | "OBJ_33" = { 332 | isa = "XCBuildConfiguration"; 333 | buildSettings = { 334 | LD = "/usr/bin/true"; 335 | OTHER_SWIFT_FLAGS = ( 336 | "-swift-version", 337 | "5", 338 | "-I", 339 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2", 340 | "-target", 341 | "x86_64-apple-macosx10.10", 342 | "-sdk", 343 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk", 344 | "-package-description-version", 345 | "5.2.0" 346 | ); 347 | SWIFT_VERSION = "5.0"; 348 | }; 349 | name = "Debug"; 350 | }; 351 | "OBJ_34" = { 352 | isa = "XCBuildConfiguration"; 353 | buildSettings = { 354 | LD = "/usr/bin/true"; 355 | OTHER_SWIFT_FLAGS = ( 356 | "-swift-version", 357 | "5", 358 | "-I", 359 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2", 360 | "-target", 361 | "x86_64-apple-macosx10.10", 362 | "-sdk", 363 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk", 364 | "-package-description-version", 365 | "5.2.0" 366 | ); 367 | SWIFT_VERSION = "5.0"; 368 | }; 369 | name = "Release"; 370 | }; 371 | "OBJ_35" = { 372 | isa = "PBXSourcesBuildPhase"; 373 | files = ( 374 | "OBJ_36" 375 | ); 376 | }; 377 | "OBJ_36" = { 378 | isa = "PBXBuildFile"; 379 | fileRef = "OBJ_6"; 380 | }; 381 | "OBJ_38" = { 382 | isa = "XCConfigurationList"; 383 | buildConfigurations = ( 384 | "OBJ_39", 385 | "OBJ_40" 386 | ); 387 | defaultConfigurationIsVisible = "0"; 388 | defaultConfigurationName = "Release"; 389 | }; 390 | "OBJ_39" = { 391 | isa = "XCBuildConfiguration"; 392 | buildSettings = { 393 | }; 394 | name = "Debug"; 395 | }; 396 | "OBJ_4" = { 397 | isa = "XCBuildConfiguration"; 398 | buildSettings = { 399 | CLANG_ENABLE_OBJC_ARC = "YES"; 400 | COMBINE_HIDPI_IMAGES = "YES"; 401 | COPY_PHASE_STRIP = "YES"; 402 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 403 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 404 | GCC_OPTIMIZATION_LEVEL = "s"; 405 | GCC_PREPROCESSOR_DEFINITIONS = ( 406 | "$(inherited)", 407 | "SWIFT_PACKAGE=1" 408 | ); 409 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 410 | OTHER_SWIFT_FLAGS = ( 411 | "$(inherited)", 412 | "-DXcode" 413 | ); 414 | PRODUCT_NAME = "$(TARGET_NAME)"; 415 | SDKROOT = "macosx"; 416 | SUPPORTED_PLATFORMS = ( 417 | "macosx", 418 | "iphoneos", 419 | "iphonesimulator", 420 | "appletvos", 421 | "appletvsimulator", 422 | "watchos", 423 | "watchsimulator" 424 | ); 425 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 426 | "$(inherited)", 427 | "SWIFT_PACKAGE" 428 | ); 429 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 430 | USE_HEADERMAP = "NO"; 431 | }; 432 | name = "Release"; 433 | }; 434 | "OBJ_40" = { 435 | isa = "XCBuildConfiguration"; 436 | buildSettings = { 437 | }; 438 | name = "Release"; 439 | }; 440 | "OBJ_41" = { 441 | isa = "PBXTargetDependency"; 442 | target = "CombineStorable::CombineStorableTests"; 443 | }; 444 | "OBJ_43" = { 445 | isa = "XCConfigurationList"; 446 | buildConfigurations = ( 447 | "OBJ_44", 448 | "OBJ_45" 449 | ); 450 | defaultConfigurationIsVisible = "0"; 451 | defaultConfigurationName = "Release"; 452 | }; 453 | "OBJ_44" = { 454 | isa = "XCBuildConfiguration"; 455 | buildSettings = { 456 | CLANG_ENABLE_MODULES = "YES"; 457 | EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES"; 458 | FRAMEWORK_SEARCH_PATHS = ( 459 | "$(inherited)", 460 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 461 | ); 462 | HEADER_SEARCH_PATHS = ( 463 | "$(inherited)" 464 | ); 465 | INFOPLIST_FILE = "CombineStorable.xcodeproj/CombineStorableTests_Info.plist"; 466 | IPHONEOS_DEPLOYMENT_TARGET = "8.0"; 467 | LD_RUNPATH_SEARCH_PATHS = ( 468 | "$(inherited)", 469 | "@loader_path/../Frameworks", 470 | "@loader_path/Frameworks" 471 | ); 472 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 473 | OTHER_CFLAGS = ( 474 | "$(inherited)" 475 | ); 476 | OTHER_LDFLAGS = ( 477 | "$(inherited)" 478 | ); 479 | OTHER_SWIFT_FLAGS = ( 480 | "$(inherited)" 481 | ); 482 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 483 | "$(inherited)" 484 | ); 485 | SWIFT_VERSION = "5.0"; 486 | TARGET_NAME = "CombineStorableTests"; 487 | TVOS_DEPLOYMENT_TARGET = "9.0"; 488 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 489 | }; 490 | name = "Debug"; 491 | }; 492 | "OBJ_45" = { 493 | isa = "XCBuildConfiguration"; 494 | buildSettings = { 495 | CLANG_ENABLE_MODULES = "YES"; 496 | EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES"; 497 | FRAMEWORK_SEARCH_PATHS = ( 498 | "$(inherited)", 499 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 500 | ); 501 | HEADER_SEARCH_PATHS = ( 502 | "$(inherited)" 503 | ); 504 | INFOPLIST_FILE = "CombineStorable.xcodeproj/CombineStorableTests_Info.plist"; 505 | IPHONEOS_DEPLOYMENT_TARGET = "8.0"; 506 | LD_RUNPATH_SEARCH_PATHS = ( 507 | "$(inherited)", 508 | "@loader_path/../Frameworks", 509 | "@loader_path/Frameworks" 510 | ); 511 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 512 | OTHER_CFLAGS = ( 513 | "$(inherited)" 514 | ); 515 | OTHER_LDFLAGS = ( 516 | "$(inherited)" 517 | ); 518 | OTHER_SWIFT_FLAGS = ( 519 | "$(inherited)" 520 | ); 521 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 522 | "$(inherited)" 523 | ); 524 | SWIFT_VERSION = "5.0"; 525 | TARGET_NAME = "CombineStorableTests"; 526 | TVOS_DEPLOYMENT_TARGET = "9.0"; 527 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 528 | }; 529 | name = "Release"; 530 | }; 531 | "OBJ_46" = { 532 | isa = "PBXSourcesBuildPhase"; 533 | files = ( 534 | "OBJ_47", 535 | "OBJ_48" 536 | ); 537 | }; 538 | "OBJ_47" = { 539 | isa = "PBXBuildFile"; 540 | fileRef = "OBJ_14"; 541 | }; 542 | "OBJ_48" = { 543 | isa = "PBXBuildFile"; 544 | fileRef = "OBJ_15"; 545 | }; 546 | "OBJ_49" = { 547 | isa = "PBXFrameworksBuildPhase"; 548 | files = ( 549 | "OBJ_50" 550 | ); 551 | }; 552 | "OBJ_5" = { 553 | isa = "PBXGroup"; 554 | children = ( 555 | "OBJ_6", 556 | "OBJ_7", 557 | "OBJ_12", 558 | "OBJ_16", 559 | "OBJ_19", 560 | "OBJ_20", 561 | "OBJ_21" 562 | ); 563 | path = ""; 564 | sourceTree = ""; 565 | }; 566 | "OBJ_50" = { 567 | isa = "PBXBuildFile"; 568 | fileRef = "CombineStorable::CombineStorable::Product"; 569 | }; 570 | "OBJ_51" = { 571 | isa = "PBXTargetDependency"; 572 | target = "CombineStorable::CombineStorable"; 573 | }; 574 | "OBJ_6" = { 575 | isa = "PBXFileReference"; 576 | explicitFileType = "sourcecode.swift"; 577 | path = "Package.swift"; 578 | sourceTree = ""; 579 | }; 580 | "OBJ_7" = { 581 | isa = "PBXGroup"; 582 | children = ( 583 | "OBJ_8" 584 | ); 585 | name = "Sources"; 586 | path = ""; 587 | sourceTree = "SOURCE_ROOT"; 588 | }; 589 | "OBJ_8" = { 590 | isa = "PBXGroup"; 591 | children = ( 592 | "OBJ_9", 593 | "OBJ_10", 594 | "OBJ_11" 595 | ); 596 | name = "CombineStorable"; 597 | path = "Sources/CombineStorable"; 598 | sourceTree = "SOURCE_ROOT"; 599 | }; 600 | "OBJ_9" = { 601 | isa = "PBXFileReference"; 602 | path = "NSObject+Storable.swift"; 603 | sourceTree = ""; 604 | }; 605 | }; 606 | rootObject = "OBJ_1"; 607 | } 608 | -------------------------------------------------------------------------------- /CombineStorable.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /CombineStorable.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CombineStorable.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | -------------------------------------------------------------------------------- /CombineStorable.xcodeproj/xcshareddata/xcschemes/CombineStorable-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 hcrane 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "CombineStorable", 8 | products: [ 9 | .library( 10 | name: "CombineStorable", 11 | targets: ["CombineStorable"]), 12 | ], 13 | dependencies: [], 14 | targets: [ 15 | .target( 16 | name: "CombineStorable", 17 | dependencies: []), 18 | .testTarget( 19 | name: "CombineStorableTests", 20 | dependencies: ["CombineStorable"]), 21 | ] 22 | ) 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Storable 2 | 3 | image 4 | 5 | 6 | ## Description 7 | 8 | If you're using Combine, you've probably encountered the following code more than a few times. 9 | 10 | ```swift 11 | class Object: NSObject { 12 | var cancellables = Set() 13 | 14 | ... 15 | } 16 | ``` 17 | 18 | Instead of adding a new property to every object, use this library to add it for you, to any subclass of NSObject. 19 | 20 | ### Example1 21 | 22 | ```swift 23 | final class Object: NSObject, Storable { // import Storable 24 | 25 | // You don't have to write this. 26 | // private var cancellables = Set() 27 | 28 | func exec() { 29 | Just(0) 30 | .sink(receiveCompletion: { _ in 31 | // do something 32 | }, receiveValue: { _ in 33 | // do something 34 | }) 35 | .store(in: &self.cancellables) // read Set in yourself. 36 | } 37 | } 38 | ``` 39 | 40 | You can read ``Set`` without member variable. It'll work just like a property: when the instance is deinit'd, the ``Set`` gets disposed. It's also a readwrite property, so you can use your own, too. 41 | 42 |
43 | 44 | Furthermore, ``.store(in: &self.cancellables)`` can be omitted. 45 | 46 | ### Example2 47 | 48 | 49 | ```swift 50 | final class Object: NSObject, Storable { 51 | 52 | func exec() { 53 | Just(0) 54 | .sink(self, receiveCompletion: { _ in // input self 55 | // do something 56 | }, receiveValue: { _ in 57 | // do something 58 | }) 59 | } 60 | } 61 | ``` 62 | 63 | For a detailed explanation, please read [here](https://qiita.com/hcrane/items/c9ae8427a5c6d8fc08ee). 64 | 65 | 66 | ## Installing 67 | 68 | ### CocoaPods 69 | 70 | Add the following line to your Podfile: 71 | 72 | ``` 73 | pod 'CombineStorable', :git => 'git@github.com:crane-hiromu/CombineStorable.git', :branch => 'main' 74 | ``` 75 | 76 | ### Carthage 77 | 78 | Add the following to your Cartfile: 79 | 80 | ``` 81 | github "crane-hiromu/CombineStorable" 82 | ``` 83 | 84 | #### Supplement 85 | 86 | If you fail to install, reset caches. (``rm -rf ~/Library/Caches/``) 87 | 88 | 89 | 90 | ### Swift Package Manager 91 | 92 | Add the following dependency to your Package.swift file: 93 | 94 | ``` 95 | .package(url: "https://github.com/crane-hiromu/CombineStorable.git", from: "1.1.0") 96 | ``` 97 | 98 | ### License 99 | 100 | MIT, of course ;-) See the LICENSE file. 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /Sources/CombineStorable/NSObject+Storable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+Storable.swift 3 | // 4 | // 5 | // Created by h.crane on 2021/04/17. 6 | // 7 | 8 | #if canImport(Combine) 9 | 10 | import Foundation 11 | import Combine 12 | 13 | // MARK: - Private Property 14 | private var cancellableContext: UInt8 = 0 15 | 16 | // MARK: - Private Wrapper Class 17 | @available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) 18 | private class CancellableWrapper { 19 | let cancellables: Set 20 | 21 | init(cancellables: Set) { 22 | self.cancellables = cancellables 23 | } 24 | } 25 | 26 | // MARK: - NSObject Extension 27 | @available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) 28 | public extension NSObject { 29 | 30 | var cancellables: Set { 31 | get { 32 | if let wrapper = objc_getAssociatedObject(self, &cancellableContext) as? CancellableWrapper { 33 | return wrapper.cancellables 34 | } 35 | let cancellables = Set() 36 | self.cancellables = cancellables 37 | return cancellables 38 | } 39 | set { 40 | let wrapper = CancellableWrapper(cancellables: newValue) 41 | objc_setAssociatedObject(self, &cancellableContext, wrapper, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 42 | } 43 | } 44 | } 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /Sources/CombineStorable/Publisher+Storable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Publisher+Storable.swift 3 | // 4 | // 5 | // Created by h.crane on 2021/04/17. 6 | // 7 | 8 | #if canImport(Combine) 9 | 10 | import Foundation 11 | import Combine 12 | 13 | // MARK: - Publisher Extension 14 | @available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) 15 | public extension Publisher { 16 | 17 | func sink( 18 | _ target: T, 19 | receiveCompletion: @escaping ((Subscribers.Completion) -> Void), 20 | receiveValue: @escaping ((Self.Output) -> Void) 21 | ) { 22 | sink(receiveCompletion: receiveCompletion, receiveValue: receiveValue) 23 | .store(in: &target.cancellables) 24 | } 25 | } 26 | 27 | // MARK: - Publisher Extension (Never) 28 | @available(iOS 13.0, OSX 10.15, *) 29 | public extension Publisher where Self.Failure == Never { 30 | 31 | func sink( 32 | _ target: T, 33 | receiveValue: @escaping ((Self.Output) -> Void) 34 | ) { 35 | sink(receiveValue: receiveValue) 36 | .store(in: &target.cancellables) 37 | } 38 | 39 | func assign( 40 | to keyPath: ReferenceWritableKeyPath, 41 | on object: O 42 | ) { 43 | assign(to: keyPath, on: object) 44 | .store(in: &object.cancellables) 45 | } 46 | } 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /Sources/CombineStorable/Storable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Storable.swift 3 | // 4 | // 5 | // Created by h.crane on 2021/04/17. 6 | // 7 | 8 | #if canImport(Combine) 9 | 10 | import Foundation 11 | import Combine 12 | 13 | // MARK: - Protocol 14 | @available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) 15 | public protocol Storable: NSObject { 16 | var cancellables: Set { get set } 17 | } 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /Tests/CombineStorableTests/StorableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PublisherTests.swift 3 | // 4 | // 5 | // Created by h.crane on 2021/04/17. 6 | // 7 | 8 | #if canImport(Combine) 9 | 10 | import XCTest 11 | import Combine 12 | @testable import CombineStorable 13 | 14 | // MARK: - XCTestCase 15 | @available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) 16 | final class StorableTests: XCTestCase { 17 | 18 | // MARK: Class 19 | 20 | final class StubObject: NSObject, Storable { 21 | 22 | // MARK: Store Method 23 | 24 | // case1 25 | func sinkWithStore( 26 | value: String, 27 | receiveCompletion: @escaping (String) -> Void, 28 | receiveValue: @escaping (String) -> Void 29 | ) { 30 | Just(value) 31 | .sink(receiveCompletion: { result in 32 | switch result { 33 | case .finished: 34 | receiveCompletion("finished") 35 | case .failure(_): 36 | receiveCompletion("error") 37 | } 38 | }, receiveValue: { value in 39 | receiveValue(value) 40 | }) 41 | .store(in: &self.cancellables) 42 | } 43 | 44 | // case2 45 | func sinkWithStore( 46 | value: String, 47 | receiveValue: @escaping (String) -> Void 48 | ) { 49 | Just(value) 50 | .sink(receiveValue: { value in 51 | receiveValue(value) 52 | }) 53 | .store(in: &self.cancellables) 54 | } 55 | 56 | // case3 57 | var valueWithStore = 0 58 | 59 | func assignWithStore(value: Int) { 60 | Just(value) 61 | .assign(to: \.valueWithStore, on: self) 62 | .store(in: &self.cancellables) 63 | } 64 | 65 | // MARK: No store Method 66 | 67 | // case1 68 | func sinkWithNoStore( 69 | value: String, 70 | receiveCompletion: @escaping (String) -> Void, 71 | receiveValue: @escaping (String) -> Void 72 | ) { 73 | Just(value) 74 | .sink(self, receiveCompletion: { result in 75 | switch result { 76 | case .finished: 77 | receiveCompletion("finished") 78 | case .failure(_): 79 | receiveCompletion("error") 80 | } 81 | }, receiveValue: { value in 82 | receiveValue(value) 83 | }) 84 | } 85 | 86 | // case2 87 | func sinkWithNoStore( 88 | value: String, 89 | receiveValue: @escaping (String) -> Void 90 | ) { 91 | Just(value) 92 | .sink(self, receiveValue: { value in 93 | receiveValue(value) 94 | }) 95 | } 96 | 97 | // case3 98 | var valueWithNoStore = 0 99 | 100 | func assignWithNoStore(value: Int) { 101 | Just(value) 102 | .assign(to: \.valueWithNoStore, on: self) 103 | } 104 | } 105 | 106 | // MARK: Prorperty 107 | 108 | static var allTests = [ 109 | ("testSinkWithCompletionAndValueWithStore", testSinkWithCompletionAndValueWithStore), 110 | ("testSinkWithValueWithStore", testSinkWithValueWithStore), 111 | ("testAssignWithStore", testAssignWithStore), 112 | ("testSinkWithCompletionAndValueWithNoStore", testSinkWithCompletionAndValueWithNoStore), 113 | ("testSinkWithValueWithNoStore", testSinkWithValueWithNoStore), 114 | ("testAssignWithNoStore", testAssignWithNoStore) 115 | ] 116 | 117 | private let stubObject = StubObject() 118 | 119 | // MARK: Test 120 | 121 | func testSinkWithCompletionAndValueWithStore() { 122 | stubObject.sinkWithStore(value: "\(#function)", receiveCompletion: { value in 123 | XCTAssertEqual(value, "finished") 124 | }, receiveValue: { value in 125 | XCTAssertEqual(value, "\(#function)") 126 | 127 | }) 128 | } 129 | 130 | func testSinkWithValueWithStore() { 131 | stubObject.sinkWithStore(value: "\(#function)", receiveValue: { value in 132 | XCTAssertEqual(value, "\(#function)") 133 | }) 134 | } 135 | 136 | func testAssignWithStore() { 137 | stubObject.assignWithStore(value: 999) 138 | XCTAssertEqual(stubObject.valueWithStore, 999) 139 | } 140 | 141 | func testSinkWithCompletionAndValueWithNoStore() { 142 | stubObject.sinkWithNoStore(value: "\(#function)", receiveCompletion: { value in 143 | XCTAssertEqual(value, "finished") 144 | }, receiveValue: { value in 145 | XCTAssertEqual(value, "\(#function)") 146 | 147 | }) 148 | } 149 | 150 | func testSinkWithValueWithNoStore() { 151 | stubObject.sinkWithNoStore(value: "\(#function)", receiveValue: { value in 152 | XCTAssertEqual(value, "\(#function)") 153 | }) 154 | } 155 | 156 | func testAssignWithNoStore() { 157 | stubObject.assignWithNoStore(value: 9999) 158 | XCTAssertEqual(stubObject.valueWithNoStore, 9999) 159 | } 160 | } 161 | 162 | #endif 163 | -------------------------------------------------------------------------------- /Tests/CombineStorableTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(StorableTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import CombineStorableTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += CombineStorableTests.allTests() 7 | XCTMain(tests) 8 | --------------------------------------------------------------------------------