diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj new file mode 100644 index 00000000..9c18174e --- /dev/null +++ b/Example/Example.xcodeproj/project.pbxproj @@ -0,0 +1,540 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + C809A1361AD009FB001AA8FE /* CollectionViewImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C809A1351AD009FB001AA8FE /* CollectionViewImageCell.swift */; }; + C81175AB1ACEEBAA001521F4 /* Wireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81175AA1ACEEBAA001521F4 /* Wireframe.swift */; }; + C81553B21A98A94700C63152 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81553B11A98A94700C63152 /* AppDelegate.swift */; }; + C81553B51A98A94700C63152 /* Example.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C81553B31A98A94700C63152 /* Example.xcdatamodeld */; }; + C81553BC1A98A94700C63152 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C81553BB1A98A94700C63152 /* Images.xcassets */; }; + C81553CB1A98A94700C63152 /* ExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81553CA1A98A94700C63152 /* ExampleTests.swift */; }; + C8AD4A741AD0253200C12FBF /* Rx.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C81553FA1A98AB7800C63152 /* Rx.framework */; }; + C8AD4A751AD0253300C12FBF /* Rx.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C81553FA1A98AB7800C63152 /* Rx.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C8AD4A771AD0253600C12FBF /* RxCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8AD4A761AD0253600C12FBF /* RxCocoa.framework */; }; + C8AD4A781AD0253600C12FBF /* RxCocoa.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C8AD4A761AD0253600C12FBF /* RxCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C8D7B92D1ACFEAC900342508 /* SearchResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D7B9271ACFEAC900342508 /* SearchResultViewModel.swift */; }; + C8D7B92E1ACFEAC900342508 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D7B9281ACFEAC900342508 /* SearchViewModel.swift */; }; + C8D7B9341ACFEAEA00342508 /* WikipediaSearchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D7B9301ACFEAEA00342508 /* WikipediaSearchCell.swift */; }; + C8D7B9351ACFEAEA00342508 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = C8D7B9311ACFEAEA00342508 /* LaunchScreen.xib */; }; + C8D7B9361ACFEAEA00342508 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C8D7B9321ACFEAEA00342508 /* Main.storyboard */; }; + C8D7B9371ACFEAEA00342508 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D7B9331ACFEAEA00342508 /* ViewController.swift */; }; + C8DDCFD31AC6F17500414017 /* HtmlParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DDCFD21AC6F17500414017 /* HtmlParsing.swift */; }; + C8DDCFD41AC6F46200414017 /* HtmlParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DDCFD21AC6F17500414017 /* HtmlParsing.swift */; }; + C8DDCFE41AC7400C00414017 /* Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DDCFE31AC7400C00414017 /* Example.swift */; }; + C8DDCFE61AC757AE00414017 /* ImageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DDCFE51AC757AE00414017 /* ImageService.swift */; }; + C8DDCFEB1AC757E700414017 /* WikipediaAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DDCFE81AC757E700414017 /* WikipediaAPI.swift */; }; + C8DDCFEC1AC757E700414017 /* WikipediaPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DDCFE91AC757E700414017 /* WikipediaPage.swift */; }; + C8DDCFED1AC757E700414017 /* WikipediaSearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DDCFEA1AC757E700414017 /* WikipediaSearchResult.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + C81553C51A98A94700C63152 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C81553A41A98A94700C63152 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C81553AB1A98A94700C63152; + remoteInfo = Example; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + C8AD4A6F1AD016DB00C12FBF /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + C8AD4A781AD0253600C12FBF /* RxCocoa.framework in Embed Frameworks */, + C8AD4A751AD0253300C12FBF /* Rx.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + C809A1351AD009FB001AA8FE /* CollectionViewImageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewImageCell.swift; sourceTree = ""; }; + C81175AA1ACEEBAA001521F4 /* Wireframe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Wireframe.swift; sourceTree = ""; }; + C81553AC1A98A94700C63152 /* RxExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RxExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + C81553B01A98A94700C63152 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C81553B11A98A94700C63152 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + C81553B41A98A94700C63152 /* Example.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Example.xcdatamodel; sourceTree = ""; }; + C81553BB1A98A94700C63152 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + C81553C41A98A94700C63152 /* ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + C81553C91A98A94700C63152 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C81553CA1A98A94700C63152 /* ExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleTests.swift; sourceTree = ""; }; + C81553FA1A98AB7800C63152 /* Rx.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Rx.framework; path = "../../../Library/Developer/Xcode/DerivedData/Rx-cfkyozdvlaegqibzixjokeysigeo/Build/Products/Debug-iphoneos/Rx.framework"; sourceTree = ""; }; + C8AD4A761AD0253600C12FBF /* RxCocoa.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; name = RxCocoa.framework; path = "/Users/kzaher/Library/Developer/Xcode/DerivedData/Rx-cfkyozdvlaegqibzixjokeysigeo/Build/Products/Debug-iphoneos/RxCocoa.framework"; sourceTree = ""; }; + C8D7B9271ACFEAC900342508 /* SearchResultViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchResultViewModel.swift; sourceTree = ""; }; + C8D7B9281ACFEAC900342508 /* SearchViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = ""; }; + C8D7B9301ACFEAEA00342508 /* WikipediaSearchCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WikipediaSearchCell.swift; sourceTree = ""; }; + C8D7B9311ACFEAEA00342508 /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; + C8D7B9321ACFEAEA00342508 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; + C8D7B9331ACFEAEA00342508 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + C8DDCFD21AC6F17500414017 /* HtmlParsing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HtmlParsing.swift; sourceTree = ""; }; + C8DDCFE31AC7400C00414017 /* Example.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Example.swift; sourceTree = ""; }; + C8DDCFE51AC757AE00414017 /* ImageService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageService.swift; sourceTree = ""; }; + C8DDCFE81AC757E700414017 /* WikipediaAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WikipediaAPI.swift; sourceTree = ""; }; + C8DDCFE91AC757E700414017 /* WikipediaPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WikipediaPage.swift; sourceTree = ""; }; + C8DDCFEA1AC757E700414017 /* WikipediaSearchResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WikipediaSearchResult.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + C81553A91A98A94700C63152 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C8AD4A771AD0253600C12FBF /* RxCocoa.framework in Frameworks */, + C8AD4A741AD0253200C12FBF /* Rx.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C81553C11A98A94700C63152 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + C81553A31A98A94700C63152 = { + isa = PBXGroup; + children = ( + C8AD4A761AD0253600C12FBF /* RxCocoa.framework */, + C81553AE1A98A94700C63152 /* Example */, + C81553C71A98A94700C63152 /* ExampleTests */, + C81553AD1A98A94700C63152 /* Products */, + ); + sourceTree = ""; + }; + C81553AD1A98A94700C63152 /* Products */ = { + isa = PBXGroup; + children = ( + C81553AC1A98A94700C63152 /* RxExample.app */, + C81553C41A98A94700C63152 /* ExampleTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + C81553AE1A98A94700C63152 /* Example */ = { + isa = PBXGroup; + children = ( + C8D7B92F1ACFEAEA00342508 /* Interface */, + C8D7B9261ACFEAC900342508 /* ViewModels */, + C81C49E31AC4114E001D7A5E /* Services */, + C81553B11A98A94700C63152 /* AppDelegate.swift */, + C81553BB1A98A94700C63152 /* Images.xcassets */, + C81553B31A98A94700C63152 /* Example.xcdatamodeld */, + C81553AF1A98A94700C63152 /* Supporting Files */, + C8DDCFE31AC7400C00414017 /* Example.swift */, + C81175AA1ACEEBAA001521F4 /* Wireframe.swift */, + ); + path = Example; + sourceTree = ""; + }; + C81553AF1A98A94700C63152 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + C81553B01A98A94700C63152 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + C81553C71A98A94700C63152 /* ExampleTests */ = { + isa = PBXGroup; + children = ( + C81553CA1A98A94700C63152 /* ExampleTests.swift */, + C81553C81A98A94700C63152 /* Supporting Files */, + ); + path = ExampleTests; + sourceTree = ""; + }; + C81553C81A98A94700C63152 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + C81553C91A98A94700C63152 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + C81C49E31AC4114E001D7A5E /* Services */ = { + isa = PBXGroup; + children = ( + C8DDCFE71AC757E700414017 /* WikipediaAPI */, + C8DDCFD21AC6F17500414017 /* HtmlParsing.swift */, + C8DDCFE51AC757AE00414017 /* ImageService.swift */, + ); + path = Services; + sourceTree = ""; + }; + C8D7B9261ACFEAC900342508 /* ViewModels */ = { + isa = PBXGroup; + children = ( + C8D7B9271ACFEAC900342508 /* SearchResultViewModel.swift */, + C8D7B9281ACFEAC900342508 /* SearchViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + C8D7B92F1ACFEAEA00342508 /* Interface */ = { + isa = PBXGroup; + children = ( + C8D7B9301ACFEAEA00342508 /* WikipediaSearchCell.swift */, + C809A1351AD009FB001AA8FE /* CollectionViewImageCell.swift */, + C8D7B9311ACFEAEA00342508 /* LaunchScreen.xib */, + C8D7B9321ACFEAEA00342508 /* Main.storyboard */, + C8D7B9331ACFEAEA00342508 /* ViewController.swift */, + ); + path = Interface; + sourceTree = ""; + }; + C8DDCFE71AC757E700414017 /* WikipediaAPI */ = { + isa = PBXGroup; + children = ( + C8DDCFE81AC757E700414017 /* WikipediaAPI.swift */, + C8DDCFE91AC757E700414017 /* WikipediaPage.swift */, + C8DDCFEA1AC757E700414017 /* WikipediaSearchResult.swift */, + ); + path = WikipediaAPI; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + C81553AB1A98A94700C63152 /* RxExample */ = { + isa = PBXNativeTarget; + buildConfigurationList = C81553CE1A98A94700C63152 /* Build configuration list for PBXNativeTarget "RxExample" */; + buildPhases = ( + C81553A81A98A94700C63152 /* Sources */, + C81553A91A98A94700C63152 /* Frameworks */, + C81553AA1A98A94700C63152 /* Resources */, + C8AD4A6F1AD016DB00C12FBF /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RxExample; + productName = Example; + productReference = C81553AC1A98A94700C63152 /* RxExample.app */; + productType = "com.apple.product-type.application"; + }; + C81553C31A98A94700C63152 /* ExampleTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = C81553D11A98A94700C63152 /* Build configuration list for PBXNativeTarget "ExampleTests" */; + buildPhases = ( + C81553C01A98A94700C63152 /* Sources */, + C81553C11A98A94700C63152 /* Frameworks */, + C81553C21A98A94700C63152 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + C81553C61A98A94700C63152 /* PBXTargetDependency */, + ); + name = ExampleTests; + productName = ExampleTests; + productReference = C81553C41A98A94700C63152 /* ExampleTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + C81553A41A98A94700C63152 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = "Krunoslav Zaher"; + TargetAttributes = { + C81553AB1A98A94700C63152 = { + CreatedOnToolsVersion = 6.1.1; + }; + C81553C31A98A94700C63152 = { + CreatedOnToolsVersion = 6.1.1; + TestTargetID = C81553AB1A98A94700C63152; + }; + }; + }; + buildConfigurationList = C81553A71A98A94700C63152 /* Build configuration list for PBXProject "Example" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = C81553A31A98A94700C63152; + productRefGroup = C81553AD1A98A94700C63152 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C81553AB1A98A94700C63152 /* RxExample */, + C81553C31A98A94700C63152 /* ExampleTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + C81553AA1A98A94700C63152 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C8D7B9361ACFEAEA00342508 /* Main.storyboard in Resources */, + C8D7B9351ACFEAEA00342508 /* LaunchScreen.xib in Resources */, + C81553BC1A98A94700C63152 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C81553C21A98A94700C63152 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + C81553A81A98A94700C63152 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C8DDCFEC1AC757E700414017 /* WikipediaPage.swift in Sources */, + C8D7B9341ACFEAEA00342508 /* WikipediaSearchCell.swift in Sources */, + C809A1361AD009FB001AA8FE /* CollectionViewImageCell.swift in Sources */, + C8DDCFEB1AC757E700414017 /* WikipediaAPI.swift in Sources */, + C8DDCFED1AC757E700414017 /* WikipediaSearchResult.swift in Sources */, + C8DDCFE41AC7400C00414017 /* Example.swift in Sources */, + C8D7B92E1ACFEAC900342508 /* SearchViewModel.swift in Sources */, + C81553B51A98A94700C63152 /* Example.xcdatamodeld in Sources */, + C81175AB1ACEEBAA001521F4 /* Wireframe.swift in Sources */, + C8D7B9371ACFEAEA00342508 /* ViewController.swift in Sources */, + C8D7B92D1ACFEAC900342508 /* SearchResultViewModel.swift in Sources */, + C8DDCFD31AC6F17500414017 /* HtmlParsing.swift in Sources */, + C8DDCFE61AC757AE00414017 /* ImageService.swift in Sources */, + C81553B21A98A94700C63152 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C81553C01A98A94700C63152 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C81553CB1A98A94700C63152 /* ExampleTests.swift in Sources */, + C8DDCFD41AC6F46200414017 /* HtmlParsing.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + C81553C61A98A94700C63152 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C81553AB1A98A94700C63152 /* RxExample */; + targetProxy = C81553C51A98A94700C63152 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + C81553CC1A98A94700C63152 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C81553CD1A98A94700C63152 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + C81553CF1A98A94700C63152 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer: Krunoslav Zaher (KQ9K2C6LEP)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "/Users/kzaher/Projects/Rx/Rx/build/Debug-iphoneos", + "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/Rx-cfkyozdvlaegqibzixjokeysigeo/Build/Products/Debug-iphoneos", + ); + INFOPLIST_FILE = Example/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_SWIFT_FLAGS = "-D DEBUG"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "7db5c878-b123-4595-ac8d-4b3c3937d3ec"; + }; + name = Debug; + }; + C81553D01A98A94700C63152 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer: Krunoslav Zaher (KQ9K2C6LEP)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "/Users/kzaher/Projects/Rx/Rx/build/Debug-iphoneos", + "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/Rx-cfkyozdvlaegqibzixjokeysigeo/Build/Products/Debug-iphoneos", + ); + INFOPLIST_FILE = Example/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "7db5c878-b123-4595-ac8d-4b3c3937d3ec"; + }; + name = Release; + }; + C81553D21A98A94700C63152 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = ExampleTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; + }; + name = Debug; + }; + C81553D31A98A94700C63152 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = ExampleTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C81553A71A98A94700C63152 /* Build configuration list for PBXProject "Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C81553CC1A98A94700C63152 /* Debug */, + C81553CD1A98A94700C63152 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C81553CE1A98A94700C63152 /* Build configuration list for PBXNativeTarget "RxExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C81553CF1A98A94700C63152 /* Debug */, + C81553D01A98A94700C63152 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C81553D11A98A94700C63152 /* Build configuration list for PBXNativeTarget "ExampleTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C81553D21A98A94700C63152 /* Debug */, + C81553D31A98A94700C63152 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCVersionGroup section */ + C81553B31A98A94700C63152 /* Example.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + C81553B41A98A94700C63152 /* Example.xcdatamodel */, + ); + currentVersion = C81553B41A98A94700C63152 /* Example.xcdatamodel */; + path = Example.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ + }; + rootObject = C81553A41A98A94700C63152 /* Project object */; +} diff --git a/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..6d2a51bb --- /dev/null +++ b/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Example/Example.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/Example.xcscheme b/Example/Example.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/Example.xcscheme new file mode 100644 index 00000000..e28ae691 --- /dev/null +++ b/Example/Example.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/Example.xcscheme @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/xcschememanagement.plist b/Example/Example.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000..f036ee8c --- /dev/null +++ b/Example/Example.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,27 @@ + + + + + SchemeUserState + + Example.xcscheme + + orderHint + 0 + + + SuppressBuildableAutocreation + + C81553AB1A98A94700C63152 + + primary + + + C81553C31A98A94700C63152 + + primary + + + + + diff --git a/Example/Example/Example.xcdatamodeld/.xccurrentversion b/Example/Example/Example.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..74c5c1b4 --- /dev/null +++ b/Example/Example/Example.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Example.xcdatamodel + + diff --git a/Example/Example/Example.xcdatamodeld/Example.xcdatamodel/contents b/Example/Example/Example.xcdatamodeld/Example.xcdatamodel/contents new file mode 100644 index 00000000..193f33c9 --- /dev/null +++ b/Example/Example/Example.xcdatamodeld/Example.xcdatamodel/contents @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Example/Example/Info.plist b/Example/Example/Info.plist new file mode 100644 index 00000000..cc02affe --- /dev/null +++ b/Example/Example/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + Krunoslav-Zaher.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Example/ExampleTests/ExampleTests.swift b/Example/ExampleTests/ExampleTests.swift new file mode 100644 index 00000000..1144d457 --- /dev/null +++ b/Example/ExampleTests/ExampleTests.swift @@ -0,0 +1,34 @@ +// +// ExampleTests.swift +// ExampleTests +// +// Created by Krunoslav Zaher on 2/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import UIKit +import XCTest + +class ExampleTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + func testParser() { + let URLs = parseImageURLsfromHTML(html) + + XCTAssertEqual(URLs[0], NSURL(string: "http://upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Wiktionary-logo-en.svg/37px-Wiktionary-logo-en.svg.png")!) + } + + func testParser2() { + let URLs = parseImageURLsfromHTML(html2) + + XCTAssertEqual(URLs[0], NSURL(string: "http://upload.wikimedia.org/wikipedia/commons/thumb/e/e2/Fox%2C_Dalston%2C_London_%283634023284%29.jpg/250px-Fox%2C_Dalston%2C_London_%283634023284%29.jpg")!) + } + + let html = "\n\n\n\n\n
\"\"Look up Rx in Wiktionary, the free dictionary.
\n

RX is the telegraph and radio abbreviation for \"receive\", \"receiver\" or \"reception\".

\n

RX, Rx, u{211E}, or rx may also refer to:

\n" + + let html2 = "
For other uses, see Pizza (disambiguation).
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Pizza
\"Pizza
TypeFlatbread
Place of originNaples, Italy
Serving temperatureHot or warm
Main ingredientsDough, often tomato sauce, cheese
VariationsCalzone, Stromboli
\"\" Cookbook:Pizza  \"\" Pizza
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Part of a series on
Pizza
\"Kartoffel
\n\n
\n\n
\n
\n
Cooking variations
\n\n
\n
\n\n
\n\n
\n
\n
Related articles
\n\n
\n
\n\n
\n\n
\n

Pizza is an oven-baked flat bread generally topped with tomato sauce and cheese. It is commonly supplemented with a selection of meats, vegetables and condiments. The term was first recorded in AD 997, in a Latin manuscript from the southern Italian town of Gaeta,[1] in Lazio, Central Italy. The modern pizza was invented in Naples, Italy, and the dish and its variants have since become popular in many areas of the world.[2]

\n

In 2009, upon Italy's request, Neapolitan pizza was safeguarded in the European Union as a Traditional Speciality Guaranteed dish.[3][4] The Associazione Verace Pizza Napoletana (the True Neapolitan Pizza Association) is a non-profit organization founded in 1984 with legal and operational headquarters in Naples. Its mission is to promote and protect the \"true Neapolitan pizza\" defined as the product made in accordance with the International Regulations for the brand.[5]

\n

Pizza is sold fresh, frozen or in portions. Various types of ovens are used to cook them and many varieties exist. Several similar dishes are prepared from ingredients commonly used in pizza preparation, such as calzone and stromboli.

\n

\n\n

\n

Etymology[edit]

\n

The origin of the word pizza is uncertain. The term \"pizza\" first appeared \"in a Latin text from the southern Italian town of Gaeta in 997 AD, which states that a tenant of certain property is to give the bishop of Gaeta duodecim pizze (\"twelve pizzas\") every Christmas Day, and another twelve every Easter Sunday\".[1][6]

\n

Suggested etymologies include:

\n
    \n
  • The Ancient Greek word u{03c0}u{03b9}u{03ba}u{03c4}u{03ae} (pikte), \"fermented pastry\", which in Latin became \"picta\", and Late Latin pitta > pizza. Compare Greek pita bread and the Apulia and Calabrian pitta.[7]
  • \n
  • The Ancient Greek word u{03c0}u{03af}u{03c3}u{03c3}u{03b1} (pissa, Attic u{03c0}u{03af}u{03c4}u{03c4}u{03b1}, pitta), \"pitch\",[8][9] or pu{1e17}tea, \"bran\" (pu{0113t}u{00ed}tu{0113}s, \"bran bread\").[10]
  • \n
  • The Italian word pizzicare meaning \"to pluck\", which refers to pizza being plucked quickly from the oven (pizzicare was derived from an older Italian word pizzo meaning \"point\").[11]
  • \n
  • The Old High German word bizzo or pizzo meaning \"mouthful\" (related to the English words \"bit\" and \"bite\"), which was brought to Italy in the middle of the 6th century AD by the invading Lombards.[1][12]
  • \n
\n

History[edit]

\n
Main article: History of pizza
\n
\n
\"\"\n
\n
\nNeapolitan Pizza Margherita
\n
\n
\n

The ancient Greeks covered their bread with oils, herbs and cheese.[13] The Romans developed placenta cake, a sheet of dough topped with cheese and honey and flavored with bay leaves.[citation needed]

\n

Modern pizza evolved from similar flatbread dishes in Naples, Italy in the 18th or early 19th century.[14] Prior to that time, flatbread was often topped with ingredients such as garlic, salt, lard, cheese, and basil. It is uncertain when tomatoes were first added and there are many conflicting claims.[14]

\n

A popular contemporary legend holds that the archetypal pizza, Pizza Margherita, was invented in 1889, when the Royal Palace of Capodimonte commissioned the Neapolitan pizzaiolo (pizza maker) Raffaele Esposito to create a pizza in honor of the visiting Queen Margherita. Of the three different pizzas he created, the Queen strongly preferred a pie swathed in the colors of the Italian flag: red (tomato), green (basil), and white (mozzarella). Supposedly, this kind of pizza was then named after the Queen as \"Pizza Margherita\",[15] although recent research casts doubt on this legend.[16]

\n

Pizza was brought to the United States with Italian immigrants in the late nineteenth century;[17] and first appeared in areas where Italian immigrants concentrated. The country's first pizzeria, Lombardi's, opened in 1905.[18] Following World War II, veterans returning from the Italian Campaign after being introduced to Italy's native cuisine proved a ready market for pizza in particular.[19] Since then pizza consumption has exploded in the U.S.[20] pizza chains such as Domino's, Pizza Hut, and Papa John's, pies from take and bake pizzerias and chilled and frozen from supermarkets, make pizza readily available nationwide. It is so ubiquitous, thirteen percent of the U.S. population consumes pizza on any given day.[21]

\n

Preparation[edit]

\n
\n
\"\"\n
\n
\nAn uncooked neapolitan pizza on a peel
\n
\n
\n

Pizza is prepared fresh, frozen, and as portion-size slices or pieces. Methods have been developed to overcome challenges such as preventing the sauce from combining with the dough and producing a crust that can be frozen and reheated without becoming rigid. There are frozen pizzas with raw ingredients and self-rising crusts.

\n

Another form of uncooked pizza is available from take and bake pizzerias. This pizza is assembled in the store, then sold to customers to bake in their own ovens. Some grocery stores sell fresh dough along with sauce and basic ingredients, to complete at home before baking in an oven.

\n

Cooking[edit]

\n

In restaurants, pizza can be baked in an oven with stone bricks above the heat source, an electric deck oven, a conveyor belt oven or, in the case of more expensive restaurants, a wood- or coal-fired brick oven. On deck ovens, pizza can be slid into the oven on a long paddle, called a peel, and baked directly on the hot bricks or baked on a screen (a round metal grate, typically aluminum). Prior to use, a peel may be sprinkled with cornmeal to allow pizza to easily slide onto and off of it.[22] When made at home, it can be baked on a pizza stone in a regular oven to reproduce the effect of a brick oven. Another option is grilled pizza, in which the crust is baked directly on a barbecue grill. Greek pizza, like Chicago-style pizza, is baked in a pan rather than directly on the bricks of the pizza oven.

\n
    \n
  • \n
    \n
    \n
    \"\"
    \n
    \n
    \n

    Traditional pizza dough is tossed

    \n
    \n
    \n
  • \n
  • \n
    \n
    \n
    \"\"
    \n
    \n
    \n

    Pizzas bake in a traditional wood-fired brick oven

    \n
    \n
    \n
  • \n
  • \n
    \n
    \n
    \"\"
    \n
    \n
    \n

    A cooked pepperoni pizza pie. In the background is a calzone

    \n
    \n
    \n
  • \n
\n

Crust[edit]

\n
\n
\"\"\n
\n
\nA pizza just removed from an oven, with a close-up view of the cornicione (crust)
\n
\n
\n

The bottom of the pizza, called the \"crust\", may vary widely according to styleu{2014}thin as in a typical hand-tossed New York-style, or thick as in a deep dish Chicago-style. It is traditionally plain, but may also be seasoned with garlic or herbs, or stuffed with cheese. The outer edge of the pizza is sometimes referred to as the cornicione.[23] Often pizza dough contains sugar, both to help its yeast rise and enhance browning of the crust.[24]

\n

Cheese[edit]

\n
Main article: Pizza cheese
\n

The original pizza used only mozzarella,[citation needed] the highest quality ones buffalo mozzarella produced in the surroundings of Naples.[25] Today, other cheeses have found their way onto quality pies, including provolone, pecorino romano, ricotta, and scamorza.

\n

Less expensive processed cheeses have been developed for mass-market pizzas to produce desirable qualities like browning, melting, stretchiness, consistent fat and moisture content, and stable shelflife. This quest to create the ideal and economical pizza cheese has involved many studies and experiments analyzing the impact of vegetable oil, manufacturing and culture processes, denatured whey proteins and other changes in manufacture. In 1997 it was estimated that annual production of pizza cheese was 2,000,000,000 pounds (910,000,000 kg) in the U.S. and 200,000,000 pounds (91,000,000 kg) in Europe.[26]

\n

Toppings[edit]

\n
\n
\"\"\n
\n
\nA pizza with various toppings
\n
\n
\n

Myriad toppings are used on pizzas, including, but not limited to:

\n\n

Varieties[edit]

\n\n
\n
\"\"\n
\n
\n500 pizza varieties listed on a trattoria menu in Southern Italy
\n
\n
\n

Italy[edit]

\n

Authentic Neapolitan pizza (pizza napoletana) is typically made with San Marzano tomatoes grown on the volcanic plains south of Mount Vesuvius, and mozzarella di bufala Campana made with the milk from water buffalo raised in the marshlands of Campania and Lazio.[27] This mozzarella is protected with its own European protected designation of origin.[27]

\n

Another popular Italian style is Sicilian, a thick-crust or deep-dish pizza originating in the 17th century in Sicily. Derived from the sicilian Sfincione,[28][29] is essentially focaccia with toppings. Until the 1860s, Sfincione was the type of pizza usually consumed in Sicily, especially on the western portion of the island.[30]

\n

Additional Italian styles include pizza capricciosa, which is prepared with mozzarella cheese, baked ham, mushroom, artichoke and tomato,[31] and pizza pugliese with tomato, mozzarella and onion.[32]

\n

United States[edit]

\n\n
\n
\"\"\n
\n
\nA wrapped frozen pizza
\n
\n
\n

Distinct regional types developed in the twentieth century, including California, Chicago, Greek, New York styles with variations, including deep-dish, stuffed, pockets, turnovers, rolled, even pizza-on-a-stick, each with seemingly limitless combinations of sauce and toppings.[20]

\n

Records[edit]

\n

The world's largest pizza was at the Norwood Pick 'n Pay hypermarket in Johannesburg, South Africa. According to the Guinness Book of Records the pizza was 37.4 meters (122 feet 8 inches) in diameter and was made using 500 kg of flour, 800 kg of cheese and 900 kg of tomato puree. This was accomplished on December 8, 1990.[33]

\n

The world's most expensive pizza listed by Guinness World Records is a commercially available thin-crust pizza at Maze restaurant in London, United Kingdom, which costs u{00a3}100. The pizza is wood fire-baked, and is topped with onion puree, white truffle paste, fontina cheese, baby mozzarella, pancetta, cep mushrooms, freshly picked wild mizuna lettuce, and fresh shavings of a rare Italian white truffle.[34]

\n

There are several instances of more expensive pizzas, such as the $4,200 \"Pizza Royale 007\" at Haggis restaurant in Glasgow, Scotland, which has caviar, lobster and is topped with 24-carat gold dust, and the $1,000 caviar pizza made by Nino's Bellissima pizzeria in New York City, New York.[35][36] However, these are not officially recognized by Guinness World Records. Additionally, a pizza was made by the restaurateur Domenico Crolla that included toppings such as sunblush-tomato sauce, Scottish smoked salmon, medallions of venison, edible gold, lobster marinated in the finest cognac and champagne-soaked caviar. The pizza was auctioned for charity in 2007, raising u{00a3}2,150.[37]

\n

Health issues[edit]

\n
\n
\"\"\n
\n
\nA vegetarian pizza
\n
\n
\n

Some mass-produced pizzas by fast food chains have been criticized as having an unhealthy balance of ingredients. Pizza can be high in salt, fat and calories. The USDA reports an average sodium content of 5101 mg per 14\" pizza in fast food chains.[38] There are concerns about negative health effects.[39] Food chains have come under criticism at various times for the high salt content of some of their meals.[40]

\n

Frequent pizza eaters in Italy have been found to have a relatively low incidence of cardiovascular disease[41] and digestive tract cancers[42] relative to infrequent pizza eaters, although the nature of the correlation between pizza and such perceived benefits is unclear. Pizza consumption in Italy might only indicate adherence to traditional Mediterranean dietary patterns, which have been shown to have various health benefits.[42]

\n

Some attribute the apparent health benefits of pizza to the lycopene content in pizza sauce,[43] which research indicates likely plays a role in protecting against cardiovascular disease and various cancers.[44]

\n

National Pizza Month[edit]

\n

National Pizza Month is an observance that occurs for the month of October every year in the United States and some areas of Canada.[45][46][47][48] This observance began in October 1984, and was created by Gerry Durnell, the publisher of Pizza Today magazine.[48] During this time, some people observe National Pizza Month by consuming various types of pizzas or pizza slices, or going to various pizzerias.[45]

\n

Similar dishes[edit]

\n
\n
\"\"\n
\n
\nA halved calzone
\n
\n
\n
\n\n
\n
    \n
  • Calzone and stromboli are similar dishes (a calzone is traditionally half-moon-shaped, while a stromboli is tube-shaped) that are often made of pizza dough rolled or folded around a filling.
  • \n
  • \"Farinata\" or \"cecina\".[49] A Ligurian (farinata) and Tuscan (cecina) regional dish made from chickpea flour, water, salt and olive oil. Also called Socca in the Provence region of France. Often baked in a brick oven, and typically weighed and sold by the slice.
  • \n
  • The Alsatian Flammekueche[50] German: Flammkuchen. French: Tarte flambu{00e9}e is a thin disc of dough covered in cru{00e8}me frau{00ee}che, onions, and bacon.
  • \n
  • Garlic fingers is an Atlantic Canadian dish, similar to a pizza in shape and size, and made with similar dough. It is garnished with melted butter, garlic, cheese, and sometimes bacon.
  • \n
  • The Anatolian Lahmacun (Arabic: lau{1e25}m bi'aju{012b}n; Armenian: lahmajoun; also Armenian pizza or Turkish pizza) is a meat-topped dough round. The bread is very thin; the layer of meat often includes chopped vegetables.
  • \n
  • The Levantine Manakish (Arabic: ma'ujnu{0101}t) and Sfiha (Arabic: lau{1e25}m bi'aju{012b}n; also Arab pizza) are dishes similar to pizza.
  • \n
  • The Macedonian Pastrmajlija is a bread pie made from dough and meat. It is usually oval-shaped with chopped meat on top of it.
  • \n
  • The Provenu{00e7}al Pissaladiu{00e8}re is similar to an Italian pizza, with a slightly thicker crust and a topping of cooked onions, anchovies, and olives.
  • \n
  • Pizza bread is a type of sandwich that is often served open-faced which consists of bread, pizza or tomato sauce, cheese[51] and various toppings. Homemade versions may be prepared.
  • \n
  • Pizza sticks may be prepared with pizza dough and pizza ingredients, in which the dough is shaped into stick forms, sauce and toppings are added, and it is then baked.[52] Bread dough may also be used in their preparation,[53] and some versions are fried.[54]
  • \n
  • Pizza Rolls are a frozen snack variation of traditional pizza that can include various toppings. Homemade versions may be prepared as well.
  • \n
  • Okonomiyaki is a Japanese flat grilled dough often referred to as \"Japanese pizza\"[55]
  • \n
  • \"Zanzibar pizza\" is a street food served in Stone Town, Zanzibar, Tanzania. It uses a dough much thinner than pizza dough, almost like phyllo dough, filled with minced beef, onions, and an egg, similar to Moroccan bestila. [56]
  • \n
\n

See also[edit]

\n
\n\n\n\n\n\n\n\n\n\n
\"PortalFood portal
\"PortalItaly portal
\n
\n\n

References[edit]

\n
\n
    \n
  1. ^ a b c Maiden, Martin. \"Linguistic Wonders Series: Pizza is a German(ic) Word\". yourDictionary.com. Archived from the original on 2003-01-15. 
  2. \n
  3. ^ Miller, Hanna (Aprilu{2013}May 2006). \"American Pie\". American Heritage Magazine. Retrieved 4 May 2012. 
  4. \n
  5. ^ Official Journal of the European Union, Commission regulation (EU) No 97/2010, 5 February 2010
  6. \n
  7. ^ International Trademark Association, European Union: Pizza napoletana obtains \"Traditional Speciality Guaranteed\" status, 1 April 2010
  8. \n
  9. ^ \"Associazione Verace Pizza Napoletana (AVPN)\". Retrieved 2 January 2015. 
  10. \n
  11. ^ Salvatore Riciniello (1987) Codice Diplomatico Gaetano, Vol. I, La Poligrafica
  12. \n
  13. ^ Babiniotis, Georgios (2005). u039bu03b5u03beu03b9u03bau03cc u03c4u03b7u03c2 u039du03adu03b1u03c2 u0395u03bbu03bbu03b7u03bdu03b9u03bau03aeu03c2 u0393u03bbu03ceu03c3u03c3u03b1u03c2 [Lexicon of New Greek] (in Greek). u039au03adu03bdu03c4u03c1u03bf u039bu03b5u03beu03b9u03bau03bfu03bbu03bfu03b3u03afu03b1u03c2. p. 1413. ISBN 960-86190-1-7. 
  14. \n
  15. ^ \"Pizza, at Online Etymology Dictionary\". Etymonline.com. Retrieved 2009-06-05. 
  16. \n
  17. ^ \"Pissa, Liddell and Scott, \"A Greek-English Lexicon, at Perseus\". Perseus.tufts.edu. Retrieved 2009-06-05. 
  18. \n
  19. ^ \"Pizza, at Dictionary.com\". Dictionary.reference.com. Retrieved 2009-06-05. 
  20. \n
  21. ^ \"Pizza, History and Legends of Pizza\". Whatscookingamerica.net. Retrieved 2009-06-05. 
  22. \n
  23. ^ \"Pizza\". Garzanti Linguistica. De Agostini Scuola Spa. Retrieved 2014-01-31. 
  24. \n
  25. ^ Talati-Padiyar, Dhwani. Travelled, Tasted, Tried & Tailored: Food Chronicles. ISBN 1304961354. Retrieved 18 November 2014. 
  26. \n
  27. ^ a b Helstosky, Carol (2008). Pizza: A Global History. London: Reaktion. pp. 21u201322. ISBN 1-86189-391-4. 
  28. \n
  29. ^ \"Pizza Margherita: History and Recipe\". Italy Magazine. 14 March 2011. Retrieved 23 April 2012. 
  30. \n
  31. ^ \"Was margherita pizza really named after Italy's queen?\". BBC Food. 28 December 2012. Retrieved 31 December 2012. 
  32. \n
  33. ^ Helstosky, Carol (2008). Pizza: A Global History. Reaktion Books. p. 48. ISBN 978-1-86189-630-8. 
  34. \n
  35. ^ \"The people who eat pizza every day\". BBC News. 28 February 2014. Retrieved 23 September 2014. 
  36. \n
  37. ^ Turim, Gayle. \"A Slice of History: Pizza Through the Ages\". History.com. Retrieved 9 November 2014. 
  38. \n
  39. ^ a b \"Pizza Garden: Italy, the Home of Pizza\". CUIP Chicago Public Schools u2013 University of Chicago Internet Project u2013 The University of Chicago. Retrieved August 2014. 
  40. \n
  41. ^ Rhodes, Donna G.; Adler, Meghan E.; Clemens,, John C.; LaComb, Randy P.; Moshfegh, Alanna J. \"Consumption of Pizza\". Food Surveys Research Group. Retrieved 25 September 2014. 
  42. \n
  43. ^ Owens, Martin J. (2003). Make Great Pizza at Home. Taste of America Press. p. 3. ISBN 0-9744470-0-5. 
  44. \n
  45. ^ Braimbridge, Sophie; Glynn, Joanne (2005). Food of Italy. Murdoch Books. p. 167. ISBN 978-1-74045-464-3. 
  46. \n
  47. ^ DeAngelis, Dominick A. (December 1, 2011). The Art of Pizza Making: Trade Secrets and Recipes. The Creative Pizza Company. pp. 20u201328. ISBN 0-9632034-0-1. 
  48. \n
  49. ^ Anderson, Sam (October 11, 2012). \"Go Ahead, Milk My Day\". NYTimes. Retrieved November 7, 2014. 
  50. \n
  51. ^ Fox, Patrick F.; () et al. (2000). Fundamentals of Cheese Science. Aspen Pub. p. 482. ISBN 0-8342-1260-9. 
  52. \n
  53. ^ a b \"Selezione geografica\". Europa.eu.int. 2009-02-23. Retrieved 2009-04-02. 
  54. \n
  55. ^ \"What is Sicilian Pizza?\". WiseGeek. Retrieved 14 April 2013. 
  56. \n
  57. ^ Giorgio Locatelli (2012-12-26). Made In Sicily. ISBN 978-0-06-213038-9. Retrieved 2013-07-04. 
  58. \n
  59. ^ Gangi, Roberta (2007). \"Sfincione\". Best of Sicily Magazine. Archived from the original on 2014-04-02. 
  60. \n
  61. ^ Rough Guide Phrasebook: Italian: Italian. 2011-08-01. p. 244. ISBN 978-1-4053-8646-3. 
  62. \n
  63. ^ Wine Enthusiast, Volume 21, Issues 1-7. Wine Enthusiast. 2007. p. 475. 
  64. \n
  65. ^ \"Mama Lena's pizza \"One\" for the book... of records\". Pittsburghlive.com. Retrieved 2009-04-02. 
  66. \n
  67. ^ \"Most expensive pizza\". Retrieved 4 October 2014. 
  68. \n
  69. ^ Shaw, Bryan (March 11, 2010). \"Top Five Most Expensive Pizzas in The World\". Haute Living. Retrieved 9 September 2014. 
  70. \n
  71. ^ Conway, Lawrence (June 18, 2012). \"New York restaurant serving up $1,000 PIZZA... decadent dish is topped with two of the world's top caviars\". Daily Mail. Retrieved 9 September 2014. 
  72. \n
  73. ^ \"Chef cooks u00a32,000 Valentine pizza\". BBC News. 2007-02-14. Retrieved 2012-07-07. 
  74. \n
  75. ^ \"Basic Report 21299\". National Nutrient Database for Standard Reference. 2014-09-28. 
  76. \n
  77. ^ \"Survey of pizzas\". Food Standards Agency. 2004-07-08. Archived from the original on 2005-12-28. Retrieved 2009-04-02. 
  78. \n
  79. ^ \"Health | Fast food salt levels \"shocking\"\". BBC News. 2007-10-18. Retrieved 2009-04-02. 
  80. \n
  81. ^ S Gallus, A Tavani, and C La Vecchia. \"Pizza and risk of acute myocardial infarction\", European Journal of Clinical Nutrition, 2004, Retrieved on 18 January 2015
  82. \n
  83. ^ a b S Gallus, Cristina Bosetti, E Negri, Renato Talamini, M Montella Ettore Conti, Silvia Franceschi and Carlo La Vecchia. \"Does pizza protect against cancer?\", International Journal of Cancer, Volume 107, Issue 2 (2003). Retrieved on 28 September 2014
  84. \n
  85. ^ Bramley, Peter \"Is Lycopene Benefitial to Human Health?\", Phytochemistry, Volume 54, Issue 3, 1 June 2000, Pages 233u2013236, Retrieved on 5 October 2014
  86. \n
  87. ^ Adetayo O. Omoni, Rotimi E. Aluko. \"The anti-carcinogenic and anti-atherogenic effects of lycopene: a review\", Trends in Food Science & Technology, Volume 16, Issue 8, August 2005, Pages 344u2013350, Retrieved on 5 October 2014.
  88. \n
  89. ^ a b Pizza City. p. 97. Retrieved 16 October 2014. 
  90. \n
  91. ^ Pizza Anytime. p. 4. Retrieved 16 October 2014. 
  92. \n
  93. ^ The Oxford Encyclopedia of Food and Drink in America. Oxford University Press. p. 643. Retrieved 16 October 2014. 
  94. \n
  95. ^ a b \"National Pizza Month\". Pizza.com. Retrieved 15 October 2014. 
  96. \n
  97. ^ \"Brick Oven Cecina\". Fornobravo.com. Retrieved 2009-04-02. 
  98. \n
  99. ^ Helga Rosemann, Flammkuchen: Ein Streifzug durch das Land der Flammkuchen mit vielen Rezepten und Anregungen (Offenbach: Hu00f6ma-Verlag, 2009).
  100. \n
  101. ^ Adler, Karen; Fertig, Judith (2014). Patio Pizzeria. Running Press. p. 67. ISBN 0-7624-4966-7. 
  102. \n
  103. ^ McNair, James (2000). James McNair's New Pizza. Chronicle Books. p. 53. ISBN 0-8118-2364-4. 
  104. \n
  105. ^ Magee, Elaine (2009). The Flax Cookbook. Da Capo Press. p. 130. ISBN 0-7867-3062-5. 
  106. \n
  107. ^ Wilbur, Todd (1997). Top Secret Restaurant Recipes. Penguin. p. 27. ISBN 1-4406-7440-X. 
  108. \n
  109. ^ http://www.hanamiweb.com/okonomiyaki.html
  110. \n
  111. ^ Samuelsson, Marcus. \"The Soul of a New Cuisine: A Discovery of the Foods and Flavors of Africa\". Houghton Mifflin Harcourt. New York: 2006.
  112. \n
\n
\n

Further reading[edit]

\n\n

External links[edit]

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\n\n\n\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\n\n\n\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\n\n\n\n\n\n\n" +} diff --git a/RxTests/Info.plist b/Example/ExampleTests/Info.plist similarity index 100% rename from RxTests/Info.plist rename to Example/ExampleTests/Info.plist diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..05c6d385 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,9 @@ +**The MIT License** +**Copyright (c) 2015 Krunoslav Zaher** +**All rights reserved.** + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..9000cfb3 --- /dev/null +++ b/README.md @@ -0,0 +1,267 @@ +RxSwift (0.7): Reactive extensions for Swift +====================================== + +Xcode 6.3 / Swift 1.2 required + +This is a Swift port of Reactive extensions. + +[https://github.com/Reactive-Extensions](https://github.com/Reactive-Extensions) + +Like the original Rx, it's intention is to enable easy composition of asynchronous operations and event streams. + +It tries to port as many concepts from the original Rx as possible, but some concepts were adapted for more pleasant and performant integration with iOS/OSX environment. + +Probably the best analogy for those who have never heard of Rx would be: + + +``` +git diff | grep bug | less # linux pipes - programs communicate by sending + # sequences of bytes, words, lines, '\0' terminated strings... +``` +would become if written in RxSwift +``` +gitDiff() >- grep("bug") >- less // rx sink (>-) operator - rx units communicate by sending + // sequences of swift objects +``` + +Rx is implemented as a slightly modified version of observer pattern. + +[http://en.wikipedia.org/wiki/Observer_pattern](http://en.wikipedia.org/wiki/Observer_pattern) + +It probably sounds little weird at first, but those abstractions are equivalent. Following paragraphs explain that in more detail. + +## But first, why would somebody want to use Rx? + +Writing correct asynchronous programs is hard because every line of code has to deal with following concerns: + +* Resource management (disposal of memory allocations, sockets, file handles) +* Asynchronous operations (composition, cancellation, deadlocks) +* Error handling + +Thinking about those concerns over and over again is tedious and error prone experience. Rx provides a level of abstraction that hides all of that complexity and makes writing performant and correct programs easy. + +It provides default implementations of most common units/operations of async programs and enables easy bridging of existing imperative APIs in a couple of lines of code. + +In the context of Rx, data is modeled as "lazy evaluated" sequence of swift objects. That includes: + +* Asynchronous operations +* UI actions +* Observing of property changes +* ... + +It is also pretty straightforward to create custom sequence transformers. + +## What's so special about sequences? + +Everybody is familiar with sequences. Lists/sequences are probably one of the first concepts programmers learn. +They are easy to visualize and easy to reason about. + +Here is a sequence of numbers + + +``` +--1--2--3--4--5--6--| // it terminates normally +``` + +Here is another one with characters + +``` +--a--b--a--a--a---d---X // it terminates with error +``` + +Some sequences are finite, and some are infinite, like sequence of button taps + +``` +---tap-tap-------tap---> +``` + +These diagrams are called marble diagrams. + +[http://rxmarbles.com/](http://rxmarbles.com/) + +## How do sequences solve anything? + +If everything is a sequence, and every operation is just a transformation of input sequence into output sequence then it's pretty straightforward to compose operations. + +Asynchronous or time delayed operations don't cause any problems because elements of Rx sequences are accessed by registering observers and are not enumerated immediatelly. This can be viewed as a "lazy evaluation" implementation technique. + +Resource management is also pretty natural. Sequence can release element computation resources once the observer has unsubscribed from receiving next elements. If no observer is waiting for next element to arrive, then it doesn't make sense to waste resources computing next elements. Of course, it's possible to implement other resource management logic. + +## Example + +This is Rx code taken from Rx example app inside repository. Example app transforms Wikipedia into a image search engine. It scrapes wikipedia pages for image URLs, and displayes all of the images in search results. + +```swift +results = searchText >- throttle(300, $.mainScheduler) + >- distinctUntilChanged >- map { query in + API.getSearchResults(query) + } + >- switchLatest >- map { results in + convertResults(results) + } +``` + +On a conceptual level, this is the explanation of applied transformations: + +* throttle - after new search value arrives, wait for 300 ms, if meanwhile new value is received, wait for another 300 ms +* distinctUntilChanged - if received value is different then the last one, forward it, otherwise don't send anything +* map - transforms sequence of search queries into a sequence of asynchronous URL requests +* switchLatest - if a new search request arrives and old request hasn't finished, old request is cancelled and new search request starts +* map - transforms a sequence of search results into view models suitable for user interface ingestion + +That code alone won't actually start any request to server. It will only create a "template" of transformations that will be performed once somebody starts to observe results of that expression. + +To start search requests, somebody needs to call something equivalent to. + +``` +// starts listening for search results +subscription = results >- subscribeNext { results in + println("Here are search results \(results)") + } +sleep(10) +// stops listening for search results +subscription.dispose() +``` + +So ... + +## How does that work? + +`throttle`, `distinctUntilChanged`, `switchLatest` ... are just normal functions that take `Observable` as input and return `Observable` as output. `>-` is a sink operator that feeds `lhs` value to `rhs` function. + +``` +func >- (source: In, transform: In -> Out) -> Out { + return transform(source) +} +``` +This is actually a general purpose operator and it can be used outside the concept of `Observable` and sequences. + +Sequences usually don't actually exist in memory. It is just an abstraction. Sequences of elements of type `Element` are represented by a corresponding `Observable`. Every time some element is observed it implicitly becomes next element in observed sequence of values. Even though the sequence of elements is implicit, that doesn't make it any less usefull. + +``` +class Observable { + func subscribe(observer: Observer) -> Disposable +} +``` + +To observe elements of a sequence `Observer` needs to subscribe to `Observable`. Every time next element of a sequence is produced, sequence terminates or fails with error, `Observable` with fire a notification to `Observer`. + +``` +enum Event { + case Next(Element) // next element of a sequence + case Error(ErrorType) // sequence failed with error + case Completed // sequence terminated successfully +} + +protocol ObserverType { + func on(event: Event) +} + +``` + +When `Observer` wants to unsubscribe notifications from `Observable` it needs to call `dispose` on `Disposable` it received while subscribing. + +``` +protocol Disposable +{ + func dispose() +} +``` + +## Error handling + +Error handling is pretty straightforward. If one sequence terminates with error, then all of the dependant sequences will terminate with error. It's usual short circuit logic. + +Swift doesn't have a concept of exceptions so this project introduces `Result` enum. +_(Haskell [`Either`](https://hackage.haskell.org/package/category-extras-0.52.0/docs/Control-Monad-Either.html) monad)_ + +``` +public enum Result { + case Success(ResultType) + case Error(ErrorType) +} +``` + +To enable writing more readable code, a few `Result` operators are introduced + +``` +result1 >== { okValue in // success chaining operator + // executed on success + return ? +} >>! { error in // error chaining operator + // executed on error + return ? +} +``` + +If some action needs to be peformed only after a successfull computation without using it's result then `>>>` is used. + +``` +result1 >>> { + // executed on success + return ? +} +``` + +_`>==` and `>>>` were chosen because they are the closest sequence of characters to standard monadic bind `>>=` and `>>` function. +`>>!` was chosen because `!` is easily associated with error._ + +## Naming conventions and best practices + +For every group of transforming functions there are versions with and without "OrDie" suffix. + +e.g. + +``` +public func mapOrDie + (selector: E -> Result) + -> (Observable -> Observable) { + return { source in + return selectOrDie(selector)(source) + } +} + +public func map + (selector: E -> R) + -> (Observable -> Observable) { + return { source in + return select(selector)(source) + } +} +``` + +Returning an error from a selector will cause entire graph of dependant sequence transformers to "die" and fail with error. Dying implies that it will release all of it's resources and never produce another sequence value. This is usually not an obvious effect. + +If there is some UITextField bound to a observable sequence that fails with error or completes, screen won't be updated ever again. + +To make those situations more obvious, RxCocoa will throw an exception in case some sequence that is bound to UI control terminates with an error. + +Using functions without "OrDie" suffix is usually a preferred option. + +Best practice would be to use `Result` enum as a `Element` type in observable sequence. This is how example app works. In that way, errors can be safely propagated to UI and observing sequences will continue to produce values in case of some transient server error. + +## Peculiarities + +* Swift support for generic enums is limited. That's why there is `Box` hack in `Result` and `Event` enums +``` +unimplemented IR generation feature non-fixed multi-payload enum layout +``` +* Swift compiler had troubles with curried functions in release mode +``` +// These two functions are equivalent, although second option is more readable IMHO + +public func map // this is ok + (selector: E -> R) + -> (Observable -> Observable) { + return { source in + return select(selector)(source) + } +} + +public func map // this will cause crashes in release version + (selector: E -> R) // of your program if >- operator is used + (source: Observable) + -> Observable { + return select(selector)(source) +} +``` \ No newline at end of file diff --git a/Rx.xcodeproj/project.pbxproj b/Rx.xcodeproj/project.pbxproj deleted file mode 100644 index 3c3e0211..00000000 --- a/Rx.xcodeproj/project.pbxproj +++ /dev/null @@ -1,410 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - C89C81931A87CCBF00AA00FF /* Rx.h in Headers */ = {isa = PBXBuildFile; fileRef = C89C81921A87CCBF00AA00FF /* Rx.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C89C81991A87CCBF00AA00FF /* Rx.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C89C818D1A87CCBF00AA00FF /* Rx.framework */; }; - C89C81A01A87CCBF00AA00FF /* RxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89C819F1A87CCBF00AA00FF /* RxTests.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - C89C819A1A87CCBF00AA00FF /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C89C81841A87CCBF00AA00FF /* Project object */; - proxyType = 1; - remoteGlobalIDString = C89C818C1A87CCBF00AA00FF; - remoteInfo = Rx; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - C89C818D1A87CCBF00AA00FF /* Rx.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Rx.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C89C81911A87CCBF00AA00FF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C89C81921A87CCBF00AA00FF /* Rx.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Rx.h; sourceTree = ""; }; - C89C81981A87CCBF00AA00FF /* RxTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RxTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - C89C819E1A87CCBF00AA00FF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C89C819F1A87CCBF00AA00FF /* RxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxTests.swift; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - C89C81891A87CCBF00AA00FF /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C89C81951A87CCBF00AA00FF /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - C89C81991A87CCBF00AA00FF /* Rx.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - C89C81831A87CCBF00AA00FF = { - isa = PBXGroup; - children = ( - C89C818F1A87CCBF00AA00FF /* Rx */, - C89C819C1A87CCBF00AA00FF /* RxTests */, - C89C818E1A87CCBF00AA00FF /* Products */, - ); - sourceTree = ""; - }; - C89C818E1A87CCBF00AA00FF /* Products */ = { - isa = PBXGroup; - children = ( - C89C818D1A87CCBF00AA00FF /* Rx.framework */, - C89C81981A87CCBF00AA00FF /* RxTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - C89C818F1A87CCBF00AA00FF /* Rx */ = { - isa = PBXGroup; - children = ( - C89C81921A87CCBF00AA00FF /* Rx.h */, - C89C81901A87CCBF00AA00FF /* Supporting Files */, - ); - path = Rx; - sourceTree = ""; - }; - C89C81901A87CCBF00AA00FF /* Supporting Files */ = { - isa = PBXGroup; - children = ( - C89C81911A87CCBF00AA00FF /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - C89C819C1A87CCBF00AA00FF /* RxTests */ = { - isa = PBXGroup; - children = ( - C89C819F1A87CCBF00AA00FF /* RxTests.swift */, - C89C819D1A87CCBF00AA00FF /* Supporting Files */, - ); - path = RxTests; - sourceTree = ""; - }; - C89C819D1A87CCBF00AA00FF /* Supporting Files */ = { - isa = PBXGroup; - children = ( - C89C819E1A87CCBF00AA00FF /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - C89C818A1A87CCBF00AA00FF /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - C89C81931A87CCBF00AA00FF /* Rx.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - C89C818C1A87CCBF00AA00FF /* Rx */ = { - isa = PBXNativeTarget; - buildConfigurationList = C89C81A31A87CCBF00AA00FF /* Build configuration list for PBXNativeTarget "Rx" */; - buildPhases = ( - C89C81881A87CCBF00AA00FF /* Sources */, - C89C81891A87CCBF00AA00FF /* Frameworks */, - C89C818A1A87CCBF00AA00FF /* Headers */, - C89C818B1A87CCBF00AA00FF /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Rx; - productName = Rx; - productReference = C89C818D1A87CCBF00AA00FF /* Rx.framework */; - productType = "com.apple.product-type.framework"; - }; - C89C81971A87CCBF00AA00FF /* RxTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = C89C81A61A87CCBF00AA00FF /* Build configuration list for PBXNativeTarget "RxTests" */; - buildPhases = ( - C89C81941A87CCBF00AA00FF /* Sources */, - C89C81951A87CCBF00AA00FF /* Frameworks */, - C89C81961A87CCBF00AA00FF /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - C89C819B1A87CCBF00AA00FF /* PBXTargetDependency */, - ); - name = RxTests; - productName = RxTests; - productReference = C89C81981A87CCBF00AA00FF /* RxTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - C89C81841A87CCBF00AA00FF /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0610; - ORGANIZATIONNAME = "Krunoslav Zaher"; - TargetAttributes = { - C89C818C1A87CCBF00AA00FF = { - CreatedOnToolsVersion = 6.1.1; - }; - C89C81971A87CCBF00AA00FF = { - CreatedOnToolsVersion = 6.1.1; - }; - }; - }; - buildConfigurationList = C89C81871A87CCBF00AA00FF /* Build configuration list for PBXProject "Rx" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = C89C81831A87CCBF00AA00FF; - productRefGroup = C89C818E1A87CCBF00AA00FF /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - C89C818C1A87CCBF00AA00FF /* Rx */, - C89C81971A87CCBF00AA00FF /* RxTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - C89C818B1A87CCBF00AA00FF /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C89C81961A87CCBF00AA00FF /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - C89C81881A87CCBF00AA00FF /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C89C81941A87CCBF00AA00FF /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C89C81A01A87CCBF00AA00FF /* RxTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - C89C819B1A87CCBF00AA00FF /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = C89C818C1A87CCBF00AA00FF /* Rx */; - targetProxy = C89C819A1A87CCBF00AA00FF /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - C89C81A11A87CCBF00AA00FF /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - C89C81A21A87CCBF00AA00FF /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 1; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - C89C81A41A87CCBF00AA00FF /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Rx/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - C89C81A51A87CCBF00AA00FF /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Rx/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - }; - name = Release; - }; - C89C81A71A87CCBF00AA00FF /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = RxTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - C89C81A81A87CCBF00AA00FF /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); - INFOPLIST_FILE = RxTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - C89C81871A87CCBF00AA00FF /* Build configuration list for PBXProject "Rx" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C89C81A11A87CCBF00AA00FF /* Debug */, - C89C81A21A87CCBF00AA00FF /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - C89C81A31A87CCBF00AA00FF /* Build configuration list for PBXNativeTarget "Rx" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C89C81A41A87CCBF00AA00FF /* Debug */, - C89C81A51A87CCBF00AA00FF /* Release */, - ); - defaultConfigurationIsVisible = 0; - }; - C89C81A61A87CCBF00AA00FF /* Build configuration list for PBXNativeTarget "RxTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C89C81A71A87CCBF00AA00FF /* Debug */, - C89C81A81A87CCBF00AA00FF /* Release */, - ); - defaultConfigurationIsVisible = 0; - }; -/* End XCConfigurationList section */ - }; - rootObject = C89C81841A87CCBF00AA00FF /* Project object */; -} diff --git a/Rx.xcworkspace/contents.xcworkspacedata b/Rx.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..d4832212 --- /dev/null +++ b/Rx.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/Rx.xcworkspace/xcshareddata/Rx.xccheckout b/Rx.xcworkspace/xcshareddata/Rx.xccheckout new file mode 100644 index 00000000..6d0110d7 --- /dev/null +++ b/Rx.xcworkspace/xcshareddata/Rx.xccheckout @@ -0,0 +1,41 @@ + + + + + IDESourceControlProjectFavoriteDictionaryKey + + IDESourceControlProjectIdentifier + 0AB0D228-3687-496F-A331-96D92DCCD648 + IDESourceControlProjectName + Rx + IDESourceControlProjectOriginsDictionary + + 8B123162C394A0A0A138779108E4C59DD771865A + github.com:kzaher/RxSwift.git + + IDESourceControlProjectPath + Rx.xcworkspace + IDESourceControlProjectRelativeInstallPathDictionary + + 8B123162C394A0A0A138779108E4C59DD771865A + .. + + IDESourceControlProjectURL + github.com:kzaher/RxSwift.git + IDESourceControlProjectVersion + 111 + IDESourceControlProjectWCCIdentifier + 8B123162C394A0A0A138779108E4C59DD771865A + IDESourceControlProjectWCConfigurations + + + IDESourceControlRepositoryExtensionIdentifierKey + public.vcs.git + IDESourceControlWCCIdentifierKey + 8B123162C394A0A0A138779108E4C59DD771865A + IDESourceControlWCCName + Rx + + + + diff --git a/Rx.xcworkspace/xcuserdata/kzaher.xcuserdatad/WorkspaceSettings.xcsettings b/Rx.xcworkspace/xcuserdata/kzaher.xcuserdatad/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..bfffcfe0 --- /dev/null +++ b/Rx.xcworkspace/xcuserdata/kzaher.xcuserdatad/WorkspaceSettings.xcsettings @@ -0,0 +1,10 @@ + + + + + HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges + + SnapshotAutomaticallyBeforeSignificantChanges + + + diff --git a/Rx.xcworkspace/xcuserdata/kzaher.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Rx.xcworkspace/xcuserdata/kzaher.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 00000000..ddaadf95 --- /dev/null +++ b/Rx.xcworkspace/xcuserdata/kzaher.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,33 @@ + + + + + + + + + + + + + diff --git a/Rx/Rx.xcodeproj/project.pbxproj b/Rx/Rx.xcodeproj/project.pbxproj new file mode 100644 index 00000000..a177927e --- /dev/null +++ b/Rx/Rx.xcodeproj/project.pbxproj @@ -0,0 +1,875 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + C815540C1A990D0C00C63152 /* SafeObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C815540B1A990D0C00C63152 /* SafeObserver.swift */; }; + C81554161A990E1F00C63152 /* AnonymousObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81554121A990E1F00C63152 /* AnonymousObserver.swift */; }; + C81554171A990E1F00C63152 /* AutoDetachObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81554131A990E1F00C63152 /* AutoDetachObserver.swift */; }; + C81554181A990E1F00C63152 /* ObserverBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81554141A990E1F00C63152 /* ObserverBase.swift */; }; + C815541B1A990E3B00C63152 /* DisposedObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C815541A1A990E3B00C63152 /* DisposedObserver.swift */; }; + C815541D1A990E5000C63152 /* DoneObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C815541C1A990E5000C63152 /* DoneObserver.swift */; }; + C815541F1A990E7100C63152 /* NopObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C815541E1A990E7100C63152 /* NopObserver.swift */; }; + C81554211A990E8E00C63152 /* AnonymousSafeObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81554201A990E8E00C63152 /* AnonymousSafeObserver.swift */; }; + C81554251A9925DA00C63152 /* Observable+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81554241A9925DA00C63152 /* Observable+Subscription.swift */; }; + C81C49E01AC3556A001D7A5E /* DisposeBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81C49DF1AC3556A001D7A5E /* DisposeBag.swift */; }; + C81C58D41A8D3A9A008A4301 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81C58D31A8D3A9A008A4301 /* Result.swift */; }; + C83270981AB4725800618D46 /* ColdObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83270971AB4725800618D46 /* ColdObservable.swift */; }; + C832709A1AB4727500618D46 /* Observable.Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83270991AB4727500618D46 /* Observable.Extensions.swift */; }; + C8336D211AB849CB00B8F943 /* Observable+Binding.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8336D0C1AB849CB00B8F943 /* Observable+Binding.swift */; }; + C8336D221AB849CB00B8F943 /* Observable+Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8336D0D1AB849CB00B8F943 /* Observable+Concurrency.swift */; }; + C8336D231AB849CB00B8F943 /* Observable+Multiple.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8336D0E1AB849CB00B8F943 /* Observable+Multiple.swift */; }; + C8336D241AB849CB00B8F943 /* Observable+Single.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8336D0F1AB849CB00B8F943 /* Observable+Single.swift */; }; + C8336D251AB849CB00B8F943 /* Observable+StandardSequenceOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8336D101AB849CB00B8F943 /* Observable+StandardSequenceOperators.swift */; }; + C8649FBF1AB19476006984E1 /* SerialDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8649FBE1AB19476006984E1 /* SerialDisposable.swift */; }; + C8649FC11AB194F3006984E1 /* Cancelable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8649FC01AB194F3006984E1 /* Cancelable.swift */; }; + C864E0D81ACB288000C2013F /* Lock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C864E0D71ACB288000C2013F /* Lock.swift */; }; + C86603571AA1E0A7000460BD /* ObserverOf.swift in Sources */ = {isa = PBXBuildFile; fileRef = C86603561AA1E0A7000460BD /* ObserverOf.swift */; }; + C86705251AA38D190048B3D5 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C86705241AA38D190048B3D5 /* Observable.swift */; }; + C86D1DE51ABD843A00F4308D /* Observable+Creation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C86D1DE41ABD843A00F4308D /* Observable+Creation.swift */; }; + C86D1DE71ABD846D00F4308D /* SchedulerDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = C86D1DE61ABD846D00F4308D /* SchedulerDefaults.swift */; }; + C86D6D761A8F88D2003BDD7C /* Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C86D6D751A8F88D2003BDD7C /* Rx.swift */; }; + C86D6D851A8FB8F3003BDD7C /* ConcurrencyTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C86D6D801A8FB8F3003BDD7C /* ConcurrencyTest.swift */; }; + C86D6D861A8FB8F3003BDD7C /* DisposableTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C86D6D811A8FB8F3003BDD7C /* DisposableTest.swift */; }; + C86D6D871A8FB8F3003BDD7C /* Observable+SingleTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C86D6D821A8FB8F3003BDD7C /* Observable+SingleTest.swift */; }; + C86D6D881A8FB8F3003BDD7C /* RxTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C86D6D831A8FB8F3003BDD7C /* RxTest.swift */; }; + C8754A201A97D65E00BE09AD /* CompositeDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8754A1F1A97D65E00BE09AD /* CompositeDisposable.swift */; }; + C8781B841A93EBE40028125F /* Observable+StandardSequenceOperatorsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8781B831A93EBE40028125F /* Observable+StandardSequenceOperatorsTest.swift */; }; + C8813D7A1AD12D3E0072A050 /* Aggregate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D631AD12D3E0072A050 /* Aggregate.swift */; }; + C8813D7B1AD12D3E0072A050 /* AnonymousObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D641AD12D3E0072A050 /* AnonymousObservable.swift */; }; + C8813D7C1AD12D3E0072A050 /* AsObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D651AD12D3E0072A050 /* AsObservable.swift */; }; + C8813D7D1AD12D3E0072A050 /* CombineLatest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D661AD12D3E0072A050 /* CombineLatest.swift */; }; + C8813D7E1AD12D3E0072A050 /* Concat.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D671AD12D3E0072A050 /* Concat.swift */; }; + C8813D7F1AD12D3E0072A050 /* ConcatSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D681AD12D3E0072A050 /* ConcatSink.swift */; }; + C8813D801AD12D3E0072A050 /* ConnectableObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D691AD12D3E0072A050 /* ConnectableObservable.swift */; }; + C8813D811AD12D3E0072A050 /* DistinctUntilChanged.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D6A1AD12D3E0072A050 /* DistinctUntilChanged.swift */; }; + C8813D821AD12D3E0072A050 /* Do.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D6B1AD12D3E0072A050 /* Do.swift */; }; + C8813D831AD12D3E0072A050 /* Merge.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D6C1AD12D3E0072A050 /* Merge.swift */; }; + C8813D841AD12D3E0072A050 /* Multicast.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D6D1AD12D3E0072A050 /* Multicast.swift */; }; + C8813D851AD12D3E0072A050 /* ObservableBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D6E1AD12D3E0072A050 /* ObservableBase.swift */; }; + C8813D861AD12D3E0072A050 /* ObserveSingleOn.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D6F1AD12D3E0072A050 /* ObserveSingleOn.swift */; }; + C8813D871AD12D3E0072A050 /* Producer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D701AD12D3E0072A050 /* Producer.swift */; }; + C8813D881AD12D3E0072A050 /* RefCount.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D711AD12D3E0072A050 /* RefCount.swift */; }; + C8813D891AD12D3E0072A050 /* Select.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D721AD12D3E0072A050 /* Select.swift */; }; + C8813D8A1AD12D3E0072A050 /* Sink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D731AD12D3E0072A050 /* Sink.swift */; }; + C8813D8B1AD12D3E0072A050 /* Subject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D741AD12D3E0072A050 /* Subject.swift */; }; + C8813D8C1AD12D3E0072A050 /* Switch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D751AD12D3E0072A050 /* Switch.swift */; }; + C8813D8D1AD12D3E0072A050 /* TailRecursiveSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D761AD12D3E0072A050 /* TailRecursiveSink.swift */; }; + C8813D8E1AD12D3E0072A050 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D771AD12D3E0072A050 /* Throttle.swift */; }; + C8813D8F1AD12D3E0072A050 /* Variable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D781AD12D3E0072A050 /* Variable.swift */; }; + C8813D901AD12D3E0072A050 /* WhereObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D791AD12D3E0072A050 /* WhereObservable.swift */; }; + C8813D921AD12D540072A050 /* ScheduledObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D911AD12D540072A050 /* ScheduledObserver.swift */; }; + C89094F91AD04995000E8322 /* OperationQueueScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89094F81AD04995000E8322 /* OperationQueueScheduler.swift */; }; + C89A2C6F1ACDAC6300CAF23E /* Observable+AggregateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89A2C6E1ACDAC6300CAF23E /* Observable+AggregateTest.swift */; }; + C89C81931A87CCBF00AA00FF /* Rx.h in Headers */ = {isa = PBXBuildFile; fileRef = C89C81921A87CCBF00AA00FF /* Rx.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C89C81991A87CCBF00AA00FF /* Rx.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C89C818D1A87CCBF00AA00FF /* Rx.framework */; }; + C89C81B41A87CD0900AA00FF /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89C81AC1A87CD0900AA00FF /* Disposable.swift */; }; + C89C81B51A87CD0900AA00FF /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89C81AD1A87CD0900AA00FF /* Event.swift */; }; + C89C81B71A87CD0900AA00FF /* ObserverType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89C81AF1A87CD0900AA00FF /* ObserverType.swift */; }; + C89C81B81A87CD0900AA00FF /* Scheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89C81B11A87CD0900AA00FF /* Scheduler.swift */; }; + C89C81BE1A87E5BB00AA00FF /* MainScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89C81BD1A87E5BB00AA00FF /* MainScheduler.swift */; }; + C89C81C51A87F62900AA00FF /* DispatchQueueScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89C81C41A87F62900AA00FF /* DispatchQueueScheduler.swift */; }; + C8B7458E1ACF530A00C60106 /* DisposeBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B7458D1ACF530A00C60106 /* DisposeBase.swift */; }; + C8B8D9801ABE069900652C5D /* QueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B8D97F1ABE069900652C5D /* QueueTests.swift */; }; + C8B8D9821ABEE98E00652C5D /* Observable+Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B8D9811ABEE98E00652C5D /* Observable+Time.swift */; }; + C8B8D9861ABF28DF00652C5D /* Observable+Aggregate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B8D9851ABF28DF00652C5D /* Observable+Aggregate.swift */; }; + C8B8D9901AC010B900652C5D /* AsyncLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B8D98A1AC010B900652C5D /* AsyncLock.swift */; }; + C8B8D9931AC010B900652C5D /* Bag.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B8D98E1AC010B900652C5D /* Bag.swift */; }; + C8B8D9941AC010B900652C5D /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B8D98F1AC010B900652C5D /* Queue.swift */; }; + C8B8D9961AC0153C00652C5D /* Observable+TimeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B8D9951AC0153C00652C5D /* Observable+TimeTest.swift */; }; + C8C339CC1A90B5780016C6EB /* HotObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C339C31A90B5780016C6EB /* HotObservable.swift */; }; + C8C339CD1A90B5780016C6EB /* MockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C339C41A90B5780016C6EB /* MockObserver.swift */; }; + C8C339CE1A90B5780016C6EB /* TestObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C339C51A90B5780016C6EB /* TestObservable.swift */; }; + C8C339CF1A90B5780016C6EB /* TestObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C339C61A90B5780016C6EB /* TestObserver.swift */; }; + C8C339D01A90B5780016C6EB /* Recorded.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C339C71A90B5780016C6EB /* Recorded.swift */; }; + C8C339D11A90B5780016C6EB /* TestScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C339C91A90B5780016C6EB /* TestScheduler.swift */; }; + C8C339D21A90B5780016C6EB /* VirtualTimeSchedulerBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C339CA1A90B5780016C6EB /* VirtualTimeSchedulerBase.swift */; }; + C8C339D31A90B5780016C6EB /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C339CB1A90B5780016C6EB /* Subscription.swift */; }; + C8C339D51A90B8620016C6EB /* TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C339D41A90B8620016C6EB /* TestExtensions.swift */; }; + C8C339D71A90B8B10016C6EB /* Observable+MultipleTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C339D61A90B8B10016C6EB /* Observable+MultipleTest.swift */; }; + C8C339DB1A90C3C20016C6EB /* ImmediateSchedulerOnCurrentThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C339DA1A90C3C20016C6EB /* ImmediateSchedulerOnCurrentThread.swift */; }; + C8C339E21A9129F00016C6EB /* SingleAssignmentDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C339E11A9129F00016C6EB /* SingleAssignmentDisposable.swift */; }; + C8C339E41A912ACD0016C6EB /* DefaultDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C339E31A912ACD0016C6EB /* DefaultDisposable.swift */; }; + C8C43C461AA3A58A00CFEC97 /* ConnectableObservableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C43C451AA3A58A00CFEC97 /* ConnectableObservableType.swift */; }; + C8C43C481AA3A5C600CFEC97 /* SubjectType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C43C471AA3A5C600CFEC97 /* SubjectType.swift */; }; + C8D26ED51A914DA40067C793 /* AnonymousDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D26ED41A914DA40067C793 /* AnonymousDisposable.swift */; }; + C8DB02A21A8FD69100B7FE90 /* AssumptionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DB02A11A8FD69100B7FE90 /* AssumptionsTest.swift */; }; + C8DDCFC91AC6CA2A00414017 /* AnyObject+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DDCFC81AC6CA2A00414017 /* AnyObject+Rx.swift */; }; + C8DDCFCB1AC6CA5500414017 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DDCFCA1AC6CA5500414017 /* Error.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + C89C819A1A87CCBF00AA00FF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C89C81841A87CCBF00AA00FF /* Project object */; + proxyType = 1; + remoteGlobalIDString = C89C818C1A87CCBF00AA00FF; + remoteInfo = Rx; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + C815540B1A990D0C00C63152 /* SafeObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SafeObserver.swift; sourceTree = ""; }; + C81554121A990E1F00C63152 /* AnonymousObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AnonymousObserver.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + C81554131A990E1F00C63152 /* AutoDetachObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoDetachObserver.swift; sourceTree = ""; }; + C81554141A990E1F00C63152 /* ObserverBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserverBase.swift; sourceTree = ""; }; + C815541A1A990E3B00C63152 /* DisposedObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisposedObserver.swift; sourceTree = ""; }; + C815541C1A990E5000C63152 /* DoneObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoneObserver.swift; sourceTree = ""; }; + C815541E1A990E7100C63152 /* NopObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NopObserver.swift; sourceTree = ""; }; + C81554201A990E8E00C63152 /* AnonymousSafeObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AnonymousSafeObserver.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + C81554241A9925DA00C63152 /* Observable+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+Subscription.swift"; sourceTree = ""; }; + C81C49DF1AC3556A001D7A5E /* DisposeBag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisposeBag.swift; sourceTree = ""; }; + C81C58D31A8D3A9A008A4301 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Result.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + C83270971AB4725800618D46 /* ColdObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColdObservable.swift; sourceTree = ""; }; + C83270991AB4727500618D46 /* Observable.Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Observable.Extensions.swift; sourceTree = ""; }; + C8336D0C1AB849CB00B8F943 /* Observable+Binding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+Binding.swift"; sourceTree = ""; }; + C8336D0D1AB849CB00B8F943 /* Observable+Concurrency.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+Concurrency.swift"; sourceTree = ""; }; + C8336D0E1AB849CB00B8F943 /* Observable+Multiple.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+Multiple.swift"; sourceTree = ""; }; + C8336D0F1AB849CB00B8F943 /* Observable+Single.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+Single.swift"; sourceTree = ""; }; + C8336D101AB849CB00B8F943 /* Observable+StandardSequenceOperators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+StandardSequenceOperators.swift"; sourceTree = ""; }; + C86217FB1A8D650800985A9C /* Rx.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Rx.pch; sourceTree = ""; }; + C8649FBE1AB19476006984E1 /* SerialDisposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SerialDisposable.swift; sourceTree = ""; }; + C8649FC01AB194F3006984E1 /* Cancelable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cancelable.swift; sourceTree = ""; }; + C864E0D71ACB288000C2013F /* Lock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Lock.swift; sourceTree = ""; }; + C86603561AA1E0A7000460BD /* ObserverOf.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserverOf.swift; sourceTree = ""; }; + C86705241AA38D190048B3D5 /* Observable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = ""; }; + C86D1DE41ABD843A00F4308D /* Observable+Creation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+Creation.swift"; sourceTree = ""; }; + C86D1DE61ABD846D00F4308D /* SchedulerDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchedulerDefaults.swift; sourceTree = ""; }; + C86D6D751A8F88D2003BDD7C /* Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Rx.swift; sourceTree = ""; }; + C86D6D801A8FB8F3003BDD7C /* ConcurrencyTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConcurrencyTest.swift; sourceTree = ""; }; + C86D6D811A8FB8F3003BDD7C /* DisposableTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisposableTest.swift; sourceTree = ""; }; + C86D6D821A8FB8F3003BDD7C /* Observable+SingleTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+SingleTest.swift"; sourceTree = ""; }; + C86D6D831A8FB8F3003BDD7C /* RxTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxTest.swift; sourceTree = ""; }; + C8754A1F1A97D65E00BE09AD /* CompositeDisposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompositeDisposable.swift; sourceTree = ""; }; + C8781B831A93EBE40028125F /* Observable+StandardSequenceOperatorsTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+StandardSequenceOperatorsTest.swift"; sourceTree = ""; }; + C8813D631AD12D3E0072A050 /* Aggregate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Aggregate.swift; sourceTree = ""; }; + C8813D641AD12D3E0072A050 /* AnonymousObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnonymousObservable.swift; sourceTree = ""; }; + C8813D651AD12D3E0072A050 /* AsObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsObservable.swift; sourceTree = ""; }; + C8813D661AD12D3E0072A050 /* CombineLatest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombineLatest.swift; sourceTree = ""; }; + C8813D671AD12D3E0072A050 /* Concat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Concat.swift; sourceTree = ""; }; + C8813D681AD12D3E0072A050 /* ConcatSink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConcatSink.swift; sourceTree = ""; }; + C8813D691AD12D3E0072A050 /* ConnectableObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectableObservable.swift; sourceTree = ""; }; + C8813D6A1AD12D3E0072A050 /* DistinctUntilChanged.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DistinctUntilChanged.swift; sourceTree = ""; }; + C8813D6B1AD12D3E0072A050 /* Do.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Do.swift; sourceTree = ""; }; + C8813D6C1AD12D3E0072A050 /* Merge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Merge.swift; sourceTree = ""; }; + C8813D6D1AD12D3E0072A050 /* Multicast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Multicast.swift; sourceTree = ""; }; + C8813D6E1AD12D3E0072A050 /* ObservableBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableBase.swift; sourceTree = ""; }; + C8813D6F1AD12D3E0072A050 /* ObserveSingleOn.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserveSingleOn.swift; sourceTree = ""; }; + C8813D701AD12D3E0072A050 /* Producer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Producer.swift; sourceTree = ""; }; + C8813D711AD12D3E0072A050 /* RefCount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefCount.swift; sourceTree = ""; }; + C8813D721AD12D3E0072A050 /* Select.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Select.swift; sourceTree = ""; }; + C8813D731AD12D3E0072A050 /* Sink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sink.swift; sourceTree = ""; }; + C8813D741AD12D3E0072A050 /* Subject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Subject.swift; sourceTree = ""; }; + C8813D751AD12D3E0072A050 /* Switch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Switch.swift; sourceTree = ""; }; + C8813D761AD12D3E0072A050 /* TailRecursiveSink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TailRecursiveSink.swift; sourceTree = ""; }; + C8813D771AD12D3E0072A050 /* Throttle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Throttle.swift; sourceTree = ""; }; + C8813D781AD12D3E0072A050 /* Variable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Variable.swift; sourceTree = ""; }; + C8813D791AD12D3E0072A050 /* WhereObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhereObservable.swift; sourceTree = ""; }; + C8813D911AD12D540072A050 /* ScheduledObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScheduledObserver.swift; sourceTree = ""; }; + C89094F81AD04995000E8322 /* OperationQueueScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationQueueScheduler.swift; sourceTree = ""; }; + C89A2C6E1ACDAC6300CAF23E /* Observable+AggregateTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+AggregateTest.swift"; sourceTree = ""; }; + C89C818D1A87CCBF00AA00FF /* Rx.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Rx.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C89C81911A87CCBF00AA00FF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C89C81921A87CCBF00AA00FF /* Rx.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Rx.h; sourceTree = ""; }; + C89C81981A87CCBF00AA00FF /* RxTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RxTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + C89C819E1A87CCBF00AA00FF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C89C81AC1A87CD0900AA00FF /* Disposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Disposable.swift; sourceTree = ""; }; + C89C81AD1A87CD0900AA00FF /* Event.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = ""; }; + C89C81AF1A87CD0900AA00FF /* ObserverType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ObserverType.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + C89C81B11A87CD0900AA00FF /* Scheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scheduler.swift; sourceTree = ""; }; + C89C81BD1A87E5BB00AA00FF /* MainScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainScheduler.swift; sourceTree = ""; }; + C89C81C41A87F62900AA00FF /* DispatchQueueScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DispatchQueueScheduler.swift; sourceTree = ""; }; + C8B7458D1ACF530A00C60106 /* DisposeBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisposeBase.swift; sourceTree = ""; }; + C8B8D97F1ABE069900652C5D /* QueueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueueTests.swift; sourceTree = ""; }; + C8B8D9811ABEE98E00652C5D /* Observable+Time.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+Time.swift"; sourceTree = ""; }; + C8B8D9851ABF28DF00652C5D /* Observable+Aggregate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+Aggregate.swift"; sourceTree = ""; }; + C8B8D98A1AC010B900652C5D /* AsyncLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncLock.swift; sourceTree = ""; }; + C8B8D98E1AC010B900652C5D /* Bag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bag.swift; sourceTree = ""; }; + C8B8D98F1AC010B900652C5D /* Queue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Queue.swift; sourceTree = ""; }; + C8B8D9951AC0153C00652C5D /* Observable+TimeTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+TimeTest.swift"; sourceTree = ""; }; + C8C339C31A90B5780016C6EB /* HotObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HotObservable.swift; sourceTree = ""; }; + C8C339C41A90B5780016C6EB /* MockObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockObserver.swift; sourceTree = ""; }; + C8C339C51A90B5780016C6EB /* TestObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestObservable.swift; sourceTree = ""; }; + C8C339C61A90B5780016C6EB /* TestObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestObserver.swift; sourceTree = ""; }; + C8C339C71A90B5780016C6EB /* Recorded.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Recorded.swift; sourceTree = ""; }; + C8C339C91A90B5780016C6EB /* TestScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestScheduler.swift; sourceTree = ""; }; + C8C339CA1A90B5780016C6EB /* VirtualTimeSchedulerBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VirtualTimeSchedulerBase.swift; sourceTree = ""; }; + C8C339CB1A90B5780016C6EB /* Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = ""; }; + C8C339D41A90B8620016C6EB /* TestExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestExtensions.swift; sourceTree = ""; }; + C8C339D61A90B8B10016C6EB /* Observable+MultipleTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+MultipleTest.swift"; sourceTree = ""; }; + C8C339DA1A90C3C20016C6EB /* ImmediateSchedulerOnCurrentThread.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImmediateSchedulerOnCurrentThread.swift; sourceTree = ""; }; + C8C339E11A9129F00016C6EB /* SingleAssignmentDisposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SingleAssignmentDisposable.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + C8C339E31A912ACD0016C6EB /* DefaultDisposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultDisposable.swift; sourceTree = ""; }; + C8C43C451AA3A58A00CFEC97 /* ConnectableObservableType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectableObservableType.swift; sourceTree = ""; }; + C8C43C471AA3A5C600CFEC97 /* SubjectType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubjectType.swift; sourceTree = ""; }; + C8D26ED41A914DA40067C793 /* AnonymousDisposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnonymousDisposable.swift; sourceTree = ""; }; + C8DB02A11A8FD69100B7FE90 /* AssumptionsTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssumptionsTest.swift; sourceTree = ""; }; + C8DDCFC81AC6CA2A00414017 /* AnyObject+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AnyObject+Rx.swift"; sourceTree = ""; }; + C8DDCFCA1AC6CA5500414017 /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + C89C81891A87CCBF00AA00FF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C89C81951A87CCBF00AA00FF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C89C81991A87CCBF00AA00FF /* Rx.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + C815540A1A990CFF00C63152 /* Observers */ = { + isa = PBXGroup; + children = ( + C81554121A990E1F00C63152 /* AnonymousObserver.swift */, + C81554201A990E8E00C63152 /* AnonymousSafeObserver.swift */, + C81554131A990E1F00C63152 /* AutoDetachObserver.swift */, + C81554141A990E1F00C63152 /* ObserverBase.swift */, + C815540B1A990D0C00C63152 /* SafeObserver.swift */, + C815541A1A990E3B00C63152 /* DisposedObserver.swift */, + C815541C1A990E5000C63152 /* DoneObserver.swift */, + C815541E1A990E7100C63152 /* NopObserver.swift */, + ); + path = Observers; + sourceTree = ""; + }; + C8336CFA1AB849CB00B8F943 /* Observables */ = { + isa = PBXGroup; + children = ( + C8813D621AD12D3E0072A050 /* Implementations */, + C8B8D9851ABF28DF00652C5D /* Observable+Aggregate.swift */, + C8336D0C1AB849CB00B8F943 /* Observable+Binding.swift */, + C8336D0D1AB849CB00B8F943 /* Observable+Concurrency.swift */, + C86D1DE41ABD843A00F4308D /* Observable+Creation.swift */, + C8336D0E1AB849CB00B8F943 /* Observable+Multiple.swift */, + C8336D0F1AB849CB00B8F943 /* Observable+Single.swift */, + C8336D101AB849CB00B8F943 /* Observable+StandardSequenceOperators.swift */, + C8B8D9811ABEE98E00652C5D /* Observable+Time.swift */, + ); + path = Observables; + sourceTree = ""; + }; + C83AA0161A8A1DD500E0605C /* Disposables */ = { + isa = PBXGroup; + children = ( + C8D26ED41A914DA40067C793 /* AnonymousDisposable.swift */, + C8C339E11A9129F00016C6EB /* SingleAssignmentDisposable.swift */, + C8C339E31A912ACD0016C6EB /* DefaultDisposable.swift */, + C8754A1F1A97D65E00BE09AD /* CompositeDisposable.swift */, + C8649FBE1AB19476006984E1 /* SerialDisposable.swift */, + C81C49DF1AC3556A001D7A5E /* DisposeBag.swift */, + C8B7458D1ACF530A00C60106 /* DisposeBase.swift */, + ); + path = Disposables; + sourceTree = ""; + }; + C86D6D7D1A8FB8F3003BDD7C /* TestImplementations */ = { + isa = PBXGroup; + children = ( + C8C339C21A90B5780016C6EB /* Mocks */, + C8C339C81A90B5780016C6EB /* Schedulers */, + C8C339C71A90B5780016C6EB /* Recorded.swift */, + C8C339CB1A90B5780016C6EB /* Subscription.swift */, + C8C339D41A90B8620016C6EB /* TestExtensions.swift */, + ); + path = TestImplementations; + sourceTree = ""; + }; + C86D6D7F1A8FB8F3003BDD7C /* Tests */ = { + isa = PBXGroup; + children = ( + C8DB02A11A8FD69100B7FE90 /* AssumptionsTest.swift */, + C86D6D801A8FB8F3003BDD7C /* ConcurrencyTest.swift */, + C86D6D811A8FB8F3003BDD7C /* DisposableTest.swift */, + C89A2C6E1ACDAC6300CAF23E /* Observable+AggregateTest.swift */, + C8C339D61A90B8B10016C6EB /* Observable+MultipleTest.swift */, + C86D6D821A8FB8F3003BDD7C /* Observable+SingleTest.swift */, + C8781B831A93EBE40028125F /* Observable+StandardSequenceOperatorsTest.swift */, + C8B8D9951AC0153C00652C5D /* Observable+TimeTest.swift */, + C8B8D97F1ABE069900652C5D /* QueueTests.swift */, + C86D6D831A8FB8F3003BDD7C /* RxTest.swift */, + ); + path = Tests; + sourceTree = ""; + }; + C8813D621AD12D3E0072A050 /* Implementations */ = { + isa = PBXGroup; + children = ( + C8813D631AD12D3E0072A050 /* Aggregate.swift */, + C8813D641AD12D3E0072A050 /* AnonymousObservable.swift */, + C8813D651AD12D3E0072A050 /* AsObservable.swift */, + C8813D661AD12D3E0072A050 /* CombineLatest.swift */, + C8813D671AD12D3E0072A050 /* Concat.swift */, + C8813D681AD12D3E0072A050 /* ConcatSink.swift */, + C8813D691AD12D3E0072A050 /* ConnectableObservable.swift */, + C8813D6A1AD12D3E0072A050 /* DistinctUntilChanged.swift */, + C8813D6B1AD12D3E0072A050 /* Do.swift */, + C8813D6C1AD12D3E0072A050 /* Merge.swift */, + C8813D6D1AD12D3E0072A050 /* Multicast.swift */, + C8813D6E1AD12D3E0072A050 /* ObservableBase.swift */, + C8813D6F1AD12D3E0072A050 /* ObserveSingleOn.swift */, + C8813D701AD12D3E0072A050 /* Producer.swift */, + C8813D711AD12D3E0072A050 /* RefCount.swift */, + C8813D721AD12D3E0072A050 /* Select.swift */, + C8813D731AD12D3E0072A050 /* Sink.swift */, + C8813D741AD12D3E0072A050 /* Subject.swift */, + C8813D751AD12D3E0072A050 /* Switch.swift */, + C8813D761AD12D3E0072A050 /* TailRecursiveSink.swift */, + C8813D771AD12D3E0072A050 /* Throttle.swift */, + C8813D781AD12D3E0072A050 /* Variable.swift */, + C8813D791AD12D3E0072A050 /* WhereObservable.swift */, + C8813D911AD12D540072A050 /* ScheduledObserver.swift */, + ); + path = Implementations; + sourceTree = ""; + }; + C89C81831A87CCBF00AA00FF = { + isa = PBXGroup; + children = ( + C89C818F1A87CCBF00AA00FF /* Rx */, + C89C819C1A87CCBF00AA00FF /* RxTests */, + C89C818E1A87CCBF00AA00FF /* Products */, + ); + sourceTree = ""; + }; + C89C818E1A87CCBF00AA00FF /* Products */ = { + isa = PBXGroup; + children = ( + C89C818D1A87CCBF00AA00FF /* Rx.framework */, + C89C81981A87CCBF00AA00FF /* RxTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + C89C818F1A87CCBF00AA00FF /* Rx */ = { + isa = PBXGroup; + children = ( + C89C81921A87CCBF00AA00FF /* Rx.h */, + C86D6D751A8F88D2003BDD7C /* Rx.swift */, + C89C81AD1A87CD0900AA00FF /* Event.swift */, + C8DDCFCA1AC6CA5500414017 /* Error.swift */, + C81C58D31A8D3A9A008A4301 /* Result.swift */, + C89C81AF1A87CD0900AA00FF /* ObserverType.swift */, + C86603561AA1E0A7000460BD /* ObserverOf.swift */, + C86705241AA38D190048B3D5 /* Observable.swift */, + C89C81AC1A87CD0900AA00FF /* Disposable.swift */, + C8649FC01AB194F3006984E1 /* Cancelable.swift */, + C89C81B11A87CD0900AA00FF /* Scheduler.swift */, + C8336CFA1AB849CB00B8F943 /* Observables */, + C8B8D9891AC010B900652C5D /* Concurrency */, + C8B8D98D1AC010B900652C5D /* DataStructures */, + C89C81901A87CCBF00AA00FF /* Supporting Files */, + C89C81B01A87CD0900AA00FF /* Scheduler */, + C83AA0161A8A1DD500E0605C /* Disposables */, + C815540A1A990CFF00C63152 /* Observers */, + C8C43C441AA3A57B00CFEC97 /* Subjects */, + C81554241A9925DA00C63152 /* Observable+Subscription.swift */, + C8DDCFC81AC6CA2A00414017 /* AnyObject+Rx.swift */, + ); + path = Rx; + sourceTree = ""; + }; + C89C81901A87CCBF00AA00FF /* Supporting Files */ = { + isa = PBXGroup; + children = ( + C89C81911A87CCBF00AA00FF /* Info.plist */, + C86217FB1A8D650800985A9C /* Rx.pch */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + C89C819C1A87CCBF00AA00FF /* RxTests */ = { + isa = PBXGroup; + children = ( + C86D6D7D1A8FB8F3003BDD7C /* TestImplementations */, + C86D6D7F1A8FB8F3003BDD7C /* Tests */, + C89C819D1A87CCBF00AA00FF /* Supporting Files */, + ); + path = RxTests; + sourceTree = ""; + }; + C89C819D1A87CCBF00AA00FF /* Supporting Files */ = { + isa = PBXGroup; + children = ( + C89C819E1A87CCBF00AA00FF /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + C89C81B01A87CD0900AA00FF /* Scheduler */ = { + isa = PBXGroup; + children = ( + C86D1DE61ABD846D00F4308D /* SchedulerDefaults.swift */, + C89C81BD1A87E5BB00AA00FF /* MainScheduler.swift */, + C89C81C41A87F62900AA00FF /* DispatchQueueScheduler.swift */, + C8C339DA1A90C3C20016C6EB /* ImmediateSchedulerOnCurrentThread.swift */, + C89094F81AD04995000E8322 /* OperationQueueScheduler.swift */, + ); + path = Scheduler; + sourceTree = ""; + }; + C8B8D9891AC010B900652C5D /* Concurrency */ = { + isa = PBXGroup; + children = ( + C8B8D98A1AC010B900652C5D /* AsyncLock.swift */, + C864E0D71ACB288000C2013F /* Lock.swift */, + ); + path = Concurrency; + sourceTree = ""; + }; + C8B8D98D1AC010B900652C5D /* DataStructures */ = { + isa = PBXGroup; + children = ( + C8B8D98E1AC010B900652C5D /* Bag.swift */, + C8B8D98F1AC010B900652C5D /* Queue.swift */, + ); + path = DataStructures; + sourceTree = ""; + }; + C8C339C21A90B5780016C6EB /* Mocks */ = { + isa = PBXGroup; + children = ( + C8C339C31A90B5780016C6EB /* HotObservable.swift */, + C83270971AB4725800618D46 /* ColdObservable.swift */, + C8C339C41A90B5780016C6EB /* MockObserver.swift */, + C8C339C51A90B5780016C6EB /* TestObservable.swift */, + C8C339C61A90B5780016C6EB /* TestObserver.swift */, + C83270991AB4727500618D46 /* Observable.Extensions.swift */, + ); + path = Mocks; + sourceTree = ""; + }; + C8C339C81A90B5780016C6EB /* Schedulers */ = { + isa = PBXGroup; + children = ( + C8C339C91A90B5780016C6EB /* TestScheduler.swift */, + C8C339CA1A90B5780016C6EB /* VirtualTimeSchedulerBase.swift */, + ); + path = Schedulers; + sourceTree = ""; + }; + C8C43C441AA3A57B00CFEC97 /* Subjects */ = { + isa = PBXGroup; + children = ( + C8C43C451AA3A58A00CFEC97 /* ConnectableObservableType.swift */, + C8C43C471AA3A5C600CFEC97 /* SubjectType.swift */, + ); + path = Subjects; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + C89C818A1A87CCBF00AA00FF /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + C89C81931A87CCBF00AA00FF /* Rx.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + C89C818C1A87CCBF00AA00FF /* Rx */ = { + isa = PBXNativeTarget; + buildConfigurationList = C89C81A31A87CCBF00AA00FF /* Build configuration list for PBXNativeTarget "Rx" */; + buildPhases = ( + C89C81881A87CCBF00AA00FF /* Sources */, + C89C81891A87CCBF00AA00FF /* Frameworks */, + C89C818A1A87CCBF00AA00FF /* Headers */, + C89C818B1A87CCBF00AA00FF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Rx; + productName = Rx; + productReference = C89C818D1A87CCBF00AA00FF /* Rx.framework */; + productType = "com.apple.product-type.framework"; + }; + C89C81971A87CCBF00AA00FF /* RxTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = C89C81A61A87CCBF00AA00FF /* Build configuration list for PBXNativeTarget "RxTests" */; + buildPhases = ( + C89C81941A87CCBF00AA00FF /* Sources */, + C89C81951A87CCBF00AA00FF /* Frameworks */, + C89C81961A87CCBF00AA00FF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + C89C819B1A87CCBF00AA00FF /* PBXTargetDependency */, + ); + name = RxTests; + productName = RxTests; + productReference = C89C81981A87CCBF00AA00FF /* RxTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + C89C81841A87CCBF00AA00FF /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = "Krunoslav Zaher"; + TargetAttributes = { + C89C818C1A87CCBF00AA00FF = { + CreatedOnToolsVersion = 6.1.1; + }; + C89C81971A87CCBF00AA00FF = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = C89C81871A87CCBF00AA00FF /* Build configuration list for PBXProject "Rx" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = C89C81831A87CCBF00AA00FF; + productRefGroup = C89C818E1A87CCBF00AA00FF /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C89C818C1A87CCBF00AA00FF /* Rx */, + C89C81971A87CCBF00AA00FF /* RxTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + C89C818B1A87CCBF00AA00FF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C89C81961A87CCBF00AA00FF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + C89C81881A87CCBF00AA00FF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C8DDCFC91AC6CA2A00414017 /* AnyObject+Rx.swift in Sources */, + C89C81B51A87CD0900AA00FF /* Event.swift in Sources */, + C8813D7B1AD12D3E0072A050 /* AnonymousObservable.swift in Sources */, + C8813D821AD12D3E0072A050 /* Do.swift in Sources */, + C89C81C51A87F62900AA00FF /* DispatchQueueScheduler.swift in Sources */, + C89094F91AD04995000E8322 /* OperationQueueScheduler.swift in Sources */, + C81554251A9925DA00C63152 /* Observable+Subscription.swift in Sources */, + C8C43C461AA3A58A00CFEC97 /* ConnectableObservableType.swift in Sources */, + C86D1DE51ABD843A00F4308D /* Observable+Creation.swift in Sources */, + C86603571AA1E0A7000460BD /* ObserverOf.swift in Sources */, + C89C81B81A87CD0900AA00FF /* Scheduler.swift in Sources */, + C8813D8D1AD12D3E0072A050 /* TailRecursiveSink.swift in Sources */, + C8813D8A1AD12D3E0072A050 /* Sink.swift in Sources */, + C8B8D9901AC010B900652C5D /* AsyncLock.swift in Sources */, + C86705251AA38D190048B3D5 /* Observable.swift in Sources */, + C89C81B71A87CD0900AA00FF /* ObserverType.swift in Sources */, + C81C58D41A8D3A9A008A4301 /* Result.swift in Sources */, + C8C339DB1A90C3C20016C6EB /* ImmediateSchedulerOnCurrentThread.swift in Sources */, + C8C43C481AA3A5C600CFEC97 /* SubjectType.swift in Sources */, + C86D1DE71ABD846D00F4308D /* SchedulerDefaults.swift in Sources */, + C8C339E41A912ACD0016C6EB /* DefaultDisposable.swift in Sources */, + C81554171A990E1F00C63152 /* AutoDetachObserver.swift in Sources */, + C8C339E21A9129F00016C6EB /* SingleAssignmentDisposable.swift in Sources */, + C8813D851AD12D3E0072A050 /* ObservableBase.swift in Sources */, + C8B8D9941AC010B900652C5D /* Queue.swift in Sources */, + C8336D231AB849CB00B8F943 /* Observable+Multiple.swift in Sources */, + C815541D1A990E5000C63152 /* DoneObserver.swift in Sources */, + C81554161A990E1F00C63152 /* AnonymousObserver.swift in Sources */, + C8649FBF1AB19476006984E1 /* SerialDisposable.swift in Sources */, + C8B8D9861ABF28DF00652C5D /* Observable+Aggregate.swift in Sources */, + C89C81BE1A87E5BB00AA00FF /* MainScheduler.swift in Sources */, + C8336D221AB849CB00B8F943 /* Observable+Concurrency.swift in Sources */, + C815541B1A990E3B00C63152 /* DisposedObserver.swift in Sources */, + C8813D901AD12D3E0072A050 /* WhereObservable.swift in Sources */, + C815540C1A990D0C00C63152 /* SafeObserver.swift in Sources */, + C8813D7C1AD12D3E0072A050 /* AsObservable.swift in Sources */, + C8813D8C1AD12D3E0072A050 /* Switch.swift in Sources */, + C8813D7A1AD12D3E0072A050 /* Aggregate.swift in Sources */, + C8649FC11AB194F3006984E1 /* Cancelable.swift in Sources */, + C8754A201A97D65E00BE09AD /* CompositeDisposable.swift in Sources */, + C8813D8F1AD12D3E0072A050 /* Variable.swift in Sources */, + C8813D921AD12D540072A050 /* ScheduledObserver.swift in Sources */, + C8B8D9931AC010B900652C5D /* Bag.swift in Sources */, + C86D6D761A8F88D2003BDD7C /* Rx.swift in Sources */, + C81554181A990E1F00C63152 /* ObserverBase.swift in Sources */, + C8813D831AD12D3E0072A050 /* Merge.swift in Sources */, + C8D26ED51A914DA40067C793 /* AnonymousDisposable.swift in Sources */, + C8813D7D1AD12D3E0072A050 /* CombineLatest.swift in Sources */, + C8B8D9821ABEE98E00652C5D /* Observable+Time.swift in Sources */, + C81C49E01AC3556A001D7A5E /* DisposeBag.swift in Sources */, + C8336D211AB849CB00B8F943 /* Observable+Binding.swift in Sources */, + C8B7458E1ACF530A00C60106 /* DisposeBase.swift in Sources */, + C8813D7E1AD12D3E0072A050 /* Concat.swift in Sources */, + C8813D7F1AD12D3E0072A050 /* ConcatSink.swift in Sources */, + C8813D861AD12D3E0072A050 /* ObserveSingleOn.swift in Sources */, + C8813D841AD12D3E0072A050 /* Multicast.swift in Sources */, + C8336D251AB849CB00B8F943 /* Observable+StandardSequenceOperators.swift in Sources */, + C8336D241AB849CB00B8F943 /* Observable+Single.swift in Sources */, + C8813D8E1AD12D3E0072A050 /* Throttle.swift in Sources */, + C81554211A990E8E00C63152 /* AnonymousSafeObserver.swift in Sources */, + C8813D801AD12D3E0072A050 /* ConnectableObservable.swift in Sources */, + C89C81B41A87CD0900AA00FF /* Disposable.swift in Sources */, + C8813D8B1AD12D3E0072A050 /* Subject.swift in Sources */, + C864E0D81ACB288000C2013F /* Lock.swift in Sources */, + C815541F1A990E7100C63152 /* NopObserver.swift in Sources */, + C8813D871AD12D3E0072A050 /* Producer.swift in Sources */, + C8DDCFCB1AC6CA5500414017 /* Error.swift in Sources */, + C8813D891AD12D3E0072A050 /* Select.swift in Sources */, + C8813D881AD12D3E0072A050 /* RefCount.swift in Sources */, + C8813D811AD12D3E0072A050 /* DistinctUntilChanged.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C89C81941A87CCBF00AA00FF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C8C339D21A90B5780016C6EB /* VirtualTimeSchedulerBase.swift in Sources */, + C89A2C6F1ACDAC6300CAF23E /* Observable+AggregateTest.swift in Sources */, + C86D6D871A8FB8F3003BDD7C /* Observable+SingleTest.swift in Sources */, + C86D6D861A8FB8F3003BDD7C /* DisposableTest.swift in Sources */, + C8C339CE1A90B5780016C6EB /* TestObservable.swift in Sources */, + C8C339D31A90B5780016C6EB /* Subscription.swift in Sources */, + C8DB02A21A8FD69100B7FE90 /* AssumptionsTest.swift in Sources */, + C832709A1AB4727500618D46 /* Observable.Extensions.swift in Sources */, + C8B8D9801ABE069900652C5D /* QueueTests.swift in Sources */, + C8B8D9961AC0153C00652C5D /* Observable+TimeTest.swift in Sources */, + C8781B841A93EBE40028125F /* Observable+StandardSequenceOperatorsTest.swift in Sources */, + C83270981AB4725800618D46 /* ColdObservable.swift in Sources */, + C8C339D11A90B5780016C6EB /* TestScheduler.swift in Sources */, + C8C339D01A90B5780016C6EB /* Recorded.swift in Sources */, + C86D6D851A8FB8F3003BDD7C /* ConcurrencyTest.swift in Sources */, + C8C339D51A90B8620016C6EB /* TestExtensions.swift in Sources */, + C86D6D881A8FB8F3003BDD7C /* RxTest.swift in Sources */, + C8C339D71A90B8B10016C6EB /* Observable+MultipleTest.swift in Sources */, + C8C339CC1A90B5780016C6EB /* HotObservable.swift in Sources */, + C8C339CD1A90B5780016C6EB /* MockObserver.swift in Sources */, + C8C339CF1A90B5780016C6EB /* TestObserver.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + C89C819B1A87CCBF00AA00FF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C89C818C1A87CCBF00AA00FF /* Rx */; + targetProxy = C89C819A1A87CCBF00AA00FF /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + C89C81A11A87CCBF00AA00FF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_SWIFT_FLAGS = "-D USE_FUNCTIONAL_NAMES"; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + C89C81A21A87CCBF00AA00FF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + CURRENT_PROJECT_VERSION = 1; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_SWIFT_FLAGS = "-D USE_FUNCTIONAL_NAMES"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + C89C81A41A87CCBF00AA00FF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = Rx/Rx.pch; + INFOPLIST_FILE = Rx/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = "-D USE_FUNCTIONAL_NAMES -D DEBUG"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + C89C81A51A87CCBF00AA00FF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = Rx/Rx.pch; + INFOPLIST_FILE = Rx/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; + C89C81A71A87CCBF00AA00FF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = RxTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = "-D USE_FUNCTIONAL_NAMES -D DEBUG"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + C89C81A81A87CCBF00AA00FF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = RxTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C89C81871A87CCBF00AA00FF /* Build configuration list for PBXProject "Rx" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C89C81A11A87CCBF00AA00FF /* Debug */, + C89C81A21A87CCBF00AA00FF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C89C81A31A87CCBF00AA00FF /* Build configuration list for PBXNativeTarget "Rx" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C89C81A41A87CCBF00AA00FF /* Debug */, + C89C81A51A87CCBF00AA00FF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C89C81A61A87CCBF00AA00FF /* Build configuration list for PBXNativeTarget "RxTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C89C81A71A87CCBF00AA00FF /* Debug */, + C89C81A81A87CCBF00AA00FF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = C89C81841A87CCBF00AA00FF /* Project object */; +} diff --git a/Rx.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Rx/Rx.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from Rx.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to Rx/Rx.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/Rx/Rx.xcodeproj/project.xcworkspace/xcuserdata/kzaher.xcuserdatad/WorkspaceSettings.xcsettings b/Rx/Rx.xcodeproj/project.xcworkspace/xcuserdata/kzaher.xcuserdatad/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..bfffcfe0 --- /dev/null +++ b/Rx/Rx.xcodeproj/project.xcworkspace/xcuserdata/kzaher.xcuserdatad/WorkspaceSettings.xcsettings @@ -0,0 +1,10 @@ + + + + + HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges + + SnapshotAutomaticallyBeforeSignificantChanges + + + diff --git a/Rx/Rx.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/Rx.xcscheme b/Rx/Rx.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/Rx.xcscheme new file mode 100644 index 00000000..ed69608f --- /dev/null +++ b/Rx/Rx.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/Rx.xcscheme @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Rx/Rx.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/xcschememanagement.plist b/Rx/Rx.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000..86a64875 --- /dev/null +++ b/Rx/Rx.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,27 @@ + + + + + SchemeUserState + + Rx.xcscheme + + orderHint + 2 + + + SuppressBuildableAutocreation + + C89C818C1A87CCBF00AA00FF + + primary + + + C89C81971A87CCBF00AA00FF + + primary + + + + + diff --git a/Rx/Rx/AnyObject+Rx.swift b/Rx/Rx/AnyObject+Rx.swift new file mode 100644 index 00000000..959797d5 --- /dev/null +++ b/Rx/Rx/AnyObject+Rx.swift @@ -0,0 +1,27 @@ +// +// AnyObject+Rx.swift +// Rx +// +// Created by Krunoslav Zaher on 3/28/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public func castOrFail(result: AnyObject!) -> Result { + if let typedResult = result as? T { + return success(typedResult) + } + else { + return .Error(CastError) + } +} + +public func makeOptionalResult(result: T) -> Result { + return success(result) +} + +public func makeOptional(result: T) -> T? { + return result +} + diff --git a/Rx/Rx/Cancelable.swift b/Rx/Rx/Cancelable.swift new file mode 100644 index 00000000..b280ef1e --- /dev/null +++ b/Rx/Rx/Cancelable.swift @@ -0,0 +1,13 @@ +// +// Cancelable.swift +// Rx +// +// Created by Krunoslav Zaher on 3/12/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public protocol Cancelable : Disposable { + var disposed: Bool { get } +} \ No newline at end of file diff --git a/Rx/Rx/Concurrency/AsyncLock.swift b/Rx/Rx/Concurrency/AsyncLock.swift new file mode 100644 index 00000000..56991903 --- /dev/null +++ b/Rx/Rx/Concurrency/AsyncLock.swift @@ -0,0 +1,74 @@ +// +// AsyncLock.swift +// Rx +// +// Created by Krunoslav Zaher on 3/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class AsyncLock : Disposable { + typealias Action = () -> Result + + private var lock: Lock = Lock() + + private var queue: Queue = Queue(capacity: 2) + private var isAcquired: Bool = false + private var hasFaulted: Bool = false + + init() { + + } + + func wait(action: Action) -> Result { + let isOwner = lock.calculateLocked { () -> Bool in + if self.hasFaulted { + return false + } + + self.queue.enqueue(action) + let isOwner = !self.isAcquired + self.isAcquired = true + + return isOwner + } + + if !isOwner { + return SuccessResult + } + + while true { + let nextAction = lock.calculateLocked { () -> Action? in + if self.queue.count > 0 { + return self.queue.dequeue() + } + else { + self.isAcquired = false + return nil + } + } + + if let nextAction = nextAction { + let executeResult = nextAction() >>! { e in + self.dispose() + return .Error(e) + } + + if executeResult.error != nil { + return executeResult + } + } + else { + return SuccessResult + } + } + } + + func dispose() { + lock.performLocked { oldState in + self.queue = Queue(capacity: 2) + self.hasFaulted = true + } + } +} \ No newline at end of file diff --git a/Rx/Rx/Concurrency/Lock.swift b/Rx/Rx/Concurrency/Lock.swift new file mode 100644 index 00000000..8422db90 --- /dev/null +++ b/Rx/Rx/Concurrency/Lock.swift @@ -0,0 +1,30 @@ +// +// Lock.swift +// Rx +// +// Created by Krunoslav Zaher on 3/31/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public struct Lock { + private var _lock = OS_SPINLOCK_INIT + + public init() { + + } + + public mutating func performLocked(@noescape action: () -> Void) { + OSSpinLockLock(&_lock) + action() + OSSpinLockUnlock(&_lock) + } + + public mutating func calculateLocked(@noescape action: () -> T) -> T { + OSSpinLockLock(&_lock) + let result = action() + OSSpinLockUnlock(&_lock) + return result + } +} \ No newline at end of file diff --git a/Rx/Rx/DataStructures/Bag.swift b/Rx/Rx/DataStructures/Bag.swift new file mode 100644 index 00000000..b859284e --- /dev/null +++ b/Rx/Rx/DataStructures/Bag.swift @@ -0,0 +1,59 @@ +// +// Bag.swift +// Rx +// +// Created by Krunoslav Zaher on 2/28/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +private struct BagPrivate { + static let maxElements = Bag.KeyType.max - 1 // this is guarding from theoretical endless loop +} + +public struct Bag { + public typealias KeyType = Int + + private var map: [KeyType: Element] = Dictionary(minimumCapacity: 5) + private var nextKey = KeyType.min + + public init() { + } + + public var count: Int { + get { + return map.count + } + } + + public mutating func put(x: Element) -> KeyType { + if map.count >= BagPrivate.maxElements { + rxFatalError("Too many elements") + } + + while map[nextKey] != nil { + nextKey++ + } + + map[nextKey] = x + + return nextKey + } + + public var all: [Element] + { + get { + return self.map.values.array + } + } + + public mutating func removeAll() { + map.removeAll(keepCapacity: false) + } + + public mutating func removeKey(key: KeyType) -> Element? { + return map.removeValueForKey(key) + } + +} \ No newline at end of file diff --git a/Rx/Rx/DataStructures/Queue.swift b/Rx/Rx/DataStructures/Queue.swift new file mode 100644 index 00000000..85c75b17 --- /dev/null +++ b/Rx/Rx/DataStructures/Queue.swift @@ -0,0 +1,100 @@ +// +// Queue.swift +// Rx +// +// Created by Krunoslav Zaher on 3/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public struct Queue { + let resizeFactor = 2 + + private var storage: [T?] + private var _count: Int + private var pushNextIndex: Int + private var initialCapacity: Int + + public init(capacity: Int) { + initialCapacity = capacity + + storage = [] + _count = 0 + pushNextIndex = 0 + + resizeTo(capacity) + } + + private var dequeueIndex: Int { + get { + var index = pushNextIndex - count + return index < 0 ? index + self.storage.count : index + } + } + + public var count: Int { + get { + return _count + } + } + + public func peek() -> T { + contract(count > 0) + + return storage[dequeueIndex]! + } + + mutating private func resizeTo(size: Int) { + var newStorage: [T?] = [] + newStorage.reserveCapacity(size) + + var count = _count + + for var i = 0; i < count; ++i { + // does swift array have some more efficient methods of copying? + newStorage.append(dequeue()) + } + + while newStorage.count < size { + newStorage.append(nil) + } + + _count = count + pushNextIndex = count + storage = newStorage + } + + public mutating func enqueue(item: T) { + let queueFull = count == storage.count + if count == storage.count { + resizeTo(storage.count * resizeFactor) + } + + storage[pushNextIndex] = item + pushNextIndex++ + _count = _count + 1 + + if pushNextIndex >= storage.count { + pushNextIndex -= storage.count + } + } + + public mutating func dequeue() -> T { + contract(count > 0) + + let index = dequeueIndex + let value = storage[index]! + + storage[index] = nil + + _count = _count - 1 + + let downsizeLimit = storage.count / (resizeFactor * resizeFactor) + if _count < downsizeLimit && downsizeLimit >= initialCapacity { + resizeTo(storage.count / resizeFactor) + } + + return value + } +} \ No newline at end of file diff --git a/Rx/Rx/Disposable.swift b/Rx/Rx/Disposable.swift new file mode 100644 index 00000000..f8c2cdcd --- /dev/null +++ b/Rx/Rx/Disposable.swift @@ -0,0 +1,36 @@ +// +// Disposable.swift +// Rx +// +// Created by Krunoslav Zaher on 2/8/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public protocol Disposable +{ + func dispose() +} + +public func allSucceedOrDispose(disposables: [Result]) -> Result { + let errors = disposables.filter { d in d.error != nil } + let numberOfFailures = errors.count + if numberOfFailures == 0 { + return success(CompositeDisposable(disposables: disposables.map { d in d.value!})) + } + else { + // dispose all of the resources + let diposeResult: [Void] = disposables.map { d in + switch d { + case .Success(let disposable): + disposable.value.dispose() + break; + case .Error(let error): break + } + + return () + } + return createCompositeFailure(errors) + } +} \ No newline at end of file diff --git a/Rx/Rx/Disposables/AnonymousDisposable.swift b/Rx/Rx/Disposables/AnonymousDisposable.swift new file mode 100644 index 00000000..efc66748 --- /dev/null +++ b/Rx/Rx/Disposables/AnonymousDisposable.swift @@ -0,0 +1,33 @@ +// +// AnonymousDisposable.swift +// Rx +// +// Created by Krunoslav Zaher on 2/15/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public class AnonymousDisposable : DisposeBase, Disposable { + public typealias DisposeAction = () -> Void + + var lock = Lock() + var disposeAction: DisposeAction? + + public init(_ disposeAction: DisposeAction) { + self.disposeAction = disposeAction + super.init() + } + + public func dispose() { + let toDispose: DisposeAction? = lock.calculateLocked { + var action = self.disposeAction + self.disposeAction = nil + return action + } + + if let toDispose = toDispose { + toDispose() + } + } +} \ No newline at end of file diff --git a/Rx/Rx/Disposables/CompositeDisposable.swift b/Rx/Rx/Disposables/CompositeDisposable.swift new file mode 100644 index 00000000..ecca6d25 --- /dev/null +++ b/Rx/Rx/Disposables/CompositeDisposable.swift @@ -0,0 +1,108 @@ +// +// CompositeDisposable.swift +// Rx +// +// Created by Krunoslav Zaher on 2/20/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public class CompositeDisposable : DisposeBase, Disposable { + public typealias BagKey = Bag.KeyType + + typealias State = ( + disposables: MutatingBox>!, + disposed: Bool + ) + + var lock: Lock = Lock() + var state: State = ( + disposables: MutatingBox(Bag()), + disposed: false + ) + + public override init() { + } + + public init(_ disposable1: Disposable, _ disposable2: Disposable) { + let bag = state.disposables + + bag.value.put(disposable1) + bag.value.put(disposable2) + } + + public init(_ disposable1: Disposable, _ disposable2: Disposable, _ disposable3: Disposable) { + let bag = state.disposables + + bag.value.put(disposable1) + bag.value.put(disposable2) + bag.value.put(disposable3) + } + + public init(disposables: [Disposable]) { + let bag = state.disposables + + for disposable in disposables { + bag.value.put(disposable) + } + } + + public func addDisposable(disposable: Disposable) -> BagKey? { + // this should be let + // bucause of compiler bug it's var + let key = self.lock.calculateLocked { oldState -> BagKey? in + if state.disposed { + return nil + } + else { + let key = state.disposables.value.put(disposable) + return key + } + } + + if key == nil { + disposable.dispose() + } + + return key + } + + public var count: Int { + get { + return self.lock.calculateLocked { + self.state.disposables.value.count + } + } + } + + public func removeDisposable(disposeKey: BagKey) { + let disposable = self.lock.calculateLocked { Void -> Disposable? in + return state.disposables.value.removeKey(disposeKey) + } + + if let disposable = disposable { + disposable.dispose() + } + } + + public func dispose() { + let oldDisposables = self.lock.calculateLocked { Void -> [Disposable] in + if state.disposed { + return [] + } + + let disposables = state.disposables + var allValues = disposables.value.all + + state.disposed = true + state.disposables = nil + + return allValues + } + + for d in oldDisposables { + d.dispose() + } + } +} \ No newline at end of file diff --git a/Rx/Rx/Disposables/DefaultDisposable.swift b/Rx/Rx/Disposables/DefaultDisposable.swift new file mode 100644 index 00000000..b8baea0f --- /dev/null +++ b/Rx/Rx/Disposables/DefaultDisposable.swift @@ -0,0 +1,19 @@ +// +// DefaultDisposable.swift +// Rx +// +// Created by Krunoslav Zaher on 2/15/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public struct DefaultDisposable : Disposable { + + public init() { + + } + + public func dispose() { + } +} \ No newline at end of file diff --git a/Rx/Rx/Disposables/DisposeBag.swift b/Rx/Rx/Disposables/DisposeBag.swift new file mode 100644 index 00000000..706f7426 --- /dev/null +++ b/Rx/Rx/Disposables/DisposeBag.swift @@ -0,0 +1,44 @@ +// +// DisposeBag.swift +// Rx +// +// Created by Krunoslav Zaher on 3/25/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public class DisposeBag: DisposeBase, Disposable { + private var lock = Lock() + + private var disposables: [Disposable] = [] + + public override init() { + super.init() + } + + public func addDisposable(disposable: Disposable) { + disposables.append(disposable) + } + + public func addDisposable(disposable: Result) { + disposables.append(*disposable) + } + + public func dispose() { + let oldDisposables = lock.calculateLocked { () -> [Disposable] in + var disposables = self.disposables + self.disposables.removeAll(keepCapacity: true) + + return disposables + } + + for disposable in oldDisposables { + disposable.dispose() + } + } + + deinit { + dispose() + } +} \ No newline at end of file diff --git a/Rx/Rx/Disposables/DisposeBase.swift b/Rx/Rx/Disposables/DisposeBase.swift new file mode 100644 index 00000000..5e004327 --- /dev/null +++ b/Rx/Rx/Disposables/DisposeBase.swift @@ -0,0 +1,23 @@ +// +// DisposeBase.swift +// Rx +// +// Created by Krunoslav Zaher on 4/4/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public class DisposeBase { + init() { +#if DEBUG + OSAtomicIncrement32(&resourceCount) +#endif + } + + deinit { +#if DEBUG + OSAtomicDecrement32(&resourceCount) +#endif + } +} \ No newline at end of file diff --git a/Rx/Rx/Disposables/SerialDisposable.swift b/Rx/Rx/Disposables/SerialDisposable.swift new file mode 100644 index 00000000..ebf8528f --- /dev/null +++ b/Rx/Rx/Disposables/SerialDisposable.swift @@ -0,0 +1,65 @@ +// +// SerialDisposable.swift +// Rx +// +// Created by Krunoslav Zaher on 3/12/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public class SerialDisposable : DisposeBase, Cancelable { + typealias State = ( + current: Disposable?, + disposed: Bool + ) + + var lock = Lock() + var state: State = ( + current: nil, + disposed: false + ) + + public var disposed: Bool { + get { + return state.disposed + } + } + + override public init() { + super.init() + } + + public func setDisposable(disposable: Disposable) { + var disposable: Disposable? = self.lock.calculateLocked { + if state.disposed { + return disposable + } + else { + var toDispose = state.current + state.current = disposable + return toDispose + } + } + + if let disposable = disposable { + disposable.dispose() + } + } + + public func dispose() { + var disposable: Disposable? = self.lock.calculateLocked { + if state.disposed { + return nil + } + else { + state.disposed = true + return state.current + } + } + + if let disposable = disposable { + disposable.dispose() + } + } +} \ No newline at end of file diff --git a/Rx/Rx/Disposables/SingleAssignmentDisposable.swift b/Rx/Rx/Disposables/SingleAssignmentDisposable.swift new file mode 100644 index 00000000..5f9e8a69 --- /dev/null +++ b/Rx/Rx/Disposables/SingleAssignmentDisposable.swift @@ -0,0 +1,65 @@ +// +// SingleAssignmentDisposable.swift +// Rx +// +// Created by Krunoslav Zaher on 2/15/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public class SingleAssignmentDisposable : DisposeBase, Disposable { + typealias State = ( + disposed: Bool, + disposableSet: Bool, + disposable: Disposable? + ) + + var lock = Lock() + var state: State = ( + disposed: false, + disposableSet: false, + disposable: nil + ) + + public override init() { + super.init() + } + + public func setDisposable(newDisposable: Disposable) { + var disposable: Disposable? = self.lock.calculateLocked { oldState in + + if state.disposableSet { + rxFatalError("oldState.disposable != nil") + } + + state.disposableSet = true + + if state.disposed { + return newDisposable + } + + state.disposable = newDisposable + + return nil + } + + if let disposable = disposable { + return disposable.dispose() + } + } + + public func dispose() { + var disposable: Disposable? = lock.calculateLocked { old in + state.disposed = true + var dispose = state.disposable + state.disposable = nil + + return dispose + } + + if let disposable = disposable { + disposable.dispose() + } + } +} \ No newline at end of file diff --git a/Rx/Rx/Error.swift b/Rx/Rx/Error.swift new file mode 100644 index 00000000..af2cc7ff --- /dev/null +++ b/Rx/Rx/Error.swift @@ -0,0 +1,35 @@ +// +// Error.swift +// Rx +// +// Created by Krunoslav Zaher on 3/28/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +let RxErrorDomain = "RxErrorDomain" +let RxCompositeFailures = "RxCompositeFailures" + +public enum RxErrorCode : Int { + case Unknown = 0 + case Composite = 1 + case Cast = 2 + case Disposed = 3 +} + +// This defines error type for entire project. +// It's not practical to have different error handling types for different areas of the app. +// This is good enough solution for now unless proven otherwise +public typealias ErrorType = NSError + +public let UnknownError = NSError(domain: RxErrorDomain, code: RxErrorCode.Unknown.rawValue, userInfo: nil) +public let CastError = NSError(domain: RxErrorDomain, code: RxErrorCode.Cast.rawValue, userInfo: nil) +public let DisposedError = NSError(domain: RxErrorDomain, code: RxErrorCode.Disposed.rawValue, userInfo: nil) + +func createCompositeFailure(failures: [Result]) -> Result { + let description: [NSObject : AnyObject] = [ RxCompositeFailures : Box(failures.map { $0.error! }) ] + let e = NSError(domain: RxErrorDomain, code: RxErrorCode.Composite.rawValue , userInfo: description) + + return .Error(e) +} diff --git a/Rx/Rx/Event.swift b/Rx/Rx/Event.swift new file mode 100644 index 00000000..8dc1a6c9 --- /dev/null +++ b/Rx/Rx/Event.swift @@ -0,0 +1,71 @@ +// +// Event.swift +// Rx +// +// Created by Krunoslav Zaher on 2/8/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + + +/// Due to current swift limitations, we have to include this Box in Result. +/// Swift cannot handle an enum with multiple associated data (A, NSError) where one is of unknown size (A) +/// This can be swiftified once the compiler is completed + +/** +* Represents event that happened +* `Box` is there because of a bug in swift compiler +* >> error: unimplemented IR generation feature non-fixed multi-payload enum layout +*/ +public enum Event { + // Box is used is because swift compiler doesn't know + // how to handle `Next(Element)` and it crashes. + case Next(Box) // next element of a sequence + case Error(ErrorType) // sequence failed with error + case Completed // sequence terminated successfully +} + +public func eventType(event: Event) -> String { + switch event { + case .Next: + return "Next: \(event)" + case .Completed: + return "Completed" + case .Error(let error): + return "Error \(error)" + } +} + +public func == (lhs: Event, rhs: Event) -> Bool { + switch (lhs, rhs) { + case (.Completed, .Completed): return true + case (.Error(let e1), .Error(let e2)): return e1 == e2 + case (.Next(let v1), .Next(let v2)): return v1.value == v2.value + default: return false + } +} + +extension Event { + public var isStopEvent: Bool { + get { + switch self { + case .Next: + return false + case .Error: fallthrough + case .Completed: return true + } + } + } + + public var value: Element? { + get { + switch self { + case .Next(let value): + return value.value + case .Error: fallthrough + case .Completed: return nil + } + } + } +} \ No newline at end of file diff --git a/Rx/Info.plist b/Rx/Rx/Info.plist similarity index 100% rename from Rx/Info.plist rename to Rx/Rx/Info.plist diff --git a/Rx/Rx/Observable+Subscription.swift b/Rx/Rx/Observable+Subscription.swift new file mode 100644 index 00000000..969cc1d5 --- /dev/null +++ b/Rx/Rx/Observable+Subscription.swift @@ -0,0 +1,49 @@ +// +// Observable+Subscription.swift +// Rx +// +// Created by Krunoslav Zaher on 2/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +extension Observable { + func subscribeSafe(observer: ObserverOf) -> Result { + if let observableBase = self as? ObservableBase { + return observableBase.subscribe(observer) + } + + var mutableObserver = observer + + return self.subscribe(observer) >>! { error in + return mutableObserver.on(Event.Error(error)) >>> { DefaultDisposable() } + } + } + +} + +public func subscribe + (on: (event: Event) -> Void) + (source: Observable) -> Result { + let observer: ObserverOf = ObserverOf(AnonymousObserver { e in + on(event: e) + return SuccessResult + }) + return source.subscribe(observer) +} + +public func subscribeNext + (onNext: (element: E) -> Void) + (source: Observable) -> Result { + let observer: ObserverOf = ObserverOf(AnonymousObserver { e in + switch e { + case .Next(let e): + onNext(element: e.value) + default: + break + } + return SuccessResult + }) + return source.subscribe(observer) +} \ No newline at end of file diff --git a/Rx/Rx/Observable.swift b/Rx/Rx/Observable.swift new file mode 100644 index 00000000..c3724458 --- /dev/null +++ b/Rx/Rx/Observable.swift @@ -0,0 +1,33 @@ +// +// Observable.swift +// Rx +// +// Created by Krunoslav Zaher on 2/8/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +/** +* Using protocol for Observer complicates implementation too much +*/ +public class Observable { + typealias ObserverAdapter = SinkOf> + + public init() { +#if DEBUG + OSAtomicIncrement32(&resourceCount) +#endif + } + + /// Subscribes `observer` to receive events from this observable + public func subscribe(observer: ObserverOf) -> Result { + return abstractMethod() + } + + deinit { +#if DEBUG + OSAtomicDecrement32(&resourceCount) +#endif + } +} diff --git a/Rx/Rx/Observables/Implementations/Aggregate.swift b/Rx/Rx/Observables/Implementations/Aggregate.swift new file mode 100644 index 00000000..1ae67e1c --- /dev/null +++ b/Rx/Rx/Observables/Implementations/Aggregate.swift @@ -0,0 +1,79 @@ +// +// Aggregate.swift +// Rx +// +// Created by Krunoslav Zaher on 4/1/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class Aggregate_ : Sink, ObserverClassType { + typealias Element = SourceType + typealias ParentType = Aggregate + + let parent: ParentType + var accumulation: AccumulateType + + init(parent: ParentType, observer: ObserverOf, cancel: Disposable) { + self.parent = parent + + self.accumulation = parent.seed + + super.init(observer: observer, cancel: cancel) + } + + func on(event: Event) -> Result { + switch event { + case .Next(let boxedValue): + let value = boxedValue.value + return parent.accumulator(accumulation, value) >== { result in + self.accumulation = result + return SuccessResult + } >>! { e in + let result = self.observer.on(.Error(e)) + self.dispose() + return result >>> { .Error(e) } + } + case .Error(let e): + let result = self.observer.on(.Error(e)) + self.dispose() + return result + case .Completed: + return self.parent.resultSelector(self.accumulation) >== { result in + let result = self.observer.on(.Next(Box(result))) >>> { + self.observer.on(.Completed) + } + self.dispose() + return result + } >>! { error in + let result = self.observer.on(.Error(error)) + self.dispose() + return result + } + } + } +} + +class Aggregate : Producer { + typealias AccumulatorType = (AccumulateType, SourceType) -> Result + typealias ResultSelectorType = (AccumulateType) -> Result + + let source: Observable + let seed: AccumulateType + let accumulator: AccumulatorType + let resultSelector: ResultSelectorType + + init(source: Observable, seed: AccumulateType, accumulator: AccumulatorType, resultSelector: ResultSelectorType) { + self.source = source + self.seed = seed + self.accumulator = accumulator + self.resultSelector = resultSelector + } + + override func run(observer: ObserverOf, cancel: Disposable, setSink: (Disposable) -> Void) -> Result { + let sink = Aggregate_(parent: self, observer: observer, cancel: cancel) + setSink(sink) + return source.subscribeSafe(ObserverOf(sink)) + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/AnonymousObservable.swift b/Rx/Rx/Observables/Implementations/AnonymousObservable.swift new file mode 100644 index 00000000..e3479c79 --- /dev/null +++ b/Rx/Rx/Observables/Implementations/AnonymousObservable.swift @@ -0,0 +1,23 @@ +// +// AnonymousObservable.swift +// Rx +// +// Created by Krunoslav Zaher on 2/8/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public class AnonymousObservable : ObservableBase { + typealias SubscribeHandler = (ObserverOf) -> Result + + let subscribeHandler: SubscribeHandler + + public init(_ subscribeHandler: SubscribeHandler) { + self.subscribeHandler = subscribeHandler + } + + public override func subscribeCore(observer: ObserverOf) -> Result { + return subscribeHandler(observer) + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/AsObservable.swift b/Rx/Rx/Observables/Implementations/AsObservable.swift new file mode 100644 index 00000000..36864bbd --- /dev/null +++ b/Rx/Rx/Observables/Implementations/AsObservable.swift @@ -0,0 +1,50 @@ +// +// AsObservable.swift +// Rx +// +// Created by Krunoslav Zaher on 2/27/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class AsObservableSink_ : ObserverClassType, Disposable { + typealias Element = ElementType + + let sink: Sink + + func dispose() { + sink.dispose() + } + + func on(event: Event) -> Result { + return self.sink.state.observer.on(event) + } + + init(observer: ObserverOf, cancel: Disposable) { + self.sink = Sink(observer: observer, cancel: cancel) + } +} + +class AsObservable : Producer { + + let source: Observable + + init(source: Observable) { + self.source = source + } + + func omega() -> Observable { + return self + } + + func eval() -> Observable { + return source + } + + override func run(observer: ObserverOf, cancel: Disposable, setSink: (Disposable) -> Void) -> Result { + let sink = AsObservableSink_(observer: observer, cancel: cancel) + setSink(sink) + return source.subscribeSafe(ObserverOf(sink)) + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/CombineLatest.swift b/Rx/Rx/Observables/Implementations/CombineLatest.swift new file mode 100644 index 00000000..9b586b1d --- /dev/null +++ b/Rx/Rx/Observables/Implementations/CombineLatest.swift @@ -0,0 +1,210 @@ +// +// CombineLatest.swift +// Rx +// +// Created by Krunoslav Zaher on 3/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class First: ObserverClassType { + typealias Parent = CombineLatest_ + typealias Element = Element1 + + let parent: Parent + let disposeSelf: Disposable + + var other: Second? = nil + + // under parent lock + var value: Element1? = nil + var done: Bool = false + + init(parent: Parent, disposeSelf: Disposable) { + self.parent = parent + self.disposeSelf = disposeSelf + } + + func on(event: Event) -> Result { + return parent.lock.calculateLocked { _ in + let (observer, disposable, disposed) = self.parent.state + + switch event { + case .Next(let boxedValue): + let value = boxedValue.value + self.value = value + + if let otherValue = self.other?.value { + let result: Result = self.parent.parent.selector(value, otherValue) + + return (result >== { res in + return observer.on(.Next(Box(res))) + }) >>! { e -> Result in + let resul = observer.on(.Error(e)) + self.parent.dispose() + return result >>> { .Error(e) } + } + } + else if self.other?.done ?? false { + let result = observer.on(.Completed) + self.parent.dispose() + return result + } + else { + return SuccessResult + } + case .Error(let error): + let result = observer.on(.Error(error)) + self.parent.dispose() + return result + case .Completed: + self.done = true + if self.other?.done ?? false { + let result = observer.on(.Completed) + self.parent.dispose() + return result + } + else { + self.disposeSelf.dispose() + return SuccessResult + } + } + } + } +} + +class Second: ObserverClassType { + typealias Parent = CombineLatest_ + typealias Element = Element2 + + let parent: Parent + let disposeSelf: Disposable + + var other: First? = nil + + var value: Element2? = nil + var done: Bool = false + + init(parent: Parent, disposeSelf: Disposable) { + self.parent = parent + self.disposeSelf = disposeSelf + self.other = nil + } + + func on(event: Event) -> Result { + return parent.lock.calculateLocked { _ in + let (observer, disposable, disposed) = self.parent.state + + switch event { + case .Next(let boxedValue): + let value = boxedValue.value + self.value = value + + if let otherValue = self.other?.value { + let result: Result = self.parent.parent.selector(otherValue, value) + return (result >== { res in + return observer.on(.Next(Box(res))) + }) >>! { e -> Result in + let result = observer.on(.Error(e)) + self.parent.dispose() + return result >>> { .Error(e) } + } + } + else if self.other?.done ?? false { + let result = observer.on(.Completed) + self.parent.dispose() + return result + } + else { + return SuccessResult + } + case .Error(let error): + let result = observer.on(.Error(error)) + self.parent.dispose() + return result + case .Completed: + self.done = true + if self.other?.done ?? false { + let result = observer.on(.Completed) + self.parent.dispose() + return result + } + else { + self.disposeSelf.dispose() + return SuccessResult + } + } + } + } +} + + +class CombineLatest_ : Sink { + typealias Parent = CombineLatest + + let parent: Parent + + var lock = Lock() + + init(parent: Parent, observer: ObserverOf, cancel: Disposable) { + self.parent = parent + + super.init(observer: observer, cancel: cancel) + } + + func run() -> Result { + let snapshot = self.state + + let subscription1 = SingleAssignmentDisposable() + let subscription2 = SingleAssignmentDisposable() + + let sink1 = First(parent: self, disposeSelf: subscription1) + let sink2 = Second(parent: self, disposeSelf: subscription2) + + sink1.other = sink2 + sink2.other = sink1 + + let removeBond = AnonymousDisposable { + sink1.other = nil + sink2.other = nil + } + + return doAll([ + parent.observable1.subscribeSafe(ObserverOf(sink1)) >== { disposable in + subscription1.setDisposable(disposable) + }, + parent.observable2.subscribeSafe(ObserverOf(sink2)) >== { disposable in + subscription2.setDisposable(disposable) + } + ]) >>> { + return success(CompositeDisposable(subscription1, subscription2, removeBond)) + } >>! { e in + subscription1.dispose() + subscription2.dispose() + removeBond.dispose() + return .Error(e) + } + } +} + +class CombineLatest : Producer { + typealias SelectorType = (Element1, Element2) -> Result + + let observable1: Observable + let observable2: Observable + let selector: SelectorType + + init(observable1: Observable, observable2: Observable, selector: SelectorType) { + self.observable1 = observable1 + self.observable2 = observable2 + + self.selector = selector + } + + override func run(observer: ObserverOf, cancel: Disposable, setSink: (Disposable) -> Void) -> Result { + let sink = CombineLatest_(parent: self, observer: observer, cancel: cancel) + setSink(sink) + return sink.run() + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/Concat.swift b/Rx/Rx/Observables/Implementations/Concat.swift new file mode 100644 index 00000000..1116b77b --- /dev/null +++ b/Rx/Rx/Observables/Implementations/Concat.swift @@ -0,0 +1,43 @@ +// +// Concat.swift +// Rx +// +// Created by Krunoslav Zaher on 3/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class Concat_ : ConcatSink { + override init(observer: ObserverOf, cancel: Disposable) { + super.init(observer: observer, cancel: cancel) + } + + override func on(event: Event) -> Result { + switch event { + case .Next(let next): + return observer.on(event) + case .Error: + let result = observer.on(event) + dispose() + return result + case .Completed: + return super.on(event) + } + } +} + +class Concat : Producer { + let sources: [Observable] + + init(sources: [Observable]) { + self.sources = sources + } + + override func run(observer: ObserverOf, cancel: Disposable, setSink: (Disposable) -> Void) -> Result { + let sink = Concat_(observer: observer, cancel: cancel) + setSink(sink) + + return sink.run(sources) + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/ConcatSink.swift b/Rx/Rx/Observables/Implementations/ConcatSink.swift new file mode 100644 index 00000000..bb8cd31c --- /dev/null +++ b/Rx/Rx/Observables/Implementations/ConcatSink.swift @@ -0,0 +1,33 @@ +// +// ConcatSink.swift +// Rx +// +// Created by Krunoslav Zaher on 3/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class ConcatSink : TailRecursiveSink { + override init(observer: ObserverOf, cancel: Disposable) { + super.init(observer: observer, cancel: cancel) + } + + override func on(event: Event) -> Result { + switch event { + case .Completed: + return scheduleMoveNext() + default: + return super.on(event) + } + } + + override func extract(observable: Observable) -> [Observable]? { + if let source = observable as? Concat { + return source.sources + } + else { + return nil + } + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/ConnectableObservable.swift b/Rx/Rx/Observables/Implementations/ConnectableObservable.swift new file mode 100644 index 00000000..b1bda791 --- /dev/null +++ b/Rx/Rx/Observables/Implementations/ConnectableObservable.swift @@ -0,0 +1,65 @@ +// +// ConnectableObservable.swift +// Rx +// +// Created by Krunoslav Zaher on 3/1/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class Connection : Disposable { + typealias SelfType = Connection + + var parent: ConnectableObservable? + var subscription: Disposable? + + init(parent: ConnectableObservable, subscription: Disposable) { + self.parent = parent + self.subscription = subscription + } + + func dispose() { + if let parent = parent { + parent.lock.performLocked { + subscription!.dispose() + subscription = nil + self.parent = nil + } + } + } +} + +class ConnectableObservable : ConnectableObservableType { + typealias ConnectionType = Connection + + let subject: SubjectType + let source: Observable + + var lock = Lock() + var connection: ConnectionType? + + init(source: Observable, subject: SubjectType) { + self.source = asObservable(source) + self.subject = subject + self.connection = nil + } + + override func connect() -> Result { + return self.lock.calculateLocked { oldConnection in + if let connection = connection { + return success(connection) + } + else { + return self.source.subscribeSafe(ObserverOf(self.subject)) >== { disposable in + self.connection = Connection(parent: self, subscription: disposable) + return success(self.connection!) + } + } + } + } + + override func subscribe(observer: ObserverOf) -> Result { + return subject.subscribeSafe(observer) + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/DistinctUntilChanged.swift b/Rx/Rx/Observables/Implementations/DistinctUntilChanged.swift new file mode 100644 index 00000000..a359fa87 --- /dev/null +++ b/Rx/Rx/Observables/Implementations/DistinctUntilChanged.swift @@ -0,0 +1,79 @@ +// +// DistinctUntilChanged.swift +// Rx +// +// Created by Krunoslav Zaher on 3/15/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class DistinctUntilChanged_: Sink, ObserverClassType { + typealias Element = ElementType + + let parent: DistinctUntilChanged + var currentKey: Key? = nil + + init(parent: DistinctUntilChanged, observer: ObserverOf, cancel: Disposable) { + self.parent = parent + super.init(observer: observer, cancel: cancel) + } + + func on(event: Event) -> Result { + let observer = super.state.observer + + switch event { + case .Next(let value): + let keyResult = self.parent.selector(value.value) + + let areEqualResult = keyResult >== { key -> Result in + if let currentKey = self.currentKey { + return self.parent.comparer(currentKey, key) + } + else { + return success(false) + } + } + + return (areEqualResult >== { areEqual in + if areEqual { + return SuccessResult + } + + self.currentKey = *keyResult + + return observer.on(event) + }) >>! { error in + let result = observer.on(.Error(error)) + self.dispose() + return result + } + case .Error: fallthrough + case .Completed: + let result = observer.on(event) + self.dispose() + return result + } + } +} + +class DistinctUntilChanged: Producer { + typealias KeySelector = (Element) -> Result + typealias EqualityComparer = (Key, Key) -> Result + + let source: Observable + let selector: KeySelector + let comparer: EqualityComparer + + init(source: Observable, selector: KeySelector, comparer: EqualityComparer) { + self.source = source + self.selector = selector + self.comparer = comparer + } + + override func run(observer: ObserverOf, cancel: Disposable, setSink: (Disposable) -> Void) -> Result { + let sink = DistinctUntilChanged_(parent: self, observer: observer, cancel: cancel) + setSink(sink) + return source.subscribeSafe(ObserverOf(sink)) + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/Do.swift b/Rx/Rx/Observables/Implementations/Do.swift new file mode 100644 index 00000000..2a985836 --- /dev/null +++ b/Rx/Rx/Observables/Implementations/Do.swift @@ -0,0 +1,55 @@ +// +// Do.swift +// Rx +// +// Created by Krunoslav Zaher on 2/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class Do_ : Sink, ObserverClassType, Disposable { + typealias Element = ElementType + typealias DoType = Do + + let parent: DoType + + init(parent: DoType, observer: ObserverOf, cancel: Disposable) { + self.parent = parent + super.init(observer: observer, cancel: cancel) + } + + func on(event: Event) -> Result { + return (parent.eventHandler(event) >>! { error in + // catch clause + return self.state.observer.on(Event.Error(error)) >>> { self.dispose() } + }) >== { _ in + return self.state.observer.on(event) >>> { + if event.isStopEvent { + self.dispose() + } + return SuccessResult + } + } + } +} + +class Do : Producer { + typealias EventHandler = Event -> Result + + let source: Observable + let eventHandler: EventHandler + + init(source: Observable, eventHandler: EventHandler) { + self.source = source + self.eventHandler = eventHandler + } + + override func run(observer: ObserverOf, cancel: Disposable, setSink: (Disposable) -> Void) -> Result { + let sink = Do_(parent: self, observer: observer, cancel: cancel) + + setSink(sink) + + return self.source.subscribeSafe(ObserverOf(sink)) + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/Merge.swift b/Rx/Rx/Observables/Implementations/Merge.swift new file mode 100644 index 00000000..faefb58d --- /dev/null +++ b/Rx/Rx/Observables/Implementations/Merge.swift @@ -0,0 +1,315 @@ +// +// Merge.swift +// Rx +// +// Created by Krunoslav Zaher on 3/28/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +// sequential + +class Merge_Iter : ObserverClassType { + typealias Element = ElementType + typealias DisposeKey = Bag.KeyType + + let parent: Merge_ + let disposeKey: DisposeKey + + init(parent: Merge_, disposeKey: DisposeKey) { + self.parent = parent + self.disposeKey = disposeKey + } + + func on(event: Event) -> Result { + switch event { + case .Next: + return parent.lock.calculateLocked { + return self.parent.observer.on(event) + } + case .Error: + return parent.lock.calculateLocked { + self.parent.dispose() + + return self.parent.observer.on(event) + } + case .Completed: + let group = parent.mergeState.group + group.removeDisposable(disposeKey) + return parent.lock.calculateLocked { + let state = parent.mergeState + if state.stopped && state.group.count == 1 { + let result = self.parent.observer.on(.Completed) + self.parent.dispose() + return result + } + return SuccessResult + } + } + } +} + +class Merge_ : Sink, ObserverClassType { + typealias Element = Observable + typealias MergeState = (stopped: Bool, group: CompositeDisposable, sourceSubscription: SingleAssignmentDisposable) + + let parent: Merge + + var lock = Lock() + var mergeState: MergeState = ( + stopped: false, + group: CompositeDisposable(), + sourceSubscription: SingleAssignmentDisposable() + ) + + init(parent: Merge, observer: ObserverOf, cancel: Disposable) { + self.parent = parent + + let state = self.mergeState + + state.group.addDisposable(state.sourceSubscription) + super.init(observer: observer, cancel: cancel) + } + + func run() -> Result { + let state = self.mergeState + + state.group.addDisposable(state.sourceSubscription) + + return self.parent.sources.subscribe(ObserverOf(self)) >== { disposable in + state.sourceSubscription.setDisposable(disposable) + return success(state.group) + } + } + + func on(event: Event>) -> Result { + switch event { + case .Next(let boxedValue): + let value = boxedValue.value + + let innerSubscription = SingleAssignmentDisposable() + let mergeStateSnapshot = mergeState + let maybeKey = mergeStateSnapshot.group.addDisposable(innerSubscription) + + if let key = maybeKey { + let observer = ObserverOf(Merge_Iter(parent: self, disposeKey: key)) + return value.subscribeSafe(observer) >== { disposable in + innerSubscription.setDisposable(disposable) + return SuccessResult + } + } + // it was already disposed + else { + return SuccessResult + } + case .Error(let error): + return lock.calculateLocked { Void -> Result in + let result = self.observer.on(.Error(error)) + self.dispose() + return result + } + case .Completed: + return lock.calculateLocked { + let mergeState = self.mergeState + + let group = mergeState.group + + self.mergeState.stopped = true + + if group.count == 1 { + let result = self.observer.on(.Completed) + self.dispose() + return result + } + else { + mergeState.sourceSubscription.dispose() + return SuccessResult + } + } + } + } +} + +// concurrent + +class Merge_ConcurrentIter : ObserverClassType { + typealias Element = ElementType + typealias DisposeKey = Bag.KeyType + + let parent: Merge_Concurrent + let disposeKey: DisposeKey + + init(parent: Merge_Concurrent, disposeKey: DisposeKey) { + self.parent = parent + self.disposeKey = disposeKey + } + + func on(event: Event) -> Result { + switch event { + case .Next: + return parent.lock.calculateLocked { + return self.parent.observer.on(event) + } + case .Error: + return parent.lock.calculateLocked { + let result = self.parent.observer.on(event) + self.parent.dispose() + return result + } + case .Completed: + let mergeState = parent.mergeState + mergeState.group.removeDisposable(disposeKey) + return parent.lock.calculateLocked { + if mergeState.queue.value.count > 0 { + let s = mergeState.queue.value.dequeue() + return self.parent.subscribe(s, group: mergeState.group) + } + else { + parent.mergeState.activeCount = mergeState.activeCount - 1 + + var result = SuccessResult + if mergeState.stopped && mergeState.activeCount == 0 { + result = self.parent.observer.on(.Completed) + self.parent.dispose() + } + + return result + } + } + } + } +} + +class Merge_Concurrent : Sink, ObserverClassType { + typealias Element = Observable + typealias QueueType = Queue> + + typealias MergeState = ( + stopped: Bool, + queue: MutatingBox, + sourceSubscription: SingleAssignmentDisposable, + group: CompositeDisposable, + activeCount: Int + ) + + let parent: Merge + + var lock = Lock() + var mergeState: MergeState = ( + stopped: false, + queue: MutatingBox(Queue(capacity: 2)), + sourceSubscription: SingleAssignmentDisposable(), + group: CompositeDisposable(), + activeCount: 0 + ) + + init(parent: Merge, observer: ObserverOf, cancel: Disposable) { + self.parent = parent + + let state = self.mergeState + + _ = state.group.addDisposable(state.sourceSubscription) + super.init(observer: observer, cancel: cancel) + } + + func run() -> Result { + let state = self.mergeState + + state.group.addDisposable(state.sourceSubscription) + + return self.parent.sources.subscribe(ObserverOf(self)) >== { disposable in + state.sourceSubscription.setDisposable(disposable) + return success(state.group) + } + } + + func subscribe(innerSource: Observable, group: CompositeDisposable) -> Result { + let subscription = SingleAssignmentDisposable() + + let key = group.addDisposable(subscription) + + if let key = key { + let observer = ObserverOf(Merge_ConcurrentIter(parent: self, disposeKey: key)) + + return innerSource.subscribeSafe(observer) >== { disposable in + subscription.setDisposable(disposable) + return SuccessResult + } + } + else { + return SuccessResult + } + } + + func on(event: Event>) -> Result { + switch event { + case .Next(let boxedValue): + let value = boxedValue.value + + return lock.calculateLocked { + let mergeState = self.mergeState + if mergeState.activeCount < self.parent.maxConcurrent { + return self.subscribe(value, group: mergeState.group) >>> { + self.mergeState.activeCount += 1 + return SuccessResult + } + } + else { + mergeState.queue.value.enqueue(value) + return SuccessResult + } + } + case .Error(let error): + return lock.calculateLocked { Void -> Result in + let result = self.observer.on(.Error(error)) + self.dispose() + return result + } + case .Completed: + return lock.calculateLocked { + let mergeState = self.mergeState + let group = mergeState.group + + var result: Result + + if mergeState.activeCount == 0 { + result = self.observer.on(.Completed) + self.dispose() + } + else { + mergeState.sourceSubscription.dispose() + result = SuccessResult + } + + return result >>> { + self.mergeState.stopped = true + return SuccessResult + } + } + } + } +} + +class Merge : Producer { + let sources: Observable> + let maxConcurrent: Int + + init(sources: Observable>, maxConcurrent: Int) { + self.sources = sources + self.maxConcurrent = maxConcurrent + } + + override func run(observer: ObserverOf, cancel: Disposable, setSink: (Disposable) -> Void) -> Result { + if maxConcurrent > 0 { + let sink = Merge_Concurrent(parent: self, observer: observer, cancel: cancel) + setSink(sink) + return sink.run() + } + else { + let sink = Merge_(parent: self, observer: observer, cancel: cancel) + setSink(sink) + return sink.run() + } + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/Multicast.swift b/Rx/Rx/Observables/Implementations/Multicast.swift new file mode 100644 index 00000000..8b3f9054 --- /dev/null +++ b/Rx/Rx/Observables/Implementations/Multicast.swift @@ -0,0 +1,80 @@ +// +// Multicast.swift +// Rx +// +// Created by Krunoslav Zaher on 2/27/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class Multicast_: Sink, ObserverClassType { + typealias Element = ResultType + typealias MutlicastType = Multicast + + typealias IntermediateObservable = ConnectableObservableType + typealias ResultObservable = Observable + + let parent: MutlicastType + + init(parent: MutlicastType, observer: ObserverOf, cancel: Disposable) { + self.parent = parent + super.init(observer: observer, cancel: cancel) + } + + func run() -> Result { + let connectableResult: Result = parent.subjectSelector() >== { subject in + return success(ConnectableObservable(source: self.parent.source, subject: subject)) + } + + let observableResult: Result = connectableResult >== { connectable in + return self.parent.selector(connectable) + } + + let subscribeResult: Result = observableResult >== { observable in + let observerOf = ObserverOf(self) + return observable.subscribeSafe(observerOf) + } + + let connectResult: Result = connectableResult >== { connectable in + return connectable.connect() + } + + let compositeResult = allSucceedOrDispose([subscribeResult, connectResult]) + + return compositeResult >>! { e in + return self.state.observer.on(Event.Error(e)) >>> { .Error(e) } + } + } + + func on(event: Event) -> Result { + let result = self.state.observer.on(event) + switch event { + case .Next: break + case .Error: fallthrough + case .Completed: self.dispose() + } + return result + } +} + +class Multicast: Producer { + typealias SubjectSelectorType = () -> Result> + typealias SelectorType = (Observable) -> Result> + + let source: Observable + let subjectSelector: SubjectSelectorType + let selector: SelectorType + + init(source: Observable, subjectSelector: SubjectSelectorType, selector: SelectorType) { + self.source = source + self.subjectSelector = subjectSelector + self.selector = selector + } + + override func run(observer: ObserverOf, cancel: Disposable, setSink: (Disposable) -> Void) -> Result { + var sink = Multicast_(parent: self, observer: observer, cancel: cancel) + setSink(sink) + return sink.run() + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/ObservableBase.swift b/Rx/Rx/Observables/Implementations/ObservableBase.swift new file mode 100644 index 00000000..ca767ec9 --- /dev/null +++ b/Rx/Rx/Observables/Implementations/ObservableBase.swift @@ -0,0 +1,25 @@ +// +// ObservableBase.swift +// Rx +// +// Created by Krunoslav Zaher on 2/15/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public class ObservableBase : Observable { + + override public func subscribe(observer: ObserverOf) -> Result { + let autoDetachObserver = AutoDetachObserver(observer: observer) + + return subscribeCore(ObserverOf(autoDetachObserver)) >== { disposable in + autoDetachObserver.setDisposable(disposable) + return success(disposable) + } + } + + func subscribeCore(observer: ObserverOf) -> Result { + return abstractMethod() + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/ObserveSingleOn.swift b/Rx/Rx/Observables/Implementations/ObserveSingleOn.swift new file mode 100644 index 00000000..93a6b589 --- /dev/null +++ b/Rx/Rx/Observables/Implementations/ObserveSingleOn.swift @@ -0,0 +1,134 @@ +// +// ObserveSingleOn.swift +// Rx +// +// Created by Krunoslav Zaher on 3/15/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +// This class is used to forward sequence of AT MOST ONE observed element to +// another schedule. +// +// In case sequence contains more then one element, it will fire an exception. + +class ObserveSingleOnObserver : ObserverClassType, Disposable { + typealias Element = ElementType + typealias Parent = ObserveSingleOn + typealias State = ( + observer: ObserverOf, + cancel: Disposable, + disposed: Bool, + element: Event? + ) + + let parent: Parent + + var lock = Lock() + var state: State + + init(parent: Parent, observer: ObserverOf, cancel: Disposable) { + self.parent = parent + self.state = ( + observer: observer, + cancel: cancel, + disposed: false, + element: nil + ) + } + + func on(event: Event) -> Result { + var elementToForward: Event? + var stopEventToForward: Event? + var observer: ObserverOf? + + self.lock.performLocked { + let scheduler = self.parent.scheduler + + switch event { + case .Next: + if self.state.element != nil { + rxFatalError("Sequence contains more then one element") + } + + self.state.element = event + case .Error: + if self.state.element != nil { + rxFatalError("Observed sequence was expected to have more then one element") + } + stopEventToForward = event + observer = self.state.observer + case .Completed: + elementToForward = self.state.element + stopEventToForward = event + observer = self.state.observer + } + } + + if let stopEventToForward = stopEventToForward { + self.parent.scheduler.schedule(()) { (_) in + var r = SuccessResult + if let elementToForward = elementToForward { + r = observer!.on(elementToForward) + } + + r = r >>! { error in + observer!.on(.Error(error)) + } + + r = r >>> { observer!.on(stopEventToForward) } + + self.dispose() + + return r + } + } + + return SuccessResult + } + + func dispose() { + if state.disposed { + return + } + + var cancel: Disposable? = self.lock.calculateLocked { + if self.state.disposed { + return nil + } + + var cancel = self.state.cancel + + self.state.disposed = true + self.state.cancel = DefaultDisposable() + self.state.observer = ObserverOf(NopObserver()) + + return cancel + } + + if let cancel = cancel { + cancel.dispose() + } + } + + func run() -> Result { + return self.parent.source.subscribeSafe(ObserverOf(self)) + } +} + +class ObserveSingleOn : Producer { + let scheduler: ImmediateScheduler + let source: Observable + + init(source: Observable, scheduler: ImmediateScheduler) { + self.source = source + self.scheduler = scheduler + } + + override func run(observer: ObserverOf, cancel: Disposable, setSink: (Disposable) -> Void) -> Result { + let sink = ObserveSingleOnObserver(parent: self, observer: observer, cancel: cancel) + setSink(sink) + return sink.run() + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/Producer.swift b/Rx/Rx/Observables/Implementations/Producer.swift new file mode 100644 index 00000000..dcc280a9 --- /dev/null +++ b/Rx/Rx/Observables/Implementations/Producer.swift @@ -0,0 +1,66 @@ +// +// Producer.swift +// Rx +// +// Created by Krunoslav Zaher on 2/20/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +struct State { + var observer: ObserverOf + let sink: SingleAssignmentDisposable + let subscription: SingleAssignmentDisposable + + init(observer: ObserverOf, sink: SingleAssignmentDisposable, subscription: SingleAssignmentDisposable) { + self.observer = observer + self.sink = sink + self.subscription = subscription + } + + func assign(disposable: Disposable) { + sink.setDisposable(disposable) + } +} + +class Producer : Observable { + + override func subscribe(observer: ObserverOf) -> Result { + return subscribeRaw(observer, enableSafeguard: true) + } + + func subscribeRaw(observer: ObserverOf, enableSafeguard: Bool) -> Result { + var state = State(observer: observer, sink: SingleAssignmentDisposable(), subscription: SingleAssignmentDisposable()) + + let d = CompositeDisposable(state.sink, state.subscription) + + if enableSafeguard { + state.observer = SafeObserver.create(observer, disposable: d) + } + + // TODO + /* + if (CurrentThreadScheduler.IsScheduleRequired) + { + CurrentThreadScheduler.Instance.Schedule(state, Run); + } + */ + + let setSink: (Disposable) -> Void = { d in state.assign(d) } + let runResult = run(state.observer, cancel: state.subscription, setSink: setSink) + + return (runResult >== { disposable in + state.subscription.setDisposable(disposable) + return success(d) + }) >>! { e -> Result in + d.dispose() + return .Error(e) + } + } + + func run(observer: ObserverOf, cancel: Disposable, setSink: (Disposable) -> Void) -> Result { + return abstractMethod() + } + +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/RefCount.swift b/Rx/Rx/Observables/Implementations/RefCount.swift new file mode 100644 index 00000000..a4f0aab4 --- /dev/null +++ b/Rx/Rx/Observables/Implementations/RefCount.swift @@ -0,0 +1,104 @@ +// +// RefCount.swift +// Rx +// +// Created by Krunoslav Zaher on 3/5/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class RefCount_ : Sink, ObserverClassType { + let parent: RefCount + typealias ParentState = RefCount.State + + init(parent: RefCount, observer: ObserverOf, cancel: Disposable) { + self.parent = parent + super.init(observer: observer, cancel: cancel) + } + + func run() -> Result { + let subscriptionResult: Result = self.parent.source.subscribeSafe(ObserverOf(self)) + + if let subscriptionResultError = subscriptionResult.error { + return .Error(subscriptionResultError) + } + + let connectResult: Result = subscriptionResult >>> { + let state = self.parent.state + + return self.parent.lock.calculateLocked { + if state.count == 0 { + return self.parent.source.connect() >== { disposable in + self.parent.state.count = 1 + self.parent.state.connectableSubscription = disposable + return SuccessResult + } + } + else { + self.parent.state.count = state.count + 1 + return SuccessResult + } + } + } + + if let connectResultError = connectResult.error { + // cleanup registration + (*subscriptionResult).dispose() + return .Error(connectResultError) + } + + return success(AnonymousDisposable { + self.parent.lock.performLocked { + let state = self.parent.state + if state.count == 1 { + state.connectableSubscription!.dispose() + self.parent.state.count = 0 + self.parent.state.connectableSubscription = nil + } + else if state.count > 1 { + self.parent.state.count = state.count - 1 + } + else { + rxFatalError("Something went wrong with RefCount disposing mechanism") + } + } + }) + } + + func on(event: Event) -> Result { + let observer = state.observer + + switch event { + case .Next: return observer.on(event) + case .Error: fallthrough + case .Completed: + let result = observer.on(event) + self.dispose() + return result + } + } +} + +class RefCount: Producer { + typealias State = (count: Int, connectableSubscription: Disposable?) + + var lock = Lock() + + var state: State = ( + count: 0, + connectableSubscription: nil + ) + + let source: ConnectableObservableType + + init(source: ConnectableObservableType) { + self.source = source + } + + override func run(observer: ObserverOf, cancel: Disposable, setSink: (Disposable) -> Void) -> Result { + let sink = RefCount_(parent: self, observer: observer, cancel: cancel) + setSink(sink) + return sink.run() + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/ScheduledObserver.swift b/Rx/Rx/Observables/Implementations/ScheduledObserver.swift new file mode 100644 index 00000000..f032adcc --- /dev/null +++ b/Rx/Rx/Observables/Implementations/ScheduledObserver.swift @@ -0,0 +1,20 @@ +// +// ScheduledObserver.swift +// Rx +// +// Created by Krunoslav Zaher on 4/5/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +enum ScheduledState: Int { + case Stopped = 0 + case Running = 1 + case Pending = 2 + case Faulted = 9 +} + +public class ScheduledObserver : ObserverBase { + +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/Select.swift b/Rx/Rx/Observables/Implementations/Select.swift new file mode 100644 index 00000000..bf34e4d3 --- /dev/null +++ b/Rx/Rx/Observables/Implementations/Select.swift @@ -0,0 +1,106 @@ +// +// Select.swift +// Rx +// +// Created by Krunoslav Zaher on 3/15/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class Select_ : Sink, ObserverClassType { + let parent: Select + + init(parent: Select, observer: ObserverOf, cancel: Disposable) { + self.parent = parent + super.init(observer: observer, cancel: cancel) + } + + func select(element: ElementType) -> Result { + return abstractMethod() + } + + func on(event: Event) -> Result { + let observer = super.state.observer + + switch event { + case .Next(let element): + let sendValueResult: Result = select(element.value) >== { value in + return observer.on(.Next(Box(value))) + } + + return sendValueResult >>! { e -> Result in + let result: Result = observer.on(.Error(e)) >>> { .Error(e) } + self.dispose() + return result + } + case .Error(let error): + let result = observer.on(.Error(error)) + self.dispose() + return result + case .Completed: + let result = observer.on(.Completed) + self.dispose() + return result + } + } +} + +class Select_1 : Select_ { + + override init(parent: Select, observer: ObserverOf, cancel: Disposable) { + super.init(parent: parent, observer: observer, cancel: cancel) + } + + override func select(element: ElementType) -> Result { + return (self.parent.selector1!)(element) + } +} + +class Select_2 : Select_ { + var index = 0 + + override init(parent: Select, observer: ObserverOf, cancel: Disposable) { + super.init(parent: parent, observer: observer, cancel: cancel) + } + override func select(element: ElementType) -> Result { + return (self.parent.selector2!)(element, index++) + } +} + +class Select: Producer { + typealias Element = ElementType + typealias Selector1 = (Element) -> Result + typealias Selector2 = (Element, Int) -> Result + + let source: Observable + + let selector1: Selector1? + let selector2: Selector2? + + init(source: Observable, selector: Selector1) { + self.source = source + self.selector1 = selector + self.selector2 = nil + } + + init(source: Observable, selector: Selector2) { + self.source = source + self.selector2 = selector + self.selector1 = nil + } + + override func run(observer: ObserverOf, cancel: Disposable, setSink: (Disposable) -> Void) -> Result { + var sink: Select_ + if let selector1 = self.selector1 { + sink = Select_1(parent: self, observer: observer, cancel: cancel) + } + else { + sink = Select_2(parent: self, observer: observer, cancel: cancel) + } + + setSink(sink) + + return self.source.subscribeSafe(ObserverOf(sink)) + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/Sink.swift b/Rx/Rx/Observables/Implementations/Sink.swift new file mode 100644 index 00000000..92f9bfe0 --- /dev/null +++ b/Rx/Rx/Observables/Implementations/Sink.swift @@ -0,0 +1,99 @@ +// +// Sink.swift +// Rx +// +// Created by Krunoslav Zaher on 2/19/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +struct Sink_: ObserverType { + typealias Element = ElementType + + let sink: Sink + + init(sink: Sink) { + self.sink = sink + } + + mutating func on(event: Event) -> Result { + let result = sink.state.observer.on(event) + switch event { + case .Next: + return result + case .Completed: fallthrough + case .Error: + self.sink.dispose() + return result + } + } +} + +class Sink : Disposable { + private typealias Element = ElementType + + typealias State = (observer: ObserverOf, cancel: Disposable, disposed: Bool) + + private var lock = Lock() + private var _state: State + + var observer: ObserverOf { + get { + return lock.calculateLocked { _state.observer } + } + } + + var cancel: Disposable { + get { + return lock.calculateLocked { _state.cancel } + } + } + + var state: State { + get { + return lock.calculateLocked { _state } + } + } + + init(observer: ObserverOf, cancel: Disposable) { +#if DEBUG + OSAtomicIncrement32(&resourceCount) +#endif + _state = ( + observer: observer, + cancel: cancel, + disposed: false + ) + } + + func getForwarder() -> Sink_ { + return Sink_(sink: self) + } + + func dispose() { + var cancel: Disposable? = lock.calculateLocked { + if _state.disposed { + return nil + } + + var cancel = _state.cancel + + _state.disposed = true + _state.observer = ObserverOf(NopObserver()) + _state.cancel = DefaultDisposable() + + return cancel + } + + if let cancel = cancel { + cancel.dispose() + } + } + + deinit { +#if DEBUG + OSAtomicDecrement32(&resourceCount) +#endif + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/Subject.swift b/Rx/Rx/Observables/Implementations/Subject.swift new file mode 100644 index 00000000..65d68ff6 --- /dev/null +++ b/Rx/Rx/Observables/Implementations/Subject.swift @@ -0,0 +1,134 @@ +// +// Subject.swift +// Rx +// +// Created by Krunoslav Zaher on 2/11/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class Subscription : Disposable { + typealias ObserverType = ObserverOf + typealias KeyType = Bag.KeyType + + private let subject : Subject + private var key: KeyType + + + private var lock = Lock() + private var observer: ObserverType? + + init(subject: Subject, key: KeyType, observer: ObserverType) { + self.key = key + self.subject = subject + self.observer = observer + } + + func dispose() { + lock.performLocked { + if let observer = self.observer { + self.subject.unsubscribe(self.key) + } + } + } +} + + +public class Subject : SubjectType, Disposable { + typealias ObserverType = ObserverOf + typealias KeyType = Bag.KeyType + typealias Observers = Bag + typealias State = ( + disposed: Bool, + observers: Observers, + stoppedEvent: Event? + ) + + private var lock = Lock() + private var state: State = ( + disposed: false, + observers: Observers(), + stoppedEvent: nil + ) + + public override init() { + super.init() + } + + public func dispose() { + self.lock.performLocked { + state.disposed = true + } + } + + public override func on(event: Event) -> Result { + switch event { + case .Next(let value): + let observers = lock.calculateLocked { () -> [ObserverType]? in + let state = self.state + let shouldReturnImmediatelly = state.disposed || state.stoppedEvent != nil + let observers: [ObserverType]? = shouldReturnImmediatelly ? nil : state.observers.all + + return observers + } + + if let observers = observers { + return dispatch(event, observers) + } + else { + return SuccessResult + } + default: + break + } + + let observers: [ObserverType] = lock.calculateLocked { + let state = self.state + + var observers = self.state.observers.all + + switch event { + case .Completed: fallthrough + case .Error: + if state.stoppedEvent == nil { + self.state.stoppedEvent = event + self.state.observers.removeAll() + } + default: + rxFatalError("Something went wrong") + } + + return observers + } + + return dispatch(event, observers) + } + + + public override func subscribe(observer: ObserverOf) -> Result { + return lock.calculateLocked { + if let stoppedEvent = state.stoppedEvent { + return observer.on(stoppedEvent) >>> { + return success(DefaultDisposable()) + } + } + + if state.disposed { + return .Error(DisposedError) + } + + let key = state.observers.put(observer) + return success(Subscription(subject: self, key: key, observer: observer)) + } + } + + func unsubscribe(key: KeyType) { + self.lock.performLocked { + let observer = state.observers.removeKey(key) + if observer == nil { + rxFatalError("Something went wrong with dispose") + } + } + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/Switch.swift b/Rx/Rx/Observables/Implementations/Switch.swift new file mode 100644 index 00000000..ccae5f0f --- /dev/null +++ b/Rx/Rx/Observables/Implementations/Switch.swift @@ -0,0 +1,150 @@ +// +// Switch.swift +// Rx +// +// Created by Krunoslav Zaher on 3/12/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class Switch_ : Sink, ObserverClassType { + typealias Element = Observable + typealias SwitchState = ( + subscription: SingleAssignmentDisposable, + innerSubscription: SerialDisposable, + stopped: Bool, + latest: Int, + hasLatest: Bool + ) + + let parent: Switch + + var lock = Lock() + var switchState: SwitchState + + init(parent: Switch, observer: ObserverOf, cancel: Disposable) { + self.parent = parent + self.switchState = ( + subscription: SingleAssignmentDisposable(), + innerSubscription: SerialDisposable(), + stopped: false, + latest: 0, + hasLatest: false + ) + super.init(observer: observer, cancel: cancel) + } + + func run() -> Result { + return self.parent.sources.subscribeSafe(ObserverOf(self)) >== { subscription in + let switchState = self.switchState + switchState.subscription.setDisposable(subscription) + return success(CompositeDisposable(switchState.subscription, switchState.innerSubscription)) + } + } + + func on(event: Event) -> Result { + switch event { + case .Next(let observable): + let latest: Int = self.lock.calculateLocked { + self.switchState.hasLatest = true + self.switchState.latest = self.switchState.latest + 1 + return self.switchState.latest + } + + let d = SingleAssignmentDisposable() + self.switchState.innerSubscription.setDisposable(d) + + let observer = ObserverOf(SwitchIter(parent: self, id: latest, _self: d)) + return observable.value.subscribeSafe(observer) >== { disposable in + d.setDisposable(disposable) + return SuccessResult + } + case .Error(let error): + let result = self.lock.calculateLocked { + return self.state.observer.on(.Error(error)) + } + self.dispose() + return result + case .Completed: + return self.lock.calculateLocked { + self.switchState.stopped = true + + self.switchState.subscription.dispose() + + var result = SuccessResult + if !self.switchState.hasLatest { + result = self.state.observer.on(.Completed) + self.dispose() + } + + return result + } + } + } +} + +class SwitchIter : ObserverClassType { + typealias Element = ElementType + + let parent: Switch_ + let id: Int + let _self: Disposable + + init(parent: Switch_, id: Int, _self: Disposable) { + self.parent = parent + self.id = id + self._self = _self + } + + func on(event: Event) -> Result { + return parent.lock.calculateLocked { state in + let switchState = self.parent.switchState + + switch event { + case .Next: break + case .Error: fallthrough + case .Completed: self._self.dispose() + } + + if switchState.latest != self.id { + return success(state) + } + + let observer = self.parent.state.observer + + switch event { + case .Next: + return observer.on(event) + case .Error: + let result = observer.on(event) + self.parent.dispose() + return result + case .Completed: + parent.switchState.hasLatest = false + if switchState.stopped { + let result = observer.on(event) + self.parent.dispose() + return result + } + else { + return SuccessResult + } + } + } + } +} + +class Switch : Producer { + let sources: Observable> + + init(sources: Observable>) { + self.sources = sources + } + + override func run(observer: ObserverOf, cancel: Disposable, setSink: (Disposable) -> Void) -> Result { + let sink = Switch_(parent: self, observer: observer, cancel: cancel) + setSink(sink) + return sink.run() + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/TailRecursiveSink.swift b/Rx/Rx/Observables/Implementations/TailRecursiveSink.swift new file mode 100644 index 00000000..e3b3acfa --- /dev/null +++ b/Rx/Rx/Observables/Implementations/TailRecursiveSink.swift @@ -0,0 +1,126 @@ +// +// TailRecursiveSink.swift +// Rx +// +// Created by Krunoslav Zaher on 3/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class TailRecursiveSink : Sink, ObserverClassType { + typealias Element = ElementType + typealias StackElementType = (generator: GeneratorOf>, length: Int) + + var stack: [StackElementType] = [] + var disposed: Bool = false + var subscription: SerialDisposable = SerialDisposable() + + // this is thread safe object + var gate: AsyncLock = AsyncLock() + + override init(observer: ObserverOf, cancel: Disposable) { + super.init(observer: observer, cancel: cancel) + } + + func run(sources: [Observable]) -> Result { + let generator: GeneratorOf> = GeneratorOf(sources.generate()) + self.stack.append((generator: generator, length: sources.count)) + + let stateSnapshot = self.state + + return scheduleMoveNext() >>> { + success(CompositeDisposable( + self.subscription, + stateSnapshot.cancel, + AnonymousDisposable { + self.disposePrivate() + } + )) + } + } + + func scheduleMoveNext() -> Result { + return schedule { + self.moveNext() + } + } + + // simple implementation for now + func schedule(action: () -> Result) -> Result { + return self.gate.wait(action) + } + + func moveNext() -> Result { + var next: Observable? = nil; + + do { + if self.stack.count == 0 { + break + } + + if disposed { + return SuccessResult + } + + var (e, l) = stack.last! + + let current = e.next() + + if current == nil { + stack.removeLast() + continue; + } + + let r = l - 1 + + stack.removeLast() + stack.append((generator: e, length: r)) + + next = current + + if r == 0 { + stack.removeLast() + } + + let nextSeq = extract(next!) + + if let nextSeq = nextSeq { + let generator = GeneratorOf(nextSeq.generate()) + let length = nextSeq.count + + next = nil + } + } while next == nil + + if next == nil { + return done() + } + + let d = SingleAssignmentDisposable() + subscription.setDisposable(d) + return next!.subscribeSafe(ObserverOf(self)) >== { subscription in + d.setDisposable(subscription) + } + } + + private func disposePrivate() { + disposed = true + + stack.removeAll(keepCapacity: false) + } + + func done() -> Result { + let result = state.observer.on(.Completed) + self.dispose() + return result + } + + func extract(observable: Observable) -> [Observable]? { + return abstractMethod() + } + + func on(event: Event) -> Result { + return abstractMethod() + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/Throttle.swift b/Rx/Rx/Observables/Implementations/Throttle.swift new file mode 100644 index 00000000..e2d6e4bd --- /dev/null +++ b/Rx/Rx/Observables/Implementations/Throttle.swift @@ -0,0 +1,137 @@ +// +// Throttle.swift +// Rx +// +// Created by Krunoslav Zaher on 3/22/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class Throttle_ : Sink, ObserverClassType { + typealias ParentType = Throttle + typealias ThrottleState = ( + value: Element?, + cancellable: SerialDisposable, + id: UInt64 + ) + + let parent: ParentType + + var lock = Lock() + var throttleState: ThrottleState = ( + value: nil, + cancellable: SerialDisposable(), + id: 0 + ) + + init(parent: ParentType, observer: ObserverOf, cancel: Disposable) { + self.parent = parent + + super.init(observer: observer, cancel: cancel) + } + + func run() -> Result { + let cancellable = self.throttleState.cancellable + return parent.source.subscribeSafe(ObserverOf(self)) >== { subscription in + return success(CompositeDisposable(subscription, cancellable)) + } + } + + func on(event: Event) -> Result { + switch event { + case .Next: + break + case .Error: fallthrough + case .Completed: + throttleState.cancellable.dispose() + break + } + + + var (latestId: UInt64, observer: ObserverOf, oldValue: Element?) = self.lock.calculateLocked { + let observer = self.observer + + var oldValue = self.throttleState.value + + switch event { + case .Next(let boxedValue): + self.throttleState.value = boxedValue.value + case .Error(let error): + self.dispose() + self.throttleState.value = nil + case .Completed: + self.dispose() + self.throttleState.value = nil + } + + self.throttleState.id = self.throttleState.id + 1 + return (self.throttleState.id, observer, oldValue) + } + + + switch event { + case .Next(let boxedValue): + let d = SingleAssignmentDisposable() + self.throttleState.cancellable.setDisposable(d) + + let scheduler = self.parent.scheduler + let dueTime = self.parent.dueTime + + return scheduler.schedule(latestId, dueTime: dueTime, action: { (id) in + return self.propagate() + }) >== { disposeTimer in + d.setDisposable(disposeTimer) + return SuccessResult + } + case .Error(let error): + return observer.on(.Error(error)) + case .Completed: + var sendResult: Result + if let oldValue = oldValue { + sendResult = observer.on(.Next(Box(oldValue))) + } + else { + sendResult = SuccessResult + } + return sendResult >>> { + return observer.on(.Completed) + } + } + } + + func propagate() -> Result { + var originalValue: Element? = self.lock.calculateLocked { + var originalValue = self.throttleState.value + self.throttleState.value = nil + return originalValue + } + + if let value = originalValue { + return self.observer.on(.Next(Box(value))) + } + else { + return SuccessResult + } + } +} + +class Throttle : Producer { + + let source: Observable + let dueTime: SchedulerType.TimeInterval + let scheduler: SchedulerType + + init(source: Observable, dueTime: SchedulerType.TimeInterval, scheduler: SchedulerType) { + self.source = source + self.dueTime = dueTime + self.scheduler = scheduler + } + + override func run(observer: ObserverOf, cancel: Disposable, setSink: (Disposable) -> Void) -> Result { + let sink = Throttle_(parent: self, observer: observer, cancel: cancel) + setSink(sink) + return sink.run() + } + +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/Variable.swift b/Rx/Rx/Observables/Implementations/Variable.swift new file mode 100644 index 00000000..84b98d63 --- /dev/null +++ b/Rx/Rx/Observables/Implementations/Variable.swift @@ -0,0 +1,61 @@ +// +// Variable.swift +// Rx +// +// Created by Krunoslav Zaher on 3/28/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public class Variable: Subject { + typealias VariableState = Element + + var lock = Lock() + var replayEvent: Event? = nil + + public init(_ initialEvent: Event) { + self.replayEvent = initialEvent + super.init() + } + + public override init() { + super.init() + } + + public override func on(event: Event) -> Result { + switch event { + case .Next: + lock.performLocked { + self.replayEvent = event + } + default: break + } + + return super.on(event) + } + + public override func subscribe(observer: ObserverOf) -> Result { + var result: Result + + var currentValue = self.lock.calculateLocked { self.replayEvent } + + if let currentValue = currentValue { + result = observer.on(currentValue) + } + else { + result = SuccessResult + } + + if let error = result.error { + dispose() + return .Error(error) + } + + return super.subscribe(observer) + } +} + +public func << (variable: Variable, element: E) { + variable.on(.Next(Box(element))) +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Implementations/WhereObservable.swift b/Rx/Rx/Observables/Implementations/WhereObservable.swift new file mode 100644 index 00000000..bfd3983e --- /dev/null +++ b/Rx/Rx/Observables/Implementations/WhereObservable.swift @@ -0,0 +1,57 @@ +// +// WhereObservable.swift +// Rx +// +// Created by Krunoslav Zaher on 2/17/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class WhereObserver: ObserverBase { + typealias Predicate = (Element) -> Result + + let observer: ObserverOf + let predicate: Predicate + + init(observer: ObserverOf, predicate: Predicate) { + self.observer = observer + self.predicate = predicate + } + + override func onCore(event: Event) -> Result { + switch event { + case .Next(let value): + return (predicate(value.value) >>! { e in + return self.observer.on(.Error(e)) >>> { .Error(e) } + }) >== { satisfies in + return satisfies + ? self.observer.on(event) + : SuccessResult + } + case .Completed: fallthrough + case .Error: return observer.on(event) + } + } +} + +class WhereObservable : ObservableBase { + typealias Predicate = (Element) -> Result + + let source: Observable + let predicate: Predicate + + init(source: Observable, predicate: Predicate) { + self.source = source + self.predicate = predicate + } + + func compose(predicate: Predicate) -> WhereObservable { + // too slow + return WhereObservable(source: source, predicate: { lift({ $0 && $1 }) (self.predicate($0), predicate($0)) }) + } + + override func subscribeCore(observer: ObserverOf) -> Result { + return source.subscribe(ObserverOf(WhereObserver(observer: observer, predicate: predicate))) + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Observable+Aggregate.swift b/Rx/Rx/Observables/Observable+Aggregate.swift new file mode 100644 index 00000000..808e2a81 --- /dev/null +++ b/Rx/Rx/Observables/Observable+Aggregate.swift @@ -0,0 +1,77 @@ +// +// Observable+Aggregate.swift +// Rx +// +// Created by Krunoslav Zaher on 3/22/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +// aggregate + +public func aggregateOrDie + (seed: A, accumulator: (A, E) -> Result, resultSelector: (A) -> Result) + -> (Observable -> Observable) { + return { source in + return Aggregate(source: source, seed: seed, accumulator: accumulator, resultSelector: resultSelector) + } +} + +public func aggregateOrDie + (seed: A, accumulator: (A, E) -> Result) + -> (Observable -> Observable) { + return { source in + return Aggregate(source: source, seed: seed, accumulator: accumulator, resultSelector: { success($0) }) + } +} + +public func aggregate + (seed: A, accumulator: (A, E) -> A, resultSelector: (A) -> R) + -> (Observable -> Observable) { + return { source in + return Aggregate(source: source, seed: seed, accumulator: { success(accumulator($0, $1)) }, resultSelector: { success(resultSelector($0)) }) + } +} + +public func aggregate + (seed: A, accumulator: (A, E) -> A) + -> (Observable -> Observable) { + return { source in + return Aggregate(source: source, seed: seed, accumulator: { success(accumulator($0, $1)) }, resultSelector: { success($0) }) + } +} + +// foldl + +public func foldlOrDie + (seed: A, accumulator: (A, E) -> Result, resultSelector: (A) -> Result) + -> (Observable -> Observable) { + return { source in + return Aggregate(source: source, seed: seed, accumulator: accumulator, resultSelector: resultSelector) + } +} + +public func foldlOrDie + (seed: A, accumulator: (A, E) -> Result) + -> (Observable -> Observable) { + return { source in + return Aggregate(source: source, seed: seed, accumulator: accumulator, resultSelector: { success($0) }) + } +} + +public func foldl + (seed: A, accumulator: (A, E) -> A, resultSelector: (A) -> R) + -> (Observable -> Observable) { + return { source in + return Aggregate(source: source, seed: seed, accumulator: { success(accumulator($0, $1)) }, resultSelector: { success(resultSelector($0)) }) + } +} + +public func foldl + (seed: A, accumulator: (A, E) -> A) + -> (Observable -> Observable) { + return { source in + return Aggregate(source: source, seed: seed, accumulator: { success(accumulator($0, $1)) }, resultSelector: { success($0) }) + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Observable+Binding.swift b/Rx/Rx/Observables/Observable+Binding.swift new file mode 100644 index 00000000..6e6c71e7 --- /dev/null +++ b/Rx/Rx/Observables/Observable+Binding.swift @@ -0,0 +1,27 @@ +// +// Observable+Binding.swift +// Rx +// +// Created by Krunoslav Zaher on 3/1/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +// multicast + +public func multicast + (subject: SubjectType) + -> (Observable -> ConnectableObservableType) { + return { source in + return ConnectableObservable(source: source, subject: subject) + } +} + +// refcount + +public func refCount + (source: ConnectableObservableType) + -> Observable { + return RefCount(source: source) +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Observable+Concurrency.swift b/Rx/Rx/Observables/Observable+Concurrency.swift new file mode 100644 index 00000000..26f57485 --- /dev/null +++ b/Rx/Rx/Observables/Observable+Concurrency.swift @@ -0,0 +1,32 @@ +// +// Observable+Concurrency.swift +// Rx +// +// Created by Krunoslav Zaher on 3/15/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +// Currently only simple observing is implemented. +// +// On client devices most common use case for observeOn would be to execute some work on background thread +// or return result to main thread. +// +// `observeSingleOn` is optimized for that specific purpose. It assumes that sequence will have one element +// and in cases it has more then one element it will throw an exception. +// +// This is a huge performance win considering most general case. +// +// General slower version of `observeOn` will not be implemented until needed. +// +// Even though it looks like naive implementation of general `observeOn` using simple `schedule` +// for each event will work, this is not the case. + +public func observeSingleOn + (scheduler: ImmediateScheduler) + -> ((Observable) -> Observable) { + return { source in + return ObserveSingleOn(source: source, scheduler: scheduler) + } +} diff --git a/Rx/Rx/Observables/Observable+Creation.swift b/Rx/Rx/Observables/Observable+Creation.swift new file mode 100644 index 00000000..ff43bbd1 --- /dev/null +++ b/Rx/Rx/Observables/Observable+Creation.swift @@ -0,0 +1,52 @@ +// +// Observable+Creation.swift +// Rx +// +// Created by Krunoslav Zaher on 3/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +// create + +public func create(subscribe: (ObserverOf) -> Result) -> Observable { + return AnonymousObservable(subscribe) +} + +// empty + +public func empty() -> Observable { + return AnonymousObservable { observer in + let result : Result = observer.on(.Completed) + return result >>> { (DefaultDisposable()) } + } +} + +// never + +public func never() -> Observable { + return AnonymousObservable { observer in + return success(DefaultDisposable()) + } +} + +// return + +public func returnElement(value: E) -> Observable { + return AnonymousObservable { observer in + return observer.on(.Next(Box(value))) >>> { observer.on(.Completed) } >>> { (DefaultDisposable()) } + } +} + +public func returnElement(values: E ...) -> Observable { + return AnonymousObservable { observer in + var result = SuccessResult + + for element in values { + result = result >>> { observer.on(.Next(Box(element))) } + } + + return (result >>> { observer.on(.Completed) }) >>> { (DefaultDisposable()) } + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Observable+Multiple.swift b/Rx/Rx/Observables/Observable+Multiple.swift new file mode 100644 index 00000000..80f2a211 --- /dev/null +++ b/Rx/Rx/Observables/Observable+Multiple.swift @@ -0,0 +1,61 @@ +// +// Observable+Multiple.swift +// Rx +// +// Created by Krunoslav Zaher on 3/12/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +// switch + +public func switchLatest + (sources: Observable>) + -> Observable { + + // swift doesn't have co/contravariance + return Switch(sources: sources) +} + +// combine latest + +public func combineLatestOrDie + (with: Observable, resultSelector: (E1, E2) -> Result) + -> (Observable -> Observable) { + return { source in + return CombineLatest(observable1: with, observable2: source, selector: resultSelector) + } +} + +public func combineLatest + (with: Observable, resultSelector: (E1, E2) -> R) + -> (Observable -> Observable) { + return { source in + return CombineLatest(observable1: with, observable2: source, selector: { success(resultSelector($0, $1)) }) + } +} + +// concat + +public func concat + (sources: [Observable]) + -> Observable { + return Concat(sources: sources) +} + +// merge + +public func merge + (sources: Observable>) + -> Observable { + return Merge(sources: sources, maxConcurrent: 0) +} + +public func merge + (maxConcurrent: Int) + -> (Observable> -> Observable) { + return { sources in + return Merge(sources: sources, maxConcurrent: maxConcurrent) + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Observable+Single.swift b/Rx/Rx/Observables/Observable+Single.swift new file mode 100644 index 00000000..a0806018 --- /dev/null +++ b/Rx/Rx/Observables/Observable+Single.swift @@ -0,0 +1,168 @@ +// +// Observable+Single.swift +// Rx +// +// Created by Krunoslav Zaher on 2/14/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +// as observable + +public func asObservable + (source: Observable) -> Observable { + if let asObservable = source as? AsObservable { + return asObservable.omega() + } + else { + return AsObservable(source: source) + } +} + +// distinct until changed + +public func distinctUntilChangedOrDie(source: Observable) + -> Observable{ + return distinctUntilChangedOrDie({ success($0) }, { success($0 == $1) })(source) +} + +public func distinctUntilChangedOrDie + (keySelector: (E) -> Result) + -> (Observable -> Observable) { + return { source in + return distinctUntilChangedOrDie(keySelector, { success($0 == $1) })(source) + } +} + +public func distinctUntilChangedOrDie + (comparer: (lhs: E, rhs: E) -> Result) + -> (Observable -> Observable) { + return { source in + return distinctUntilChangedOrDie({ success($0) }, comparer)(source) + } +} + +public func distinctUntilChangedOrDie + (keySelector: (E) -> Result, comparer: (lhs: K, rhs: K) -> Result) + -> (Observable -> Observable) { + return { source in + return DistinctUntilChanged(source: source, selector: keySelector, comparer: comparer) + } +} + +public func distinctUntilChanged(source: Observable) + -> Observable { + return distinctUntilChanged({ $0 }, { ($0 == $1) })(source) +} + +public func distinctUntilChanged + (keySelector: (E) -> K) + -> (Observable -> Observable) { + return { source in + return distinctUntilChanged(keySelector, { ($0 == $1) })(source) + } +} + +public func distinctUntilChanged + (comparer: (lhs: E, rhs: E) -> Bool) + -> (Observable -> Observable) { + return { source in + return distinctUntilChanged({ ($0) }, comparer)(source) + } +} + +public func distinctUntilChanged + (keySelector: (E) -> K, comparer: (lhs: K, rhs: K) -> Bool) + -> (Observable -> Observable) { + return { source in + return DistinctUntilChanged(source: source, selector: {success(keySelector($0)) }, comparer: { success(comparer(lhs: $0, rhs: $1))}) + } +} + +// do + +public func doOrDie + (eventHandler: (Event) -> Result) + -> (Observable -> Observable) { + return { source in + return Do(source: source, eventHandler: eventHandler) + } +} + +public func `do` + (eventHandler: (Event) -> Void) + -> (Observable -> Observable) { + return { source in + return Do(source: source, eventHandler: { success(eventHandler($0)) }) + } +} + +// map aka select + +public func mapOrDie + (selector: E -> Result) + -> (Observable -> Observable) { + return { source in + return selectOrDie(selector)(source) + } +} + +public func map + (selector: E -> R) + -> (Observable -> Observable) { + return { source in + return select(selector)(source) + } +} + +public func mapWithIndexOrDie + (selector: (E, Int) -> Result) + -> (Observable -> Observable) { + return { source in + return selectWithIndexOrDie(selector)(source) + } +} + +public func mapWithIndex + (selector: (E, Int) -> R) + -> (Observable -> Observable) { + return { source in + return selectWithIndex(selector)(source) + } +} + +// select + +public func selectOrDie + (selector: (E) -> Result) + -> (Observable -> Observable) { + return { source in + return Select(source: source, selector: selector) + } +} + +public func select + (selector: (E) -> R) + -> (Observable -> Observable) { + return { source in + return Select(source: source, selector: {success(selector($0)) }) + } +} + +public func selectWithIndexOrDie + (selector: (E, Int) -> Result) + -> (Observable -> Observable) { + return { source in + return Select(source: source, selector: selector) + } +} + +public func selectWithIndex + (selector: (E, Int) -> R) + -> (Observable -> Observable) { + return { source in + return Select(source: source, selector: {success(selector($0, $1)) }) + } +} + diff --git a/Rx/Rx/Observables/Observable+StandardSequenceOperators.swift b/Rx/Rx/Observables/Observable+StandardSequenceOperators.swift new file mode 100644 index 00000000..f9c7aa3e --- /dev/null +++ b/Rx/Rx/Observables/Observable+StandardSequenceOperators.swift @@ -0,0 +1,43 @@ +// +// Observable+StandardSequenceOperators.swift +// Rx +// +// Created by Krunoslav Zaher on 2/17/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +// filter aka where + +public func filterOrDie + (predicate: (E) -> Result) + -> (Observable -> Observable) { + return { source in + return whereOrDie(predicate)(source) + } +} + +public func filter + (predicate: (E) -> Bool) + -> (Observable -> Observable) { + return { source in + return `where`(predicate)(source) + } +} + +public func whereOrDie + (predicate: (E) -> Result) + -> (Observable -> Observable) { + return { source in + return WhereObservable(source: source, predicate: predicate) + } +} + +public func `where` + (predicate: (E) -> Bool) + -> (Observable -> Observable) { + return { source in + return WhereObservable(source: source, predicate: { success(predicate($0)) }) + } +} \ No newline at end of file diff --git a/Rx/Rx/Observables/Observable+Time.swift b/Rx/Rx/Observables/Observable+Time.swift new file mode 100644 index 00000000..ce02f979 --- /dev/null +++ b/Rx/Rx/Observables/Observable+Time.swift @@ -0,0 +1,19 @@ +// +// Observable+Time.swift +// Rx +// +// Created by Krunoslav Zaher on 3/22/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +// throttle + +public func throttle + (dueTime: S.TimeInterval, scheduler: S) + -> (Observable -> Observable) { + return { source in + return Throttle(source: source, dueTime: dueTime, scheduler: scheduler) + } +} \ No newline at end of file diff --git a/Rx/Rx/ObserverOf.swift b/Rx/Rx/ObserverOf.swift new file mode 100644 index 00000000..d7021eb2 --- /dev/null +++ b/Rx/Rx/ObserverOf.swift @@ -0,0 +1,47 @@ +// +// ObserverOf.swift +// Rx +// +// Created by Krunoslav Zaher on 2/28/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public struct ObserverOf : ObserverType { + typealias Element = ElementType + + private typealias ObserverSinkType = (Event) -> Result + + private let observer: ObserverSinkType + private let instance: AnyObject? + + /// Construct an instance whose `on(event)` calls `observer.on(event)` + public init(_ observer: O) { + var observerReference = observer // this is because swift compiler crashing + self.instance = observerReference + self.observer = { e in + return observerReference.on(e) + } + } + + func ofType() -> T? { + return self.instance as? T + } + + /// Send `event` to this observer. + public func on(event: Event) -> Result { + return observer(event) + } +} + +public func dispatch(event: Event, observers: [ObserverOf]?) -> Result { + if let observers = observers { + let results = observers.map { $0.on(event) } + let result = doAll(results) + return result + } + else { + return SuccessResult + } +} diff --git a/Rx/Rx/ObserverType.swift b/Rx/Rx/ObserverType.swift new file mode 100644 index 00000000..d19c284b --- /dev/null +++ b/Rx/Rx/ObserverType.swift @@ -0,0 +1,21 @@ +// +// ObserverType.swift +// Rx +// +// Created by Krunoslav Zaher on 2/8/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public protocol ObserverClassType : class, ObserverType { + +} + +public protocol ObserverType { + /// The type of event to be written to this observer. + typealias Element + + /// Send `event` to this observer. + mutating func on(event: Event) -> Result +} \ No newline at end of file diff --git a/Rx/Rx/Observers/AnonymousObserver.swift b/Rx/Rx/Observers/AnonymousObserver.swift new file mode 100644 index 00000000..9028a76d --- /dev/null +++ b/Rx/Rx/Observers/AnonymousObserver.swift @@ -0,0 +1,29 @@ +// +// AnonymousObserver.swift +// Rx +// +// Created by Krunoslav Zaher on 2/8/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public class AnonymousObserver : ObserverClassType { + typealias Element = ElementType + + typealias EventHandler = Event -> Result + + private let eventHandler : EventHandler + + public init(_ eventHandler: EventHandler) { + self.eventHandler = eventHandler + } + + public func on(event: Event) -> Result { + return self.eventHandler(event) + } + + func makeSafe(disposable: Disposable) -> AnonymousSafeObserver { + return AnonymousSafeObserver(eventHandler: eventHandler, disposable: disposable) + } +} \ No newline at end of file diff --git a/Rx/Rx/Observers/AnonymousSafeObserver.swift b/Rx/Rx/Observers/AnonymousSafeObserver.swift new file mode 100644 index 00000000..9a59103d --- /dev/null +++ b/Rx/Rx/Observers/AnonymousSafeObserver.swift @@ -0,0 +1,61 @@ +// +// AnonymousSafeObserver.swift +// Rx +// +// Created by Krunoslav Zaher on 2/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class AnonymousSafeObserver : ObserverClassType { + typealias Element = ElementType + + typealias State = Bool + typealias EventHandler = Event -> Result + + let eventHandler: EventHandler + let disposable: Disposable + + var lock = Lock() + var stopped: State = false + + init(eventHandler: EventHandler, disposable: Disposable) { + self.eventHandler = eventHandler + self.disposable = disposable + } + + func on(event: Event) -> Result { + switch event { + case .Next(let next): + // TODO: in general case where next values could come from any thread + // this is direct port from Rx + // this looks like wrong logic, but for most cases this will work + if stopped { + return SuccessResult + } + + let nextResult = eventHandler(event) + + return nextResult >>! { e in + self.disposable.dispose() + return .Error(e) + } + case .Error: fallthrough + case .Completed: + var stopped: Bool = lock.calculateLocked { + var stopped = self.stopped; + self.stopped = true; + return stopped + } + + if !stopped { + let result = self.eventHandler(event) + self.disposable.dispose() + return result + } + + return SuccessResult + } + } +} \ No newline at end of file diff --git a/Rx/Rx/Observers/AutoDetachObserver.swift b/Rx/Rx/Observers/AutoDetachObserver.swift new file mode 100644 index 00000000..e3717b77 --- /dev/null +++ b/Rx/Rx/Observers/AutoDetachObserver.swift @@ -0,0 +1,47 @@ +// +// AutoDetachObserver.swift +// Rx +// +// Created by Krunoslav Zaher on 2/15/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class AutoDetachObserver : ObserverBase { + private let observer : ObserverOf + private let m : SingleAssignmentDisposable + + init(observer: ObserverOf) { + self.observer = observer + self.m = SingleAssignmentDisposable() + + super.init() + } + + func setDisposable(disposable: Disposable) { + m.setDisposable(disposable) + } + + override func onCore(event: Event) -> Result { + switch event { + case .Next: + + return observer.on(event) >>! { e in + self.dispose() + return .Error(e) + } + + case .Completed: fallthrough + case .Error: + let result = observer.on(event) + dispose() + return result + } + } + + override func dispose() { + super.dispose() + m.dispose() + } +} \ No newline at end of file diff --git a/Rx/Rx/Observers/DisposedObserver.swift b/Rx/Rx/Observers/DisposedObserver.swift new file mode 100644 index 00000000..90071fbe --- /dev/null +++ b/Rx/Rx/Observers/DisposedObserver.swift @@ -0,0 +1,20 @@ +// +// DisposedObserver.swift +// Rx +// +// Created by Krunoslav Zaher on 2/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class DisposedObserver : ObserverType { + typealias Element = ElementType + + //static let Instance = DisposedObserver() + + func on(event: Event) -> Result { + rxFatalError("Already disposed") + return .Error(UnknownError) + } +} \ No newline at end of file diff --git a/Rx/Rx/Observers/DoneObserver.swift b/Rx/Rx/Observers/DoneObserver.swift new file mode 100644 index 00000000..a03c9a63 --- /dev/null +++ b/Rx/Rx/Observers/DoneObserver.swift @@ -0,0 +1,19 @@ +// +// DoneObserver.swift +// Rx +// +// Created by Krunoslav Zaher on 2/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class DoneObserver : ObserverType { + typealias Element = ElementType + + //static let Instance = DoneObserver() + + func on(event: Event) -> Result { + return SuccessResult + } +} \ No newline at end of file diff --git a/Rx/Rx/Observers/NopObserver.swift b/Rx/Rx/Observers/NopObserver.swift new file mode 100644 index 00000000..2318e63f --- /dev/null +++ b/Rx/Rx/Observers/NopObserver.swift @@ -0,0 +1,18 @@ +// +// NopObserver.swift +// Rx +// +// Created by Krunoslav Zaher on 2/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class NopObserver : ObserverClassType { + typealias Element = ElementType + //static let Instance = NopObserver() + + func on(event: Event) -> Result { + return SuccessResult + } +} \ No newline at end of file diff --git a/Rx/Rx/Observers/ObserverBase.swift b/Rx/Rx/Observers/ObserverBase.swift new file mode 100644 index 00000000..694b43dd --- /dev/null +++ b/Rx/Rx/Observers/ObserverBase.swift @@ -0,0 +1,68 @@ +// +// ObserverBase.swift +// Rx +// +// Created by Krunoslav Zaher on 2/15/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public class ObserverBase : ObserverClassType, Disposable { + typealias Element = ElementType + + var lock = Lock() + var isStopped: Bool = false + + public init() { + } + + public func on(event: Event) -> Result { + switch event { + case .Next: + if !isStopped { + return onCore(event) + } + else { + return SuccessResult + } + //return abstractMethod() + case .Error: fallthrough + case .Completed: + var wasStopped: Bool = lock.calculateLocked { + var wasStopped = self.isStopped + self.isStopped = true + return wasStopped + } + + if !wasStopped { + return self.onCore(event) + } + return SuccessResult + } + } + + public func onCore(event: Event) -> Result { + return SuccessResult + } + + func fail(error: ErrorType) -> Result { + var wasStopped: Bool = lock.calculateLocked { + var wasStopped = self.isStopped + self.isStopped = true + return wasStopped + } + + if !wasStopped { + return self.onCore(.Error(error)) >>> { + success(true) + } + } + else { + return success(false) + } + } + + public func dispose() { + } +} \ No newline at end of file diff --git a/Rx/Rx/Observers/SafeObserver.swift b/Rx/Rx/Observers/SafeObserver.swift new file mode 100644 index 00000000..b03fd945 --- /dev/null +++ b/Rx/Rx/Observers/SafeObserver.swift @@ -0,0 +1,56 @@ +// +// SafeObserver.swift +// Rx +// +// Created by Krunoslav Zaher on 2/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +class SafeObserver : ObserverClassType { + typealias Element = ElementType + + let observer: ObserverOf + let disposable: Disposable + + class func create(observer: ObserverOf, disposable: Disposable) -> ObserverOf { + if let anonymousObserver: AnonymousObserver = observer.ofType() { + let anonymousSafeObserver: AnonymousSafeObserver = anonymousObserver.makeSafe(disposable) + return ObserverOf(anonymousSafeObserver) + } + else { + let safeObserver: SafeObserver = SafeObserver(observer: observer, disposable: disposable) + return ObserverOf(safeObserver) + } + } + + init(observer: ObserverOf, disposable: Disposable) { + self.observer = observer + self.disposable = disposable +#if DEBUG + OSAtomicIncrement32(&resourceCount) +#endif + } + + func on(event: Event) -> Result { + switch event { + case .Next: + return self.observer.on(event) >>! { e in + self.disposable.dispose() + return .Error(e) + } + case .Completed: fallthrough + case .Error: + let result = self.observer.on(event) + self.disposable.dispose() + return result + } + } + + deinit { +#if DEBUG + OSAtomicDecrement32(&resourceCount) +#endif + } +} \ No newline at end of file diff --git a/Rx/Rx/Result.swift b/Rx/Rx/Result.swift new file mode 100644 index 00000000..f4b1b103 --- /dev/null +++ b/Rx/Rx/Result.swift @@ -0,0 +1,205 @@ +// +// Result.swift +// Rx +// +// Created by Krunoslav Zaher on 2/12/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +// Represents computation result. +// +// The result can be either successful or failure. +// +// Result is a `Either` Haskell monad. +// The name `Result` was chosen because it better describes it's common usage. +// +public enum Result { + // Box is used is because swift compiler doesn't know + // how to handle `Success(ResultType)` and it crashes. + case Success(Box) + case Error(ErrorType) + + init(_ value: ResultType) { + self = .Success(Box(value)) + } + + init(_ error: ErrorType) { + self = .Error(error) + } + + public var error: ErrorType? { + get { + switch self { + case .Error(let error): return error + default: return nil + } + } + } + + public var value : ResultType? { + get { + switch self { + case .Success(let value): return value.value + default: return nil + } + } + } +} + +public let SuccessResult = success(()) + +// Monad implementation for Result + +// Monadic `return` implementation +// +// Naming clash with `return` keyword, `Success` function is more practical replacement. +public func `return`(value: T) -> Result { + return .Success(Box(value)) +} + +// Monadic `bind` implementation +// +// `>==` is being used because `>>=` is already reserved operator in Swift. +infix operator >== { associativity left precedence 95 } + +public func >== (lhs: Result, @noescape rhs: (In) -> Result) -> Result { + switch lhs { + case .Success(let result): return rhs(result.value) + case .Error(let error): return .Error(error) + } +} + +public func >== (lhs: Result, @noescape rhs: (In) -> Out) -> Result { + switch lhs { + case .Success(let result): return success(rhs(result.value)) + case .Error(let error): return .Error(error) + } +} + +// Control flow operators for `Result` + +// In case `lhs` succeeded, result equals `rhs()` +// In case `lhs` failed, just propagates failure +infix operator >>> { associativity left precedence 95 } + +public func >>> (lhs: Result, @noescape rhs: () -> Out) -> Result { + switch lhs { + case .Success: return success(rhs()) + case .Error(let error): return .Error(error) + } +} + +public func >>> (lhs: Result, @noescape rhs: () -> Result) -> Result { + switch lhs { + case .Success: return rhs() + case .Error(let error): return .Error(error) + } +} + +// catch / fail operator +// +// In case `lhs` succeeded, result is propagated +// In case `lhs` failed, result is equal to `rhs(error)` (catch clause) result +infix operator >>! { associativity left precedence 95 } + +public func >>! (lhs: Result, @noescape rhs: (ErrorType) -> Result) -> Result { + switch lhs { + case .Error(let error): + return rhs(error) + default: + return lhs + } +} + +// This shouldn't be a common operator to use (although it does come in handy sometime) +// +// In case `Result` contains value it will return value, otherwise it will throw an exception +// This should only be used when the result is already checked for failure +// +// `>==` operator or lift functions are preferred. +// +prefix operator * { } +public prefix func *(result: Result) -> T { + switch result { + case .Success(let value): return value.value + default: + var result: T? = nil + return result! + } +} + +// Convenience constructor +public func success(value: T) -> Result { + return .Success(Box(value)) +} + +// aggregate `Result` functions + +public func doAll(results: [Result]) -> Result { + var failures = results.filter { $0.error != nil } + if failures.count > 0 { + return createCompositeFailure(failures) + } + else { + return SuccessResult + } +} + + +// lift functions + +// "Lifts" functions that take normal arguments to functions that take `Result` monad arguments. +// Unfortunatelly these are not generic `Monad` lift functions because +// creating generic lift functions that work for arbitrary monads is a lot more tricky. + +func lift(function: (T1) -> TRet) -> (Result) -> Result { + return { arg1 in + return arg1 >== { value1 in + return success(function(value1)) + } + } +} + +func lift(function: (T1, T2) -> TRet) -> (Result, Result) -> Result { + return { arg1, arg2 in + return arg1 >== { value1 in + return arg2 >== { value2 in + return success(function(value1, value2)) + } + } + } +} + +func lift(function: (T1, T2, T3) -> TRet) -> (Result, Result, Result) -> Result { + return { arg1, arg2, arg3 in + return arg1 >== { value1 in + return arg2 >== { value2 in + return arg3 >== { value3 in + return success(function(value1, value2, value3)) + } + } + } + } +} + +// error conversion functions + +public func replaceErrorWith(result: Result, errorValue: T) -> T { + switch result { + case .Success(let boxedValue): + return boxedValue.value + case .Error: + return errorValue + } +} + +public func replaceErrorWithNil(result: Result) -> T? { + switch result { + case .Success(let boxedValue): + return boxedValue.value + case .Error: + return nil + } +} \ No newline at end of file diff --git a/Rx/Rx.h b/Rx/Rx/Rx.h similarity index 100% rename from Rx/Rx.h rename to Rx/Rx/Rx.h diff --git a/Rx/Rx/Rx.pch b/Rx/Rx/Rx.pch new file mode 100644 index 00000000..00125e2a --- /dev/null +++ b/Rx/Rx/Rx.pch @@ -0,0 +1,14 @@ +// +// Rx.pch +// Rx +// +// Created by Krunoslav Zaher on 2/12/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +#ifndef Rx_Rx_pch +#define Rx_Rx_pch + +#import + +#endif diff --git a/Rx/Rx/Rx.swift b/Rx/Rx/Rx.swift new file mode 100644 index 00000000..a46538fb --- /dev/null +++ b/Rx/Rx/Rx.swift @@ -0,0 +1,69 @@ +// +// Rx.swift +// Rx +// +// Created by Krunoslav Zaher on 2/14/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +#if DEBUG +// counts resources +// used to detect resource leaks during unit tests +// it's not perfect, but works well +public var resourceCount: Int32 = 0 +#endif + +// This is the pipline operator +// a >- b >- c == c(b(a)) +// The reason this one is chosen for now is because +// * It's subtle, doesn't add a lot of visual noise +// * It's short +// * It kind of looks like ASCII art horizontal sink to the right +// +infix operator >- { associativity left precedence 91 } + +public func >- (source: In, @noescape transform: In -> Out) -> Out { + return transform(source) +} + +func contract(@autoclosure condition: () -> Bool) { + if !condition() { + let exception = NSException(name: "ContractError", reason: "Contract failed", userInfo: nil) + exception.raise() + } +} + +// Because ... Swift +// Because ... Crash +// Because ... compiler bugs + +// Wrapper for any value type +public class Box { + public let value : T + public init (_ value: T) { + self.value = value + } +} + +// Wrapper for any value type that can be mutated +public class MutatingBox { + public var value : T + public init (_ value: T) { + self.value = value + } +} + +// Swift doesn't have a concept of abstract metods. +// This function is being used as a runtime check that abstract methods aren't being called. +func abstractMethod() -> T { + rxFatalError("Abstract method") + let dummyValue: T? = nil + return dummyValue! +} + +func rxFatalError(lastMessage: String) { + // The temptation to comment this line is great, but please don't, it's for your own good. The choice is yours. + fatalError(lastMessage) +} diff --git a/Rx/Rx/Scheduler.swift b/Rx/Rx/Scheduler.swift new file mode 100644 index 00000000..8ada7e1d --- /dev/null +++ b/Rx/Rx/Scheduler.swift @@ -0,0 +1,54 @@ +// +// Scheduler.swift +// Rx +// +// Created by Krunoslav Zaher on 2/8/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public protocol ImmediateScheduler { + func schedule(state: StateType, action: (StateType) -> Result) -> Result +} + +public protocol Scheduler: ImmediateScheduler { + typealias TimeInterval + typealias Time + + var now : Time { + get + } + + func schedule(state: StateType, dueTime: TimeInterval, action: (StateType) -> Result) -> Result +} + + +// This is being called every time `Rx` scheduler performs action to +// check the result of the computation. +// +// The default implementation will throw an Exception if the result failed. +// +// It's probably best to make sure all of the errors have been handled before +// the computation finishes, but it's not unreasonable to change the implementation +// for release builds to silently fail (although I would not recommended). +// +// Changing default behavior is not recommended because possible data corruption +// is "usually" a lot worse then letting program to crash. +// +func ensureScheduledSuccessfully(result: Result) -> Result { + switch result { + case .Error(let error): + return errorDuringScheduledAction(error); + default: break + } + + return SuccessResult +} + +func errorDuringScheduledAction(error: ErrorType) -> Result { + let exception = NSException(name: "ScheduledActionError", reason: "Error happened during scheduled action execution", userInfo: ["error": error]) + exception.raise() + + return SuccessResult +} diff --git a/Rx/Rx/Scheduler/DispatchQueueScheduler.swift b/Rx/Rx/Scheduler/DispatchQueueScheduler.swift new file mode 100644 index 00000000..bcf652bc --- /dev/null +++ b/Rx/Rx/Scheduler/DispatchQueueScheduler.swift @@ -0,0 +1,62 @@ +// +// DispatchQueueScheduler.swift +// Rx +// +// Created by Krunoslav Zaher on 2/8/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public class DispatchQueueScheduler : Scheduler { + public typealias Time = NSDate + public typealias TimeInterval = NSTimeInterval + + private let queue : dispatch_queue_t + + public init(queue: dispatch_queue_t) { + self.queue = queue + + } + + // DISPATCH_QUEUE_PRIORITY_DEFAULT + // DISPATCH_QUEUE_PRIORITY_HIGH + // DISPATCH_QUEUE_PRIORITY_LOW + convenience public init(priority: Int) { + self.init(queue: dispatch_get_global_queue(priority, UInt(0))) + } + + public var now : NSDate { + get { + return NSDate() + } + } + + class func convertTimeIntervalToDispatchTime(timeInterval: NSTimeInterval) -> dispatch_time_t { + return dispatch_time(DISPATCH_TIME_NOW, Int64(timeInterval * Double(NSEC_PER_SEC) / 1000)) + } + + public func schedule(state: StateType, action: (StateType) -> Result) -> Result { + dispatch_async(self.queue, { + ensureScheduledSuccessfully(action(state)) + }) + + return success(DefaultDisposable()) + } + + public func schedule(state: StateType, dueTime: NSTimeInterval, action: (StateType) -> Result) -> Result { + let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue) + + let dispatchInterval = MainScheduler.convertTimeIntervalToDispatchTime(dueTime) + + dispatch_source_set_timer(timer, dispatchInterval, DISPATCH_TIME_FOREVER, 0) + dispatch_source_set_event_handler(timer, { + ensureScheduledSuccessfully(action(state)) + }) + dispatch_resume(timer) + + return success(AnonymousDisposable { + dispatch_source_cancel(timer) + }) + } +} \ No newline at end of file diff --git a/Rx/Rx/Scheduler/ImmediateSchedulerOnCurrentThread.swift b/Rx/Rx/Scheduler/ImmediateSchedulerOnCurrentThread.swift new file mode 100644 index 00000000..aeec37f3 --- /dev/null +++ b/Rx/Rx/Scheduler/ImmediateSchedulerOnCurrentThread.swift @@ -0,0 +1,15 @@ +// +// ImmediateSchedulerOnCurrentThread.swift +// Rx +// +// Created by Krunoslav Zaher on 2/15/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public struct ImmediateSchedulerOnCurrentThread : ImmediateScheduler { + public func schedule(state: StateType, action: (StateType) -> Result) -> Result { + return action(state) >>> { (DefaultDisposable()) } + } +} \ No newline at end of file diff --git a/Rx/Rx/Scheduler/MainScheduler.swift b/Rx/Rx/Scheduler/MainScheduler.swift new file mode 100644 index 00000000..78e070f1 --- /dev/null +++ b/Rx/Rx/Scheduler/MainScheduler.swift @@ -0,0 +1,41 @@ +// +// MainScheduler.swift +// Rx +// +// Created by Krunoslav Zaher on 2/8/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public class MainScheduler : DispatchQueueScheduler { + struct Singleton { + static let sharedInstance = MainScheduler() + } + + init() { + super.init(queue: dispatch_get_main_queue()) + } + + public class var sharedInstance: MainScheduler { + get { + return Singleton.sharedInstance + } + } + + public class func ensureExecutingOnScheduler() { + if !NSThread.currentThread().isMainThread { + rxFatalError("Executing on wrong scheduler") + } + } + + public override func schedule(state: StateType, action: (StateType) -> Result) -> Result { + if NSThread.currentThread().isMainThread { + ensureScheduledSuccessfully(action(state)) + + return success(DefaultDisposable()) + } + + return super.schedule(state, action: action) + } +} diff --git a/Rx/Rx/Scheduler/OperationQueueScheduler.swift b/Rx/Rx/Scheduler/OperationQueueScheduler.swift new file mode 100644 index 00000000..2de8b9a0 --- /dev/null +++ b/Rx/Rx/Scheduler/OperationQueueScheduler.swift @@ -0,0 +1,28 @@ +// +// OperationQueueScheduler.swift +// Rx +// +// Created by Krunoslav Zaher on 4/4/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public class OperationQueueScheduler: ImmediateScheduler { + private let operationQueue: NSOperationQueue + + public init(operationQueue: NSOperationQueue) { + self.operationQueue = operationQueue + } + + public func schedule(state: StateType, action: (StateType) -> Result) -> Result { + let operation = NSBlockOperation { + ensureScheduledSuccessfully(action(state)) + } + self.operationQueue.addOperation(operation) + + return success(AnonymousDisposable { + operation.cancel() + }) + } +} \ No newline at end of file diff --git a/Rx/Rx/Scheduler/SchedulerDefaults.swift b/Rx/Rx/Scheduler/SchedulerDefaults.swift new file mode 100644 index 00000000..2cbdcd93 --- /dev/null +++ b/Rx/Rx/Scheduler/SchedulerDefaults.swift @@ -0,0 +1,17 @@ +// +// SchedulerDefaults.swift +// Rx +// +// Created by Krunoslav Zaher on 2/15/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +struct SchedulerDefaults { + static var ConstantTimeOperations : ImmediateScheduler { + get { + return ImmediateSchedulerOnCurrentThread() + } + } +} \ No newline at end of file diff --git a/Rx/Rx/Subjects/ConnectableObservableType.swift b/Rx/Rx/Subjects/ConnectableObservableType.swift new file mode 100644 index 00000000..d9d457cd --- /dev/null +++ b/Rx/Rx/Subjects/ConnectableObservableType.swift @@ -0,0 +1,15 @@ +// +// ConnectableObservableType.swift +// Rx +// +// Created by Krunoslav Zaher on 3/1/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public class ConnectableObservableType: Observable { + public func connect() -> Result { + return abstractMethod() + } +} \ No newline at end of file diff --git a/Rx/Rx/Subjects/SubjectType.swift b/Rx/Rx/Subjects/SubjectType.swift new file mode 100644 index 00000000..759ba95b --- /dev/null +++ b/Rx/Rx/Subjects/SubjectType.swift @@ -0,0 +1,17 @@ +// +// SubjectType.swift +// Rx +// +// Created by Krunoslav Zaher on 3/1/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public class SubjectType : Observable, ObserverClassType { + typealias Element = SourceType + + public func on(x: Event) -> Result { + return abstractMethod() + } +} \ No newline at end of file diff --git a/Rx/RxTests/Info.plist b/Rx/RxTests/Info.plist new file mode 100644 index 00000000..7f80ab77 --- /dev/null +++ b/Rx/RxTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + Krunoslav-Zaher.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Rx/RxTests/TestImplementations/Mocks/ColdObservable.swift b/Rx/RxTests/TestImplementations/Mocks/ColdObservable.swift new file mode 100644 index 00000000..d03a1685 --- /dev/null +++ b/Rx/RxTests/TestImplementations/Mocks/ColdObservable.swift @@ -0,0 +1,52 @@ +// +// ColdObservable.swift +// Rx +// +// Created by Krunoslav Zaher on 3/14/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Rx + +class ColdObservable: Observable { + typealias Events = Recorded + typealias ObserverType = ObserverOf + + let testScheduler: TestScheduler + + var subscriptions: [Subscription] + var recordedEvents: [Events] + var observers: Bag> + + init(testScheduler: TestScheduler, recordedEvents: [Events]) { + self.testScheduler = testScheduler + + self.recordedEvents = recordedEvents + self.subscriptions = [] + self.observers = Bag() + + super.init() + } + + override func subscribe(observer: ObserverOf) -> Result { + let key = observers.put(observer) + subscriptions.append(Subscription(subscribe: self.testScheduler.now)) + + let i = self.subscriptions.count - 1 + + for recordedEvent in recordedEvents { + testScheduler.scheduleRelative((), after: recordedEvent.time, action: { (Int) in + return doAll(self.observers.all.map { o in o.on(recordedEvent.event) }) + }) + } + + return success(AnonymousDisposable { + let removed = self.observers.removeKey(key) + assert(removed != nil); + + let existing = self.subscriptions[i] + self.subscriptions[i] = Subscription(existing.subscribe, self.testScheduler.now) + + }) + } +} \ No newline at end of file diff --git a/Rx/RxTests/TestImplementations/Mocks/HotObservable.swift b/Rx/RxTests/TestImplementations/Mocks/HotObservable.swift new file mode 100644 index 00000000..1ebf1241 --- /dev/null +++ b/Rx/RxTests/TestImplementations/Mocks/HotObservable.swift @@ -0,0 +1,56 @@ +// +// HotObservable.swift +// Rx +// +// Created by Krunoslav Zaher on 2/14/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +class HotObservable : Observable { + typealias Events = Recorded + typealias ObserverType = ObserverOf + + let testScheduler: TestScheduler + + var subscriptions: [Subscription] + var recordedEvents: [Events] + var observers: Bag> + + init(testScheduler: TestScheduler, recordedEvents: [Events]) { + self.testScheduler = testScheduler + + self.recordedEvents = recordedEvents + self.subscriptions = [] + self.observers = Bag() + + super.init() + + for recordedEvent in recordedEvents { + testScheduler.schedule((), date: recordedEvent.time, action: { (Int) in + return doAll(self.observers.all.map { o in o.on(recordedEvent.event) }) + }) + } + } + + override func subscribe(observer: ObserverOf) -> Result { + let key = observers.put(observer) + subscriptions.append(Subscription(subscribe: self.testScheduler.now)) + + let i = self.subscriptions.count - 1 + + return success(AnonymousDisposable { + let removed = self.observers.removeKey(key) + assert(removed != nil) + + let existing = self.subscriptions[i] + self.subscriptions[i] = Subscription(existing.subscribe, self.testScheduler.now) + }) + } +} + +public func == (lhs: Observable, rhs: Observable) -> Bool { + return lhs === rhs +} \ No newline at end of file diff --git a/Rx/RxTests/TestImplementations/Mocks/MockObserver.swift b/Rx/RxTests/TestImplementations/Mocks/MockObserver.swift new file mode 100644 index 00000000..5bd3aba6 --- /dev/null +++ b/Rx/RxTests/TestImplementations/Mocks/MockObserver.swift @@ -0,0 +1,27 @@ +// +// MockObserver.swift +// Rx +// +// Created by Krunoslav Zaher on 2/15/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +class MockObserver : ObserverClassType { + typealias Element = ElementType + + let scheduler: TestScheduler + var messages: [Recorded] + + init(scheduler: TestScheduler) { + self.scheduler = scheduler + self.messages = [] + } + + func on(event: Event) -> Result { + messages.append(Recorded(time: scheduler.now, event: event)) + return SuccessResult + } +} \ No newline at end of file diff --git a/Rx/RxTests/TestImplementations/Mocks/Observable.Extensions.swift b/Rx/RxTests/TestImplementations/Mocks/Observable.Extensions.swift new file mode 100644 index 00000000..0bc3c6e1 --- /dev/null +++ b/Rx/RxTests/TestImplementations/Mocks/Observable.Extensions.swift @@ -0,0 +1,13 @@ +// +// Observable.Extensions.swift +// Rx +// +// Created by Krunoslav Zaher on 3/14/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Rx + +extension Observable : Equatable { + +} \ No newline at end of file diff --git a/Rx/RxTests/TestImplementations/Mocks/TestObservable.swift b/Rx/RxTests/TestImplementations/Mocks/TestObservable.swift new file mode 100644 index 00000000..cb1e0eee --- /dev/null +++ b/Rx/RxTests/TestImplementations/Mocks/TestObservable.swift @@ -0,0 +1,15 @@ +// +// TestObservable.swift +// Rx +// +// Created by Krunoslav Zaher on 2/14/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +/*protocol TestObservable : Observable { + var subscriptions: [Subscription] { get } + var recordedEvents: [Recorded] { get } +}*/ \ No newline at end of file diff --git a/Rx/RxTests/TestImplementations/Mocks/TestObserver.swift b/Rx/RxTests/TestImplementations/Mocks/TestObserver.swift new file mode 100644 index 00000000..6f750e8b --- /dev/null +++ b/Rx/RxTests/TestImplementations/Mocks/TestObserver.swift @@ -0,0 +1,14 @@ +// +// TestObserver.swift +// Rx +// +// Created by Krunoslav Zaher on 2/15/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +/*protocol TestObserver : Observer { + var messages : [Recorded] { get } +}*/ \ No newline at end of file diff --git a/Rx/RxTests/TestImplementations/Recorded.swift b/Rx/RxTests/TestImplementations/Recorded.swift new file mode 100644 index 00000000..92559e78 --- /dev/null +++ b/Rx/RxTests/TestImplementations/Recorded.swift @@ -0,0 +1,30 @@ +// +// Recorded.swift +// Rx +// +// Created by Krunoslav Zaher on 2/14/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +struct Recorded : Printable, Equatable { + let time: Time + let event: Event + + init(time: Time, event: Event) { + self.time = time + self.event = event + } + + var description: String { + get { + return "\(event) @ \(time)" + } + } +} + +func == (lhs: Recorded, rhs: Recorded) -> Bool { + return lhs.time == rhs.time && lhs.event == rhs.event +} \ No newline at end of file diff --git a/Rx/RxTests/TestImplementations/Schedulers/TestScheduler.swift b/Rx/RxTests/TestImplementations/Schedulers/TestScheduler.swift new file mode 100644 index 00000000..8185c3d7 --- /dev/null +++ b/Rx/RxTests/TestImplementations/Schedulers/TestScheduler.swift @@ -0,0 +1,59 @@ +// +// TestScheduler.swift +// Rx +// +// Created by Krunoslav Zaher on 2/8/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +class TestScheduler : VirtualTimeSchedulerBase { + func advanceTimeFor(interval: Time) { + + } + + func createHotObservable(events: [Recorded]) -> HotObservable { + return HotObservable(testScheduler: self, recordedEvents: events) + } + + func createColdObservable(events: [Recorded]) -> ColdObservable { + return ColdObservable(testScheduler: self, recordedEvents: events) + } + + func start(created: Time, subscribed: Time, disposed: Time, create: () -> Observable) -> MockObserver { + var source : Observable? = nil + var subscription : Disposable? = nil + var observer = MockObserver(scheduler: self) + + let state : Void = () + + self.schedule(state, date: created) { (state) in + source = create() + return SuccessResult + } + + self.schedule(state, date: subscribed) { (state) in + subscription = source!.subscribe(ObserverOf(observer)).value! + return SuccessResult + } + + self.schedule(state, date: disposed) { (state) in + subscription!.dispose() + return SuccessResult + } + + start() + + return observer + } + + func start(disposed: Time, create: () -> Observable) -> MockObserver { + return start(RxTest.Defaults.created, subscribed: RxTest.Defaults.subscribed, disposed: disposed, create: create) + } + + func start(create: () -> Observable) -> MockObserver { + return start(RxTest.Defaults.created, subscribed: RxTest.Defaults.subscribed, disposed: RxTest.Defaults.disposed, create: create) + } +} \ No newline at end of file diff --git a/Rx/RxTests/TestImplementations/Schedulers/VirtualTimeSchedulerBase.swift b/Rx/RxTests/TestImplementations/Schedulers/VirtualTimeSchedulerBase.swift new file mode 100644 index 00000000..3f4f9416 --- /dev/null +++ b/Rx/RxTests/TestImplementations/Schedulers/VirtualTimeSchedulerBase.swift @@ -0,0 +1,118 @@ +// +// VirtualTimeSchedulerBase.swift +// Rx +// +// Created by Krunoslav Zaher on 2/14/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +protocol ScheduledItem { + +} + +class VirtualTimeSchedulerBase : Scheduler { + typealias Time = Int + typealias TimeInterval = Int + + typealias ScheduledItem = (() -> Result, AnyObject, Int, time: Int) + + var clock : Time + var enabled : Bool + + var now : Time { + get { + return self.clock + } + } + + private var schedulerQueue : [ScheduledItem] = [] + private var ID : Int = 0 + + init(initialClock: Time) { + self.clock = initialClock + self.enabled = false + } + + func scheduleRelative(state: StateType, after: Time, action: (StateType) -> Result) -> Result { + return self.schedule(state, date: Time(after + self.clock), action: action) + } + + func schedule(state: StateType, action: (StateType) -> Result) -> Result { + return self.scheduleRelative(state, after: 0, action: action) + } + + func schedule(state: StateType, dueTime: TimeInterval, action: (StateType) -> Result) -> Result { + return schedule(state, date: now + dueTime, action: action) + } + + func schedule(state: StateType, date: Time, action: (StateType) -> Result) -> Result { + let latestID = self.ID + ID = ID + 1 + + let timeInterval = self.clock + + let actionDescription : ScheduledItem = ({ + return action(state) + }, Box(state), latestID, date) + + schedulerQueue.append(actionDescription) + + return success(AnonymousDisposable { + var index : Int = 0 + + for (_, _, id, _) in self.schedulerQueue { + if id == latestID { + self.schedulerQueue.removeAtIndex(index) + return + } + + index++ + } + }) + } + + func start() { + if !enabled { + enabled = true + do { + if let next = getNext() { + if next.time > self.now { + self.clock = next.time + } + + (next.0)() + } + else { + enabled = false; + } + + } while enabled + } + } + + func getNext() -> ScheduledItem? { + var minDate = Time.max + var minElement : ScheduledItem? = nil + var minIndex = -1 + var index = 0 + + for item in self.schedulerQueue { + if item.time < minDate { + minDate = item.time + minElement = item + minIndex = index + } + + index++ + } + + if minElement != nil { + self.schedulerQueue.removeAtIndex(minIndex) + } + + return minElement + } +} \ No newline at end of file diff --git a/Rx/RxTests/TestImplementations/Subscription.swift b/Rx/RxTests/TestImplementations/Subscription.swift new file mode 100644 index 00000000..79d5114e --- /dev/null +++ b/Rx/RxTests/TestImplementations/Subscription.swift @@ -0,0 +1,41 @@ +// +// Subscription.swift +// Rx +// +// Created by Krunoslav Zaher on 2/14/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +struct Subscription : Equatable, Hashable, Printable { + let subscribe : Time + let unsubscribe : Time + + init(subscribe: Time) { + self.subscribe = subscribe + self.unsubscribe = Int.max + } + + init(_ subscribe: Time, _ unsubscribe: Time) { + self.subscribe = subscribe + self.unsubscribe = unsubscribe + } + + var hashValue : Int { + get { + return subscribe.hashValue ^ unsubscribe.hashValue + } + } + + var description : String { + get { + let infiniteText = "Infinity" + return "(\(subscribe) : \(unsubscribe != Time.max ? String(unsubscribe) : infiniteText))" + } + } +} + +func == (lhs: Subscription, rhs: Subscription) -> Bool { + return lhs.subscribe == rhs.subscribe && lhs.unsubscribe == rhs.unsubscribe +} \ No newline at end of file diff --git a/Rx/RxTests/TestImplementations/TestExtensions.swift b/Rx/RxTests/TestImplementations/TestExtensions.swift new file mode 100644 index 00000000..bc799558 --- /dev/null +++ b/Rx/RxTests/TestImplementations/TestExtensions.swift @@ -0,0 +1,14 @@ +// +// TestExtensions.swift +// Rx +// +// Created by Krunoslav Zaher on 2/15/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import XCTest + +func assertEquals(lhs: T, rhs: T) { + XCTAssertTrue(lhs == rhs) +} \ No newline at end of file diff --git a/Rx/RxTests/Tests/AssumptionsTest.swift b/Rx/RxTests/Tests/AssumptionsTest.swift new file mode 100644 index 00000000..2a3bf8ff --- /dev/null +++ b/Rx/RxTests/Tests/AssumptionsTest.swift @@ -0,0 +1,38 @@ +// +// AssumptionsTest.swift +// Rx +// +// Created by Krunoslav Zaher on 2/14/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import XCTest +import Rx + +class AssumptionsTest : RxTest { + func testAssumptionInCodeIsThatArraysAreStructs() { + var a = ["a"] + var b = a + b += ["b"] + + XCTAssert(a == ["a"]) + XCTAssert(b == ["a", "b"]) + } + + func testResourceLeaksDetectionIsTurnedOn() { +#if DEBUG + let startResourceCount = resourceCount + + var observable: Observable! = Observable() + + XCTAssertEqual(resourceCount, startResourceCount + 1) + + observable = nil + + XCTAssertEqual(resourceCount, startResourceCount) +#else + XCTAssert(false, "Can't run unit tests in release mode") +#endif + } +} \ No newline at end of file diff --git a/Rx/RxTests/Tests/ConcurrencyTest.swift b/Rx/RxTests/Tests/ConcurrencyTest.swift new file mode 100644 index 00000000..b8b75d1d --- /dev/null +++ b/Rx/RxTests/Tests/ConcurrencyTest.swift @@ -0,0 +1,24 @@ +// +// ConcurrencyTest.swift +// Rx +// +// Created by Krunoslav Zaher on 2/12/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +import Foundation +import XCTest +import Rx + +class ConcurrencyTest : XCTestCase { + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } + +} \ No newline at end of file diff --git a/Rx/RxTests/Tests/DisposableTest.swift b/Rx/RxTests/Tests/DisposableTest.swift new file mode 100644 index 00000000..dc262582 --- /dev/null +++ b/Rx/RxTests/Tests/DisposableTest.swift @@ -0,0 +1,71 @@ +// +// DisposableTest.swift +// Rx +// +// Created by Krunoslav Zaher on 2/8/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import XCTest +import Rx + +class DisposableTest : RxTest { + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } + + func testActionDisposable() { + var counter = 0 + + let disposable = AnonymousDisposable { + counter = counter + 1 + } + + XCTAssert(counter == 0) + disposable.dispose() + XCTAssert(counter == 1) + disposable.dispose() + XCTAssert(counter == 1) + } + + func testHotObservable_Disposing() { + let scheduler = TestScheduler(initialClock: 0) + + var xs = scheduler.createHotObservable([ + next(110, 1), + next(180, 2), + next(230, 3), + next(270, 4), + next(340, 5), + next(380, 6), + next(390, 7), + next(450, 8), + next(470, 9), + next(560, 10), + next(580, 11), + completed(600) + ]) + + let res = scheduler.start(400) { () -> Observable in + return xs + } + + XCTAssertEqual(res.messages, [ + next(230, 3), + next(270, 4), + next(340, 5), + next(380, 6), + next(390, 7), + ]) + + XCTAssertEqual(xs.subscriptions, [ + Subscription(200, 400) + ]) + } + +} \ No newline at end of file diff --git a/Rx/RxTests/Tests/Observable+AggregateTest.swift b/Rx/RxTests/Tests/Observable+AggregateTest.swift new file mode 100644 index 00000000..45e25e28 --- /dev/null +++ b/Rx/RxTests/Tests/Observable+AggregateTest.swift @@ -0,0 +1,339 @@ +// +// Observable+AggregateTest.swift +// Rx +// +// Created by Krunoslav Zaher on 4/2/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import XCTest +import Rx + +class ObservableAggregateTest : RxTest { + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } +} + +extension ObservableAggregateTest { + func test_AggregateWithSeed_Empty() { + let scheduler = TestScheduler(initialClock: 0) + + var xs = scheduler.createHotObservable([ + next(150, 1), + completed(250) + ]) + + + let res = scheduler.start { xs >- aggregate(42, +) } + + let correctMessages = [ + next(250, 42), + completed(250) + ] + + let correctSubscriptions = [ + Subscription(200, 250) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func test_AggregateWithSeed_Return() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 24), + completed(250) + ]) + + let res = scheduler.start { xs >- aggregate(42, +) } + + let correctMessages = [ + next(250, 42 + 24), + completed(250) + ] + + let correctSubscriptions = [ + Subscription(200, 250) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func test_AggregateWithSeed_Throw() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + error(210, testError), + ]) + + let res = scheduler.start { xs >- aggregate(42, +) } + + let correctMessages: [Recorded] = [ + error(210, testError) + ] + + let correctSubscriptions = [ + Subscription(200, 210) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func test_AggregateWithSeed_Never() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + ]) + + let res = scheduler.start { xs >- aggregate(42, +) } + + let correctMessages: [Recorded] = [ + ] + + let correctSubscriptions = [ + Subscription(200, 1000) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func test_AggregateWithSeed_Range() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 0), + next(220, 1), + next(230, 2), + next(240, 3), + next(250, 4), + completed(260) + ]) + + let res = scheduler.start { xs >- aggregate(42, +) } + + let correctMessages: [Recorded] = [ + next(260, 42 + 0 + 1 + 2 + 3 + 4), + completed(260) + ] + + let correctSubscriptions = [ + Subscription(200, 260) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func test_AggregateWithSeed_AccumulatorThrows() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 0), + next(220, 1), + next(230, 2), + next(240, 3), + next(250, 4), + completed(260) + ]) + + let res = scheduler.start { xs >- aggregateOrDie(42, { $1 < 3 ? success($0 + $1) : .Error(testError)}) } + + let correctMessages: [Recorded] = [ + error(240, testError) + ] + + let correctSubscriptions = [ + Subscription(200, 240) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func test_AggregateWithSeedAndResult_Empty() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + completed(250) + ]) + + let res = scheduler.start { xs >- aggregate(42, +, { $0 * 5 }) } + + let correctMessages = [ + next(250, 42 * 5), + completed(250) + ] + + let correctSubscriptions = [ + Subscription(200, 250) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func test_AggregateWithSeedAndResult_Return() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 24), + completed(250) + ]) + + let res = scheduler.start { xs >- aggregate(42, { $0 + $1 }, { $0 * 5 }) } + + let correctMessages = [ + next(250, (42 + 24) * 5), + completed(250) + ] + + let correctSubscriptions = [ + Subscription(200, 250) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func test_AggregateWithSeedAndResult_Throw() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + error(210, testError), + ]) + + let res = scheduler.start { xs >- aggregate(42, { $0 + $1 }, { $0 * 5 }) } + + let correctMessages: [Recorded] = [ + error(210, testError) + ] + + let correctSubscriptions = [ + Subscription(200, 210) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func test_AggregateWithSeedAndResult_Never() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + ]) + + let res = scheduler.start { xs >- aggregate(42, { $0 + $1 }, { $0 * 5 }) } + + let correctMessages: [Recorded] = [ + ] + + let correctSubscriptions = [ + Subscription(200, 1000) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func test_AggregateWithSeedAndResult_Range() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 0), + next(220, 1), + next(230, 2), + next(240, 3), + next(250, 4), + completed(260) + ]) + + let res = scheduler.start { xs >- aggregate(42, { $0 + $1 }, { $0 * 5 }) } + + let correctMessages: [Recorded] = [ + next(260, (42 + 0 + 1 + 2 + 3 + 4) * 5), + completed(260) + ] + + let correctSubscriptions = [ + Subscription(200, 260) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func test_AggregateWithSeedAndResult_AccumulatorThrows() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 0), + next(220, 1), + next(230, 2), + next(240, 3), + next(250, 4), + completed(260) + ]) + + let res = scheduler.start { xs >- aggregateOrDie(42, { $1 < 3 ? success($0 + $1) : .Error(testError) }, { success($0 * 5) }) } + + let correctMessages: [Recorded] = [ + error(240, testError) + ] + + let correctSubscriptions = [ + Subscription(200, 240) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func test_AggregateWithSeedAndResult_SelectorThrows() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 0), + next(220, 1), + next(230, 2), + next(240, 3), + next(250, 4), + completed(260) + ]) + + let res = scheduler.start { xs >- aggregateOrDie(42, { success($0 + $1) }, { (_: Int) -> Result in .Error(testError) }) } + + let correctMessages: [Recorded] = [ + error(260, testError) + ] + + let correctSubscriptions = [ + Subscription(200, 260) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } +} \ No newline at end of file diff --git a/Rx/RxTests/Tests/Observable+MultipleTest.swift b/Rx/RxTests/Tests/Observable+MultipleTest.swift new file mode 100644 index 00000000..f38b12df --- /dev/null +++ b/Rx/RxTests/Tests/Observable+MultipleTest.swift @@ -0,0 +1,2391 @@ +// +// Observable+MultipleTest.swift +// Rx +// +// Created by Krunoslav Zaher on 2/15/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import XCTest +import Rx + +class ObservableMultipleTest : RxTest { + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } +} + +// switch +extension ObservableMultipleTest { + + func testSwitch_Data() { + let scheduler = TestScheduler(initialClock: 0) + + let ys1 = scheduler.createColdObservable([ + next(10, 101), + next(20, 102), + next(110, 103), + next(120, 104), + next(210, 105), + next(220, 106), + completed(230) + ]) + + let ys2 = scheduler.createColdObservable([ + next(10, 201), + next(20, 202), + next(30, 203), + next(40, 204), + completed(50) + ]) + + let ys3 = scheduler.createColdObservable([ + next(10, 301), + next(20, 302), + next(30, 303), + next(40, 304), + completed(150) + ]) + + let xSequence: [Recorded>] = [ + next(300, ys1), + next(400, ys2), + next(500, ys3), + completed(600) + ] + + var xs = scheduler.createHotObservable(xSequence) + + let res = scheduler.start { + switchLatest(xs) + } + + let correct = [ + next(310, 101), + next(320, 102), + next(410, 201), + next(420, 202), + next(430, 203), + next(440, 204), + next(510, 301), + next(520, 302), + next(530, 303), + next(540, 304), + completed(650) + ] + + XCTAssertEqual(res.messages, correct) + + let subscriptions = [ + Subscription(200, 600) + ] + + XCTAssertEqual(xs.subscriptions, subscriptions) + + let ys1Subscriptions = [ + Subscription(300, 400) + ] + + XCTAssertEqual(ys1.subscriptions, ys1Subscriptions) + + let y2Subscriptions = [ + Subscription(400, 450) + ] + + XCTAssertEqual(ys2.subscriptions, y2Subscriptions) + + let y3Subscriptions = [ + Subscription(500, 650) + ] + + XCTAssertEqual(ys3.subscriptions, y3Subscriptions) + } + + func testSwitch_InnerThrows() { + let scheduler = TestScheduler(initialClock: 0) + + let ys1 = scheduler.createColdObservable([ + next(10, 101), + next(20, 102), + next(110, 103), + next(120, 104), + next(210, 105), + next(220, 106), + completed(230) + ]) + + let ys2 = scheduler.createColdObservable([ + next(10, 201), + next(20, 202), + next(30, 203), + next(40, 204), + error(50, testError) + ]) + + let ys3 = scheduler.createColdObservable([ + next(10, 301), + next(20, 302), + next(30, 303), + next(40, 304), + completed(150) + ]) + + let xSequence: [Recorded>] = [ + next(300, ys1), + next(400, ys2), + next(500, ys3), + completed(600) + ] + + var xs = scheduler.createHotObservable(xSequence) + + let res = scheduler.start { + switchLatest(xs) + } + + let correct = [ + next(310, 101), + next(320, 102), + next(410, 201), + next(420, 202), + next(430, 203), + next(440, 204), + error(450, testError), + ] + + XCTAssertEqual(res.messages, correct) + + let subscriptions = [ + Subscription(200, 450) + ] + + XCTAssertEqual(xs.subscriptions, subscriptions) + + let ys1Subscriptions = [ + Subscription(300, 400) + ] + + XCTAssertEqual(ys1.subscriptions, ys1Subscriptions) + + let y2Subscriptions = [ + Subscription(400, 450) + ] + + XCTAssertEqual(ys2.subscriptions, y2Subscriptions) + + let y3Subscriptions: [Subscription] = [ + ] + + XCTAssertEqual(ys3.subscriptions, y3Subscriptions) + } + + func testSwitch_OuterThrows() { + let scheduler = TestScheduler(initialClock: 0) + + let ys1 = scheduler.createColdObservable([ + next(10, 101), + next(20, 102), + next(110, 103), + next(120, 104), + next(210, 105), + next(220, 106), + completed(230) + ]) + + let ys2 = scheduler.createColdObservable([ + next(10, 201), + next(20, 202), + next(30, 203), + next(40, 204), + completed(50) + ]) + + let xSequence: [Recorded>] = [ + next(300, ys1), + next(400, ys2), + error(500, testError) + ] + + var xs = scheduler.createHotObservable(xSequence) + + let res = scheduler.start { + switchLatest(xs) + } + + let correct = [ + next(310, 101), + next(320, 102), + next(410, 201), + next(420, 202), + next(430, 203), + next(440, 204), + error(500, testError), + ] + + XCTAssertEqual(res.messages, correct) + + let subscriptions = [ + Subscription(200, 500) + ] + + XCTAssertEqual(xs.subscriptions, subscriptions) + + let ys1Subscriptions = [ + Subscription(300, 400) + ] + + XCTAssertEqual(ys1.subscriptions, ys1Subscriptions) + + let y2Subscriptions = [ + Subscription(400, 450) + ] + + XCTAssertEqual(ys2.subscriptions, y2Subscriptions) + } +} + + +// combine latest +extension ObservableMultipleTest { + func testCombineLatest_Never2() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (_, _) -> Int in + return (42) + }) + } + + XCTAssertEqual(res.messages, []) + + let subscriptions = [Subscription(200, 1000)] + + for e in [e0, e1] { + XCTAssertEqual(e.subscriptions, subscriptions) + } + } + + func testCombineLatest_NeverEmpty() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + completed(210) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, []) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 1000)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 210)]) + } + + func testCombineLatest_EmptyNever() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + completed(210) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, []) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 210)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 1000)]) + } + + func testCombineLatest_Empty2() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + completed(210) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + completed(220) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (_, _) -> Int in + return (42) + }) + } + + XCTAssertEqual(res.messages, [completed(220)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 210)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 220)]) + } + + func testCombineLatest_EmptyReturn() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + completed(210) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + next(215, 2), + completed(220) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [completed(215)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 210)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 215)]) + } + + func testCombineLatest_ReturnEmpty() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + next(215, 2), + completed(220) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + completed(210) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [completed(215)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 215)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 210)]) + } + + func testCombineLatest_NeverReturn() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + next(215, 2), + completed(220) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, []) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 220)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 1000)]) + } + + func testCombineLatest_ReturnNever() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + next(215, 2), + completed(220) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, []) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 1000)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 220)]) + } + + func testCombineLatest_ReturnReturn1() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + next(215, 2), + completed(230) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + next(220, 3), + completed(240) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [next(220, (2 + 3)), completed(240)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 230)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 240)]) + } + + func testCombineLatest_ReturnReturn2() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + next(220, 3), + completed(240) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + next(215, 2), + completed(230) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [next(220, (2 + 3)), completed(240)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 240)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 230)]) + } + + func testCombineLatest_EmptyError() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + completed(230) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + error(220, testError) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [error(220, testError)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 220)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 220)]) + } + + func testCombineLatest_ErrorEmpty() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + error(220, testError) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + completed(230) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [error(220, testError)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 220)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 220)]) + } + + func testCombineLatest_ReturnThrow() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + next(210, 2), + completed(230) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + error(220, testError) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [error(220, testError)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 220)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 220)]) + } + + func testCombineLatest_ThrowReturn() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + error(220, testError) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + next(210, 2), + completed(230) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [error(220, testError)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 220)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 220)]) + } + + func testCombineLatest_ThrowThrow1() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + error(220, testError1), + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + error(230, testError2), + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [error(220, testError1)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 220)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 220)]) + } + + func testCombineLatest_ThrowThrow2() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + error(230, testError1), + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + error(220, testError2), + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [error(220, testError2)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 220)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 220)]) + } + + func testCombineLatest_ErrorThrow() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + next(210, 2), + error(220, testError1), + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + error(230, testError2), + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [error(220, testError1)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 220)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 220)]) + } + + func testCombineLatest_ThrowError() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + error(230, testError2), + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + next(210, 2), + error(220, testError1), + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [error(220, testError1)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 220)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 220)]) + } + + func testCombineLatest_SomeThrow() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + next(215, 2), + completed(230) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + error(220, testError), + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [error(220, testError)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 220)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 220)]) + } + + func testCombineLatest_ThrowSome() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + error(220, testError), + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + next(215, 2), + completed(230) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [error(220, testError)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 220)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 220)]) + } + + func testCombineLatest_ThrowAfterCompleteLeft() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + next(215, 2), + completed(220) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + error(230, testError), + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [error(230, testError)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 220)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 230)]) + } + + func testCombineLatest_ThrowAfterCompleteRight() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + error(230, testError), + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + next(215, 2), + completed(220) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [error(230, testError)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 230)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 220)]) + } + + func testCombineLatest_TestInterleavedWithTail() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + next(215, 2), + next(225, 4), + completed(230) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + next(220, 3), + next(230, 5), + next(235, 6), + next(240, 7), + completed(250) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + let messages = [ + next(220, 2 + 3), + next(225, 3 + 4), + next(230, 4 + 5), + next(235, 4 + 6), + next(240, 4 + 7), + completed(250) + ] + + XCTAssertEqual(res.messages, messages) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 230)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 250)]) + } + + func testCombineLatest_Consecutive() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + next(215, 2), + next(225, 4), + completed(230) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + next(235, 6), + next(240, 7), + completed(250) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + let messages = [ + next(235, 4 + 6), + next(240, 4 + 7), + completed(250) + ] + + XCTAssertEqual(res.messages, messages) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 230)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 250)]) + } + + func testCombineLatest_ConsecutiveEndWithErrorLeft() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + next(215, 2), + next(225, 4), + error(230, testError) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + next(235, 6), + next(240, 7), + completed(250) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [error(230, testError)]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 230)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 230)]) + } + + func testCombineLatest_ConsecutiveEndWithErrorRight() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + next(215, 2), + next(225, 4), + completed(250) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + next(235, 6), + next(240, 7), + error(245, testError) + ]) + + let res = scheduler.start { + e0 >- combineLatest(e1, { (x1, x2) -> Int in + return (x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [ + next(235, 4 + 6), + next(240, 4 + 7), + error(245, testError) + ]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 245)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 245)]) + } + + func testCombineLatest_SelectorThrows() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + next(215, 2), + completed(230) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + next(220, 3), + completed(240) + ]) + + let res = scheduler.start { + e0 >- combineLatestOrDie(e1, { (x1, x2) -> Result in + return .Error(testError) + }) + } + + XCTAssertEqual(res.messages, [ + error(220, testError) + ]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 220)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 220)]) + } + + func testCombineLatest_WillNeverBeAbleToCombine2() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + completed(250) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + next(500, 3), + completed(800) + ]) + + let res = scheduler.start { + e0 >- combineLatestOrDie(e1, { (x1, x2) -> Result in + return .Error(testError) + }) + } + + XCTAssertEqual(res.messages, [ + completed(500) + ]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 250)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 500)]) + } + + func testCombineLatest_Typical2() { + let scheduler = TestScheduler(initialClock: 0) + + let e0 = scheduler.createHotObservable([ + next(150, 1), + next(210, 1), + next(410, 3), + completed(800) + ]) + + let e1 = scheduler.createHotObservable([ + next(150, 1), + next(220, 2), + next(420, 4), + completed(800) + ]) + + let res = scheduler.start { + e0 >- combineLatestOrDie(e1, { (x1, x2) -> Result in + return success(x1 + x2) + }) + } + + XCTAssertEqual(res.messages, [ + next(220, 3), + next(410, 5), + next(420, 7), + completed(800) + ]) + + XCTAssertEqual(e0.subscriptions, [Subscription(200, 800)]) + XCTAssertEqual(e1.subscriptions, [Subscription(200, 800)]) + } +} + +// concat + +extension ObservableMultipleTest { + func testConcat_DefaultScheduler() { + var sum = 0 + concat([returnElement(1), returnElement(2), returnElement(3)]) >- subscribeNext { (e) -> Void in + sum += e + } + + XCTAssertEqual(sum, 6) + } + + func testConcat_IEofIO() { + let scheduler = TestScheduler(initialClock: 0) + + let xs1 = scheduler.createColdObservable([ + next(10, 1), + next(20, 2), + next(30, 3), + completed(40), + ]) + + let xs2 = scheduler.createColdObservable([ + next(10, 4), + next(20, 5), + completed(30), + ]) + + let xs3 = scheduler.createColdObservable([ + next(10, 6), + next(20, 7), + next(30, 8), + next(40, 9), + completed(50) + ]) + + let res = scheduler.start { + concat([xs1, xs2, xs3]) + } + + let messages = [ + next(210, 1), + next(220, 2), + next(230, 3), + next(250, 4), + next(260, 5), + next(280, 6), + next(290, 7), + next(300, 8), + next(310, 9), + completed(320) + ] + + XCTAssertEqual(xs1.subscriptions, [ + Subscription(200, 240), + ]) + + XCTAssertEqual(xs2.subscriptions, [ + Subscription(240, 270), + ]) + + XCTAssertEqual(xs3.subscriptions, [ + Subscription(270, 320), + ]) + } + + func testConcat_EmptyEmpty() { + let scheduler = TestScheduler(initialClock: 0) + + let xs1 = scheduler.createHotObservable([ + next(150, 1), + completed(230), + ]) + + let xs2 = scheduler.createHotObservable([ + next(150, 1), + completed(250), + ]) + + let res = scheduler.start { + concat([xs1, xs2]) + } + + let messages: [Recorded] = [ + completed(250) + ] + + XCTAssertEqual(xs1.subscriptions, [ + Subscription(200, 230), + ]) + + XCTAssertEqual(xs2.subscriptions, [ + Subscription(230, 250), + ]) + } + + func testConcat_EmptyNever() { + let scheduler = TestScheduler(initialClock: 0) + + let xs1 = scheduler.createHotObservable([ + next(150, 1), + completed(230), + ]) + + let xs2 = scheduler.createHotObservable([ + next(150, 1), + ]) + + let res = scheduler.start { + concat([xs1, xs2]) + } + + let messages: [Recorded] = [ + completed(250) + ] + + XCTAssertEqual(xs1.subscriptions, [ + Subscription(200, 230), + ]) + + XCTAssertEqual(xs2.subscriptions, [ + Subscription(230, 1000), + ]) + } + + func testConcat_NeverNever() { + let scheduler = TestScheduler(initialClock: 0) + + let xs1 = scheduler.createHotObservable([ + next(150, 1), + ]) + + let xs2 = scheduler.createHotObservable([ + next(150, 1), + ]) + + let res = scheduler.start { + concat([xs1, xs2]) + } + + let messages: [Recorded] = [ + ] + + XCTAssertEqual(xs1.subscriptions, [ + Subscription(200, 1000), + ]) + + XCTAssertEqual(xs2.subscriptions, [ + ]) + } + + func testConcat_EmptyThrow() { + let scheduler = TestScheduler(initialClock: 0) + + let xs1 = scheduler.createHotObservable([ + next(150, 1), + completed(230), + ]) + + let xs2 = scheduler.createHotObservable([ + next(150, 1), + error(250, testError) + ]) + + let res = scheduler.start { + concat([xs1, xs2]) + } + + let messages: [Recorded] = [ + error(250, testError) + ] + + XCTAssertEqual(xs1.subscriptions, [ + Subscription(200, 230), + ]) + + XCTAssertEqual(xs2.subscriptions, [ + Subscription(230, 250), + ]) + } + + func testConcat_ThrowEmpty() { + let scheduler = TestScheduler(initialClock: 0) + + let xs1 = scheduler.createHotObservable([ + next(150, 1), + error(230, testError), + ]) + + let xs2 = scheduler.createHotObservable([ + next(150, 1), + completed(250) + ]) + + let res = scheduler.start { + concat([xs1, xs2]) + } + + let messages: [Recorded] = [ + error(230, testError) + ] + + XCTAssertEqual(xs1.subscriptions, [ + Subscription(200, 230), + ]) + + XCTAssertEqual(xs2.subscriptions, [ + ]) + } + + func testConcat_ThrowThrow() { + let scheduler = TestScheduler(initialClock: 0) + + let xs1 = scheduler.createHotObservable([ + next(150, 1), + error(230, testError1), + ]) + + let xs2 = scheduler.createHotObservable([ + next(150, 1), + error(250, testError2) + ]) + + let res = scheduler.start { + concat([xs1, xs2]) + } + + let messages: [Recorded] = [ + error(230, testError1) + ] + + XCTAssertEqual(xs1.subscriptions, [ + Subscription(200, 230), + ]) + + XCTAssertEqual(xs2.subscriptions, [ + ]) + } + + func testConcat_ReturnEmpty() { + let scheduler = TestScheduler(initialClock: 0) + + let xs1 = scheduler.createHotObservable([ + next(150, 1), + next(210, 2), + completed(230), + ]) + + let xs2 = scheduler.createHotObservable([ + next(150, 1), + completed(250) + ]) + + let res = scheduler.start { + concat([xs1, xs2]) + } + + let messages: [Recorded] = [ + next(210, 1), + completed(250) + ] + + XCTAssertEqual(xs1.subscriptions, [ + Subscription(200, 230), + ]) + + XCTAssertEqual(xs2.subscriptions, [ + Subscription(230, 250), + ]) + } + + func testConcat_EmptyReturn() { + let scheduler = TestScheduler(initialClock: 0) + + let xs1 = scheduler.createHotObservable([ + next(150, 1), + completed(230), + ]) + + let xs2 = scheduler.createHotObservable([ + next(150, 1), + next(240, 2), + completed(250) + ]) + + let res = scheduler.start { + concat([xs1, xs2]) + } + + let messages: [Recorded] = [ + next(240, 1), + completed(250) + ] + + XCTAssertEqual(xs1.subscriptions, [ + Subscription(200, 230), + ]) + + XCTAssertEqual(xs2.subscriptions, [ + Subscription(230, 250), + ]) + } + + func testConcat_ReturnNever() { + let scheduler = TestScheduler(initialClock: 0) + + let xs1 = scheduler.createHotObservable([ + next(150, 1), + next(210, 2), + completed(230), + ]) + + let xs2 = scheduler.createHotObservable([ + next(150, 1), + ]) + + let res = scheduler.start { + concat([xs1, xs2]) + } + + let messages: [Recorded] = [ + next(210, 1), + ] + + XCTAssertEqual(xs1.subscriptions, [ + Subscription(200, 230), + ]) + + XCTAssertEqual(xs2.subscriptions, [ + Subscription(230, 1000), + ]) + } + + func testConcat_NeverReturn() { + let scheduler = TestScheduler(initialClock: 0) + + let xs1 = scheduler.createHotObservable([ + next(150, 1), + ]) + + let xs2 = scheduler.createHotObservable([ + next(150, 1), + next(210, 2), + completed(230), + ]) + + let res = scheduler.start { + concat([xs1, xs2]) + } + + let messages: [Recorded] = [ + ] + + XCTAssertEqual(xs1.subscriptions, [ + Subscription(200, 1000), + ]) + + XCTAssertEqual(xs2.subscriptions, [ + ]) + } + + func testConcat_ReturnReturn() { + let scheduler = TestScheduler(initialClock: 0) + + let xs1 = scheduler.createHotObservable([ + next(150, 1), + next(220, 2), + completed(230) + ]) + + let xs2 = scheduler.createHotObservable([ + next(150, 1), + next(240, 3), + completed(250), + ]) + + let res = scheduler.start { + concat([xs1, xs2]) + } + + let messages: [Recorded] = [ + next(220, 2), + next(240, 3), + completed(250) + ] + + XCTAssertEqual(xs1.subscriptions, [ + Subscription(200, 230), + ]) + + XCTAssertEqual(xs2.subscriptions, [ + Subscription(230, 250), + ]) + } + + func testConcat_ThrowReturn() { + let scheduler = TestScheduler(initialClock: 0) + + let xs1 = scheduler.createHotObservable([ + next(150, 1), + error(230, testError1) + ]) + + let xs2 = scheduler.createHotObservable([ + next(150, 1), + next(240, 2), + completed(250), + ]) + + let res = scheduler.start { + concat([xs1, xs2]) + } + + let messages: [Recorded] = [ + error(230, testError1) + ] + + XCTAssertEqual(xs1.subscriptions, [ + Subscription(200, 230), + ]) + + XCTAssertEqual(xs2.subscriptions, [ + ]) + } + + func testConcat_ReturnThrow() { + let scheduler = TestScheduler(initialClock: 0) + + let xs1 = scheduler.createHotObservable([ + next(150, 1), + next(220, 2), + completed(230) + ]) + + let xs2 = scheduler.createHotObservable([ + next(150, 1), + error(250, testError2), + ]) + + let res = scheduler.start { + concat([xs1, xs2]) + } + + let messages: [Recorded] = [ + next(220, 2), + error(250, testError1) + ] + + XCTAssertEqual(xs1.subscriptions, [ + Subscription(200, 230), + ]) + + XCTAssertEqual(xs2.subscriptions, [ + Subscription(230, 250), + ]) + } + + func testConcat_SomeDataSomeData() { + let scheduler = TestScheduler(initialClock: 0) + + let xs1 = scheduler.createHotObservable([ + next(150, 1), + next(210, 2), + next(220, 3), + completed(225) + ]) + + let xs2 = scheduler.createHotObservable([ + next(150, 1), + next(230, 4), + next(240, 5), + completed(250) + ]) + + let res = scheduler.start { + concat([xs1, xs2]) + } + + let messages: [Recorded] = [ + next(210, 1), + next(220, 3), + next(230, 4), + next(240, 5), + completed(250) + ] + + XCTAssertEqual(xs1.subscriptions, [ + Subscription(200, 225), + ]) + + XCTAssertEqual(xs2.subscriptions, [ + Subscription(225, 250), + ]) + } + + func testConcat_EnumerableTiming() { + let scheduler = TestScheduler(initialClock: 0) + + let xs1 = scheduler.createHotObservable([ + next(150, 1), + next(210, 2), + next(220, 3), + completed(230) + ]) + + let xs2 = scheduler.createColdObservable([ + next(50, 4), + next(60, 5), + next(70, 6), + completed(80) + ]) + + let xs3 = scheduler.createHotObservable([ + next(150, 1), + next(200, 2), + next(210, 3), + next(220, 4), + next(230, 5), + next(270, 6), + next(320, 7), + next(330, 8), + completed(340) + ]) + + let res = scheduler.start { + concat([xs1, xs2, xs3, xs2]) + } + + let messages: [Recorded] = [ + next(210, 2), + next(220, 3), + next(280, 4), + next(290, 5), + next(300, 6), + next(320, 7), + next(330, 8), + next(390, 4), + next(400, 5), + next(410, 6), + completed(420) + ] + + XCTAssertEqual(xs1.subscriptions, [ + Subscription(200, 230), + ]) + + XCTAssertEqual(xs2.subscriptions, [ + Subscription(230, 310), + Subscription(340, 420), + ]) + + XCTAssertEqual(xs3.subscriptions, [ + Subscription(310, 340), + ]) + + } +} + +// merge + +extension ObservableMultipleTest { + func testMerge_ObservableOfObservable_Data() { + let scheduler = TestScheduler(initialClock: 0) + + let ys1 = scheduler.createColdObservable([ + next(10, 101), + next(20, 102), + next(110, 103), + next(120, 104), + next(210, 105), + next(220, 106), + completed(230) + ]) + + let ys2 = scheduler.createColdObservable([ + next(10, 201), + next(20, 202), + next(30, 203), + next(40, 204), + completed(50) + ]) + + let ys3 = scheduler.createColdObservable([ + next(10, 301), + next(20, 302), + next(30, 303), + next(40, 304), + next(120, 305), + completed(150) + ]) + + let xs: Observable> = scheduler.createHotObservable([ + next(300, ys1), + next(400, ys2), + next(500, ys3), + completed(600) + ]) + + let res = scheduler.start { + xs >- merge + } + + let messages = [ + next(310, 101), + next(320, 102), + next(410, 103), + next(410, 201), + next(420, 104), + next(420, 202), + next(430, 203), + next(440, 204), + next(510, 105), + next(510, 301), + next(520, 106), + next(520, 302), + next(530, 303), + next(540, 304), + next(620, 305), + completed(650) + ] + + XCTAssertEqual(ys1.subscriptions, [ + Subscription(300, 530), + ]) + + XCTAssertEqual(ys2.subscriptions, [ + Subscription(400, 450), + ]) + + XCTAssertEqual(ys3.subscriptions, [ + Subscription(500, 650), + ]) + } + + func testMerge_ObservableOfObservable_Data_NotOverlapped() { + let scheduler = TestScheduler(initialClock: 0) + + let ys1 = scheduler.createColdObservable([ + next(10, 101), + next(20, 102), + completed(230) + ]) + + let ys2 = scheduler.createColdObservable([ + next(10, 201), + next(20, 202), + next(30, 203), + next(40, 204), + completed(50) + ]) + + let ys3 = scheduler.createColdObservable([ + next(10, 301), + next(20, 302), + next(30, 303), + next(40, 304), + completed(50) + ]) + + let xs: HotObservable> = scheduler.createHotObservable([ + next(300, ys1), + next(400, ys2), + next(500, ys3), + completed(600) + ]) + + let res = scheduler.start { + xs >- merge + } + + let messages = [ + next(310, 101), + next(320, 102), + next(410, 201), + next(420, 202), + next(430, 203), + next(440, 204), + next(510, 301), + next(520, 302), + next(530, 303), + next(540, 304), + completed(600) + ] + + XCTAssertEqual(xs.subscriptions, [ + Subscription(200, 600), + ]) + + XCTAssertEqual(ys1.subscriptions, [ + Subscription(300, 530), + ]) + + XCTAssertEqual(ys2.subscriptions, [ + Subscription(400, 450), + ]) + + XCTAssertEqual(ys3.subscriptions, [ + Subscription(500, 550), + ]) + } + + func testMerge_ObservableOfObservable_InnerThrows() { + let scheduler = TestScheduler(initialClock: 0) + + let ys1 = scheduler.createColdObservable([ + next(10, 101), + next(20, 102), + next(110, 103), + next(120, 104), + next(210, 105), + next(220, 106), + completed(230) + ]) + + let ys2 = scheduler.createColdObservable([ + next(10, 201), + next(20, 202), + next(30, 203), + next(40, 204), + error(50, testError1) + ]) + + let ys3 = scheduler.createColdObservable([ + next(10, 301), + next(20, 302), + next(30, 303), + next(40, 304), + completed(150) + ]) + + let xs: HotObservable> = scheduler.createHotObservable([ + next(300, ys1), + next(400, ys2), + next(500, ys3), + completed(600) + ]) + + let res = scheduler.start { + xs >- merge + } + + let messages = [ + next(310, 101), + next(320, 102), + next(410, 103), + next(410, 201), + next(420, 104), + next(420, 202), + next(430, 203), + next(440, 204), + error(600, testError1) + ] + + XCTAssertEqual(xs.subscriptions, [ + Subscription(200, 450), + ]) + + XCTAssertEqual(ys1.subscriptions, [ + Subscription(300, 450), + ]) + + XCTAssertEqual(ys2.subscriptions, [ + Subscription(400, 450), + ]) + + XCTAssertEqual(ys3.subscriptions, [ + ]) + } + + func testMerge_ObservableOfObservable_OuterThrows() { + let scheduler = TestScheduler(initialClock: 0) + + let ys1 = scheduler.createColdObservable([ + next(10, 101), + next(20, 102), + next(110, 103), + next(120, 104), + next(210, 105), + next(220, 106), + completed(230) + ]) + + let ys2 = scheduler.createColdObservable([ + next(10, 201), + next(20, 202), + next(30, 203), + next(40, 204), + completed(50) + ]) + + let xs: HotObservable> = scheduler.createHotObservable([ + next(300, ys1), + next(400, ys2), + error(500, testError1), + ]) + + let res = scheduler.start { + xs >- merge + } + + let messages = [ + next(310, 101), + next(320, 102), + next(410, 103), + next(410, 201), + next(420, 104), + next(420, 202), + next(430, 203), + next(440, 204), + error(600, testError1) + ] + + XCTAssertEqual(xs.subscriptions, [ + Subscription(200, 500), + ]) + + XCTAssertEqual(ys1.subscriptions, [ + Subscription(300, 500), + ]) + + XCTAssertEqual(ys2.subscriptions, [ + Subscription(400, 450), + ]) + } + + func testMerge_MergeConcat_Basic() { + let scheduler = TestScheduler(initialClock: 0) + + let ys1 = scheduler.createColdObservable([ + next(50, 1), + next(100, 2), + next(120, 3), + completed(140) + ]) + + let ys2 = scheduler.createColdObservable([ + next(20, 4), + next(70, 5), + completed(200) + ]) + + let ys3 = scheduler.createColdObservable([ + next(10, 6), + next(90, 7), + next(110, 8), + completed(130) + ]) + + let ys4 = scheduler.createColdObservable([ + next(210, 9), + next(240, 10), + completed(300) + ]) + + let xs: HotObservable> = scheduler.createHotObservable([ + next(210, ys1), + next(260, ys2), + next(270, ys3), + next(320, ys4), + completed(400) + ]) + + let res = scheduler.start { + xs >- merge(2) + } + + let messages = [ + next(260, 1), + next(280, 4), + next(310, 2), + next(330, 3), + next(330, 5), + next(360, 6), + next(440, 7), + next(460, 8), + next(670, 9), + next(700, 10), + completed(760) + ] + + XCTAssertEqual(xs.subscriptions, [ + Subscription(200, 400), + ]) + + XCTAssertEqual(ys1.subscriptions, [ + Subscription(210, 350), + ]) + + XCTAssertEqual(ys2.subscriptions, [ + Subscription(260, 460), + ]) + + XCTAssertEqual(ys3.subscriptions, [ + Subscription(350, 480), + ]) + + XCTAssertEqual(ys4.subscriptions, [ + Subscription(460, 760), + ]) + } + + func testMerge_MergeConcat_BasicLong() { + let scheduler = TestScheduler(initialClock: 0) + + let ys1 = scheduler.createColdObservable([ + next(50, 1), + next(100, 2), + next(120, 3), + completed(140) + ]) + + let ys2 = scheduler.createColdObservable([ + next(20, 4), + next(70, 5), + completed(300) + ]) + + let ys3 = scheduler.createColdObservable([ + next(10, 6), + next(90, 7), + next(110, 8), + completed(130) + ]) + + let ys4 = scheduler.createColdObservable([ + next(210, 9), + next(240, 10), + completed(300) + ]) + + let xs: HotObservable> = scheduler.createHotObservable([ + next(210, ys1), + next(260, ys2), + next(270, ys3), + next(320, ys4), + completed(400) + ]) + + let res = scheduler.start { + xs >- merge(2) + } + + let messages = [ + next(260, 1), + next(280, 4), + next(310, 2), + next(330, 3), + next(330, 5), + next(360, 6), + next(440, 7), + next(460, 8), + next(690, 9), + next(720, 10), + completed(780) + ] + + XCTAssertEqual(xs.subscriptions, [ + Subscription(200, 400), + ]) + + XCTAssertEqual(ys1.subscriptions, [ + Subscription(210, 350), + ]) + + XCTAssertEqual(ys2.subscriptions, [ + Subscription(260, 560), + ]) + + XCTAssertEqual(ys3.subscriptions, [ + Subscription(350, 480), + ]) + + XCTAssertEqual(ys4.subscriptions, [ + Subscription(480, 780), + ]) + } + + func testMerge_MergeConcat_BasicWide() { + let scheduler = TestScheduler(initialClock: 0) + + let ys1 = scheduler.createColdObservable([ + next(50, 1), + next(100, 2), + next(120, 3), + completed(140) + ]) + + let ys2 = scheduler.createColdObservable([ + next(20, 4), + next(70, 5), + completed(300) + ]) + + let ys3 = scheduler.createColdObservable([ + next(10, 6), + next(90, 7), + next(110, 8), + completed(130) + ]) + + let ys4 = scheduler.createColdObservable([ + next(210, 9), + next(240, 10), + completed(300) + ]) + + let xs: HotObservable> = scheduler.createHotObservable([ + next(210, ys1), + next(260, ys2), + next(270, ys3), + next(420, ys4), + completed(450) + ]) + + let res = scheduler.start { + xs >- merge(3) + } + + let messages = [ + next(260, 1), + next(280, 4), + next(280, 6), + next(310, 2), + next(330, 3), + next(330, 5), + next(360, 7), + next(380, 8), + next(630, 9), + next(660, 10), + completed(720) + ] + + XCTAssertEqual(xs.subscriptions, [ + Subscription(200, 450), + ]) + + XCTAssertEqual(ys1.subscriptions, [ + Subscription(210, 350), + ]) + + XCTAssertEqual(ys2.subscriptions, [ + Subscription(260, 560), + ]) + + XCTAssertEqual(ys3.subscriptions, [ + Subscription(270, 400), + ]) + + XCTAssertEqual(ys4.subscriptions, [ + Subscription(420, 720), + ]) + } + + func testMerge_MergeConcat_BasicLate() { + let scheduler = TestScheduler(initialClock: 0) + + let ys1 = scheduler.createColdObservable([ + next(50, 1), + next(100, 2), + next(120, 3), + completed(140) + ]) + + let ys2 = scheduler.createColdObservable([ + next(20, 4), + next(70, 5), + completed(300) + ]) + + let ys3 = scheduler.createColdObservable([ + next(10, 6), + next(90, 7), + next(110, 8), + completed(130) + ]) + + let ys4 = scheduler.createColdObservable([ + next(210, 9), + next(240, 10), + completed(300) + ]) + + let xs: HotObservable> = scheduler.createHotObservable([ + next(210, ys1), + next(260, ys2), + next(270, ys3), + next(420, ys4), + completed(750) + ]) + + let res = scheduler.start { + xs >- merge(3) + } + + let messages = [ + next(260, 1), + next(280, 4), + next(280, 6), + next(310, 2), + next(330, 3), + next(330, 5), + next(360, 7), + next(380, 8), + next(630, 9), + next(660, 10), + completed(750) + ] + + XCTAssertEqual(xs.subscriptions, [ + Subscription(200, 750), + ]) + + XCTAssertEqual(ys1.subscriptions, [ + Subscription(210, 350), + ]) + + XCTAssertEqual(ys2.subscriptions, [ + Subscription(260, 560), + ]) + + XCTAssertEqual(ys3.subscriptions, [ + Subscription(270, 400), + ]) + + XCTAssertEqual(ys4.subscriptions, [ + Subscription(420, 720), + ]) + } + + func testMerge_MergeConcat_Disposed() { + let scheduler = TestScheduler(initialClock: 0) + + let ys1 = scheduler.createColdObservable([ + next(50, 1), + next(100, 2), + next(120, 3), + completed(140) + ]) + + let ys2 = scheduler.createColdObservable([ + next(20, 4), + next(70, 5), + completed(200) + ]) + + let ys3 = scheduler.createColdObservable([ + next(10, 6), + next(90, 7), + next(110, 8), + completed(130) + ]) + + let ys4 = scheduler.createColdObservable([ + next(210, 9), + next(240, 10), + completed(300) + ]) + + let xs: HotObservable> = scheduler.createHotObservable([ + next(210, ys1), + next(260, ys2), + next(270, ys3), + next(320, ys4), + completed(400) + ]) + + let res = scheduler.start(450) { + xs >- merge(2) + } + + let messages = [ + next(260, 1), + next(280, 4), + next(310, 2), + next(330, 3), + next(330, 5), + next(360, 6), + next(440, 7) + ] + + XCTAssertEqual(xs.subscriptions, [ + Subscription(200, 400), + ]) + + XCTAssertEqual(ys1.subscriptions, [ + Subscription(210, 350), + ]) + + XCTAssertEqual(ys2.subscriptions, [ + Subscription(260, 450), + ]) + + XCTAssertEqual(ys3.subscriptions, [ + Subscription(350, 450), + ]) + + XCTAssertEqual(ys4.subscriptions, [ + ]) + } + + func testMerge_MergeConcat_OuterError() { + let scheduler = TestScheduler(initialClock: 0) + + let ys1 = scheduler.createColdObservable([ + next(50, 1), + next(100, 2), + next(120, 3), + completed(140) + ]) + + let ys2 = scheduler.createColdObservable([ + next(20, 4), + next(70, 5), + completed(200) + ]) + + let ys3 = scheduler.createColdObservable([ + next(10, 6), + next(90, 7), + next(110, 8), + completed(130) + ]) + + let ys4 = scheduler.createColdObservable([ + next(210, 9), + next(240, 10), + completed(300) + ]) + + let xs: HotObservable> = scheduler.createHotObservable([ + next(210, ys1), + next(260, ys2), + next(270, ys3), + next(320, ys4), + error(400, testError1) + ]) + + let res = scheduler.start { + xs >- merge(2) + } + + let messages = [ + next(260, 1), + next(280, 4), + next(310, 2), + next(330, 3), + next(330, 5), + next(360, 6), + error(400, testError1) + ] + + XCTAssertEqual(xs.subscriptions, [ + Subscription(200, 400), + ]) + + XCTAssertEqual(ys1.subscriptions, [ + Subscription(210, 350), + ]) + + XCTAssertEqual(ys2.subscriptions, [ + Subscription(260, 400), + ]) + + XCTAssertEqual(ys3.subscriptions, [ + Subscription(350, 400), + ]) + + XCTAssertEqual(ys4.subscriptions, [ + ]) + } + + func testMerge_MergeConcat_InnerError() { + let scheduler = TestScheduler(initialClock: 0) + + let ys1 = scheduler.createColdObservable([ + next(50, 1), + next(100, 2), + next(120, 3), + completed(140) + ]) + + let ys2 = scheduler.createColdObservable([ + next(20, 4), + next(70, 5), + completed(200) + ]) + + let ys3 = scheduler.createColdObservable([ + next(10, 6), + next(90, 7), + next(110, 8), + error(140, testError1) + ]) + + let ys4 = scheduler.createColdObservable([ + next(210, 9), + next(240, 10), + completed(300) + ]) + + let xs: HotObservable> = scheduler.createHotObservable([ + next(210, ys1), + next(260, ys2), + next(270, ys3), + next(320, ys4), + completed(400) + ]) + + let res = scheduler.start { + xs >- merge(2) + } + + let messages = [ + next(260, 1), + next(280, 4), + next(310, 2), + next(330, 3), + next(330, 5), + next(360, 6), + next(440, 7), + next(460, 8), + error(490, testError1) + ] + + XCTAssertEqual(xs.subscriptions, [ + Subscription(200, 400), + ]) + + XCTAssertEqual(ys1.subscriptions, [ + Subscription(210, 350), + ]) + + XCTAssertEqual(ys2.subscriptions, [ + Subscription(260, 460), + ]) + + XCTAssertEqual(ys3.subscriptions, [ + Subscription(350, 490), + ]) + + XCTAssertEqual(ys4.subscriptions, [ + Subscription(460, 490), + ]) + } +} \ No newline at end of file diff --git a/Rx/RxTests/Tests/Observable+SingleTest.swift b/Rx/RxTests/Tests/Observable+SingleTest.swift new file mode 100644 index 00000000..5adfe161 --- /dev/null +++ b/Rx/RxTests/Tests/Observable+SingleTest.swift @@ -0,0 +1,754 @@ +// +// Observable+SingleTest.swift +// Rx +// +// Created by Krunoslav Zaher on 2/8/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import XCTest +import Rx + +class ObservableSingleTest : RxTest { + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } +} + +// Creation +extension ObservableSingleTest { + func testAsObservable_asObservable() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(220, 2), + completed(250) + ]) + + let ys = asObservable(xs) + + XCTAssert(xs !== ys) + + let res = scheduler.start { ys } + + let correct = [ + next(220, 2), + completed(250) + ] + + XCTAssertEqual(res.messages, correct) + } + + + func testAsObservable_hides() { + let xs : Observable = empty() + + let res = asObservable(xs) + + XCTAssertTrue(res !== xs) + } + + func testAsObservable_never() { + let scheduler = TestScheduler(initialClock: 0) + + let xs : Observable = never() + + let res = scheduler.start { xs } + + let correct: [Recorded] = [] + + XCTAssertEqual(res.messages, correct) + } + + // ... +} + +// Distinct +extension ObservableSingleTest { + func testDistinctUntilChanged_allChanges() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 2), + next(220, 3), + next(230, 4), + next(240, 5), + completed(250) + ]) + + let res = scheduler.start { xs >- distinctUntilChanged { $0 } } + + let correctMessages = [ + next(210, 2), + next(220, 3), + next(230, 4), + next(240, 5), + completed(250) + ] + + let correctSubscriptions = [ + Subscription(200, 250) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testDistinctUntilChanged_someChanges() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 2), // * + next(215, 3), // * + next(220, 3), + next(225, 2), // * + next(230, 2), + next(230, 1), // * + next(240, 2), // * + completed(250) + ]) + + + let res = scheduler.start { xs >- distinctUntilChanged { $0 } } + + let correctMessages = [ + next(210, 2), + next(215, 3), + next(225, 2), + next(230, 1), + next(240, 2), + completed(250) + ] + + let correctSubscriptions = [ + Subscription(200, 250) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testDistinctUntilChanged_allEqual() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 2), + next(220, 3), + next(230, 4), + next(240, 5), + completed(250) + ]) + + let res = scheduler.start { xs >- distinctUntilChanged { l, r in true } } + + let correctMessages = [ + next(210, 2), + completed(250) + ] + + let correctSubscriptions = [ + Subscription(200, 250) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + // ... +} + +// Do +extension ObservableSingleTest { + func testDo_shouldSeeAllValues() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 2), + next(220, 3), + next(230, 4), + next(240, 5), + completed(250) + ]) + + var i = 0 + var sum = 2 + 3 + 4 + 5 + let res = scheduler.start { xs >- `do` { e in + switch e { + case .Next(let value): + i++ + sum -= e.value ?? 0 + + default: break + } + } + } + + XCTAssertEqual(i, 4) + XCTAssertEqual(sum, 0) + + let correctMessages = [ + next(210, 2), + next(220, 3), + next(230, 4), + next(240, 5), + completed(250) + ] + + let correctSubscriptions = [ + Subscription(200, 250) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testDo_plainAction() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 2), + next(220, 3), + next(230, 4), + next(240, 5), + completed(250) + ]) + + var i = 0 + let res = scheduler.start { xs >- `do` { e in + switch e { + case .Next(let value): + i++ + default: break + } + } + } + + XCTAssertEqual(i, 4) + + let correctMessages = [ + next(210, 2), + next(220, 3), + next(230, 4), + next(240, 5), + completed(250) + ] + + let correctSubscriptions = [ + Subscription(200, 250) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testDo_nextCompleted() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 2), + next(220, 3), + next(230, 4), + next(240, 5), + completed(250) + ]) + + var i = 0 + var sum = 2 + 3 + 4 + 5 + var completedEvaluation = false + let res = scheduler.start { xs >- `do` { e in + switch e { + case .Next(let value): + i++ + sum -= value.value + case .Completed: + completedEvaluation = true + default: break + } + } + } + + XCTAssertEqual(i, 4) + XCTAssertEqual(sum, 0) + XCTAssertEqual(completedEvaluation, true) + + let correctMessages = [ + next(210, 2), + next(220, 3), + next(230, 4), + next(240, 5), + completed(250) + ] + + let correctSubscriptions = [ + Subscription(200, 250) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testDo_completedNever() { + let scheduler = TestScheduler(initialClock: 0) + + let recordedEvents: [Recorded] = [ + ] + + let xs = scheduler.createHotObservable(recordedEvents) + + var i = 0 + var completedEvaluation = false + let res = scheduler.start { xs >- `do` { e in + switch e { + case .Next(let value): + i++ + case .Completed: + completedEvaluation = true + default: break + } + } + } + + XCTAssertEqual(i, 0) + XCTAssertEqual(completedEvaluation, false) + + let correctMessages: [Recorded] = [ + ] + + let correctSubscriptions = [ + Subscription(200, 1000) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testDo_nextError() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 2), + next(220, 3), + next(230, 4), + next(240, 5), + error(250, testError) + ]) + + var i = 0 + var sum = 2 + 3 + 4 + 5 + var sawError = false + let res = scheduler.start { xs >- `do` { e in + switch e { + case .Next(let value): + i++ + sum -= value.value + case .Error: + sawError = true + default: break + } + } + } + + XCTAssertEqual(i, 4) + XCTAssertEqual(sum, 0) + XCTAssertEqual(sawError, true) + + let correctMessages = [ + next(210, 2), + next(220, 3), + next(230, 4), + next(240, 5), + error(250, testError) + ] + + let correctSubscriptions = [ + Subscription(200, 250) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testDo_nextErrorNot() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 2), + next(220, 3), + next(230, 4), + next(240, 5), + completed(250) + ]) + + var i = 0 + var sum = 2 + 3 + 4 + 5 + var sawError = false + let res = scheduler.start { xs >- `do` { e in + switch e { + case .Next(let value): + i++ + sum -= value.value + case .Error: + sawError = true + default: break + } + } + } + + XCTAssertEqual(i, 4) + XCTAssertEqual(sum, 0) + XCTAssertEqual(sawError, false) + + let correctMessages = [ + next(210, 2), + next(220, 3), + next(230, 4), + next(240, 5), + completed(250) + ] + + let correctSubscriptions = [ + Subscription(200, 250) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + // ... +} + +// map +// these test are not port from Rx +extension ObservableSingleTest { + func testMap_Never() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + ]) + + let res = scheduler.start { xs >- map { $0 * 2 } } + + let correctMessages: [Recorded] = [ + ] + + let correctSubscriptions = [ + Subscription(200, 1000) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testMap_Empty() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + completed(300) + ]) + + let res = scheduler.start { xs >- map { $0 * 2 } } + + let correctMessages: [Recorded] = [ + completed(300) + ] + + let correctSubscriptions = [ + Subscription(200, 300) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testMap_Range() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 0), + next(220, 1), + next(230, 2), + next(240, 4), + completed(300) + ]) + + let res = scheduler.start { xs >- map { $0 * 2 } } + + let correctMessages: [Recorded] = [ + next(210, 0 * 2), + next(220, 1 * 2), + next(230, 2 * 2), + next(240, 4 * 2), + completed(300) + ] + + let correctSubscriptions = [ + Subscription(200, 300) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testMap_Error() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 0), + next(220, 1), + next(230, 2), + next(240, 4), + error(300, testError) + ]) + + let res = scheduler.start { xs >- map { $0 * 2 } } + + let correctMessages: [Recorded] = [ + next(210, 0 * 2), + next(220, 1 * 2), + next(230, 2 * 2), + next(240, 4 * 2), + error(300, testError) + ] + + let correctSubscriptions = [ + Subscription(200, 300) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testMap_Dispose() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 0), + next(220, 1), + next(230, 2), + next(240, 4), + error(300, testError) + ]) + + let res = scheduler.start(290) { xs >- map { $0 * 2 } } + + let correctMessages: [Recorded] = [ + next(210, 0 * 2), + next(220, 1 * 2), + next(230, 2 * 2), + next(240, 4 * 2), + ] + + let correctSubscriptions = [ + Subscription(200, 290) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testMap_SelectorThrows() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 0), + next(220, 1), + next(230, 2), + next(240, 4), + error(300, testError) + ]) + + let res = scheduler.start { xs >- mapOrDie { $0 < 2 ? success($0 * 2) : .Error(testError) } } + + let correctMessages: [Recorded] = [ + next(210, 0 * 2), + next(220, 1 * 2), + error(230, testError) + ] + + let correctSubscriptions = [ + Subscription(200, 230) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testMap1_Never() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + ]) + + let res = scheduler.start { xs >- mapWithIndex { ($0 + $1) * 2 } } + + let correctMessages: [Recorded] = [ + ] + + let correctSubscriptions = [ + Subscription(200, 1000) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testMap1_Empty() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + completed(300) + ]) + + let res = scheduler.start { xs >- mapWithIndex { ($0 + $1) * 2 } } + + let correctMessages: [Recorded] = [ + completed(300) + ] + + let correctSubscriptions = [ + Subscription(200, 300) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testMap1_Range() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 5), + next(220, 6), + next(230, 7), + next(240, 8), + completed(300) + ]) + + let res = scheduler.start { xs >- mapWithIndex { ($0 + $1) * 2 } } + + let correctMessages: [Recorded] = [ + next(210, (5 + 0) * 2), + next(220, (6 + 1) * 2), + next(230, (7 + 2) * 2), + next(240, (8 + 3) * 2), + completed(300) + ] + + let correctSubscriptions = [ + Subscription(200, 300) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testMap1_Error() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 5), + next(220, 6), + next(230, 7), + next(240, 8), + error(300, testError) + ]) + + let res = scheduler.start { xs >- mapWithIndex { ($0 + $1) * 2 } } + + let correctMessages: [Recorded] = [ + next(210, (5 + 0) * 2), + next(220, (6 + 1) * 2), + next(230, (7 + 2) * 2), + next(240, (8 + 3) * 2), + error(300, testError) + ] + + let correctSubscriptions = [ + Subscription(200, 300) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testMap1_Dispose() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 5), + next(220, 6), + next(230, 7), + next(240, 8), + error(300, testError) + ]) + + let res = scheduler.start(290) { xs >- mapWithIndex { ($0 + $1) * 2 } } + + let correctMessages: [Recorded] = [ + next(210, (5 + 0) * 2), + next(220, (6 + 1) * 2), + next(230, (7 + 2) * 2), + next(240, (8 + 3) * 2), + ] + + let correctSubscriptions = [ + Subscription(200, 290) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } + + func testMap1_SelectorThrows() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 1), + next(210, 5), + next(220, 6), + next(230, 7), + next(240, 8), + error(300, testError) + ]) + + let res = scheduler.start { xs >- mapWithIndexOrDie { $0 < 7 ? success(($0 + $1) * 2) : .Error(testError) } } + + let correctMessages: [Recorded] = [ + next(210, (5 + 0) * 2), + next(220, (6 + 1) * 2), + error(230, testError) + ] + + let correctSubscriptions = [ + Subscription(200, 230) + ] + + XCTAssertEqual(res.messages, correctMessages) + XCTAssertEqual(xs.subscriptions, correctSubscriptions) + } +} \ No newline at end of file diff --git a/Rx/RxTests/Tests/Observable+StandardSequenceOperatorsTest.swift b/Rx/RxTests/Tests/Observable+StandardSequenceOperatorsTest.swift new file mode 100644 index 00000000..c6f3811d --- /dev/null +++ b/Rx/RxTests/Tests/Observable+StandardSequenceOperatorsTest.swift @@ -0,0 +1,210 @@ +// +// Observable+StandardSequenceOperatorsTest.swift +// Rx +// +// Created by Krunoslav Zaher on 2/17/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import XCTest +import Rx + + +class ObservableStandardSequenceOperators : RxTest { + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } +} + +func isPrime(i: Int) -> Bool { + if i <= 1 { + return false + } + + var max = Int(sqrt(Float(i))) + for (var j = 2; j <= max; ++j) { + if i % j == 0 { + return false + } + } + + return true +} + +// Where Tests +extension ObservableStandardSequenceOperators { + func test_whereComplete() { + let scheduler = TestScheduler(initialClock: 0) + + var invoked = 0 + + var xs = scheduler.createHotObservable([ + next(110, 1), + next(180, 2), + next(230, 3), + next(270, 4), + next(340, 5), + next(380, 6), + next(390, 7), + next(450, 8), + next(470, 9), + next(560, 10), + next(580, 11), + completed(600), + next(610, 12), + error(620, testError), + completed(630) + ]) + + let res = scheduler.start { () -> Observable in + return xs >- filter { (num: Int) -> Bool in + invoked++; + return isPrime(num); + } + } + + XCTAssertEqual(res.messages, [ + next(230, 3), + next(340, 5), + next(390, 7), + next(580, 11), + completed(600) + ]) + + XCTAssertEqual(xs.subscriptions, [ + Subscription(200, 600) + ]) + + XCTAssertEqual(9, invoked) + } + + func test_whereTrue() { + let scheduler = TestScheduler(initialClock: 0) + + var invoked = 0 + + var xs = scheduler.createHotObservable([ + next(110, 1), + next(180, 2), + next(230, 3), + next(270, 4), + next(340, 5), + next(380, 6), + next(390, 7), + next(450, 8), + next(470, 9), + next(560, 10), + next(580, 11), + completed(600) + ]) + + let res = scheduler.start { () -> Observable in + return xs >- filter { (num: Int) -> Bool in + invoked++ + return true + } + } + + XCTAssertEqual(res.messages, [ + next(230, 3), + next(270, 4), + next(340, 5), + next(380, 6), + next(390, 7), + next(450, 8), + next(470, 9), + next(560, 10), + next(580, 11), + completed(600) + ]) + + XCTAssertEqual(xs.subscriptions, [ + Subscription(200, 600) + ]) + + XCTAssertEqual(9, invoked) + } + + func test_whereFalse() { + let scheduler = TestScheduler(initialClock: 0) + + var invoked = 0 + + var xs = scheduler.createHotObservable([ + next(110, 1), + next(180, 2), + next(230, 3), + next(270, 4), + next(340, 5), + next(380, 6), + next(390, 7), + next(450, 8), + next(470, 9), + next(560, 10), + next(580, 11), + completed(600) + ]) + + let res = scheduler.start { () -> Observable in + return xs >- filter { (num: Int) -> Bool in + invoked++ + return false + } + } + + XCTAssertEqual(res.messages, [ + completed(600) + ]) + + XCTAssertEqual(xs.subscriptions, [ + Subscription(200, 600) + ]) + + XCTAssertEqual(9, invoked) + } + + func test_whereDisposed() { + let scheduler = TestScheduler(initialClock: 0) + + var invoked = 0 + + var xs = scheduler.createHotObservable([ + next(110, 1), + next(180, 2), + next(230, 3), + next(270, 4), + next(340, 5), + next(380, 6), + next(390, 7), + next(450, 8), + next(470, 9), + next(560, 10), + next(580, 11), + completed(600) + ]) + + let res = scheduler.start(400) { () -> Observable in + return xs >- filter { (num: Int) -> Bool in + invoked++; + return isPrime(num) + } + } + + XCTAssertEqual(res.messages, [ + next(230, 3), + next(340, 5), + next(390, 7) + ]) + + XCTAssertEqual(xs.subscriptions, [ + Subscription(200, 400) + ]) + + XCTAssertEqual(5, invoked) + } +} diff --git a/Rx/RxTests/Tests/Observable+TimeTest.swift b/Rx/RxTests/Tests/Observable+TimeTest.swift new file mode 100644 index 00000000..2319589a --- /dev/null +++ b/Rx/RxTests/Tests/Observable+TimeTest.swift @@ -0,0 +1,261 @@ +// +// Observable+TimeTest.swift +// Rx +// +// Created by Krunoslav Zaher on 3/23/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx +import XCTest + +class ObservableTimeTest : RxTest { + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } +} + +// throttle + +extension ObservableTimeTest { + func test_ThrottleTimeSpan_AllPass() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 0), + next(210, 1), + next(240, 2), + next(270, 3), + next(300, 4), + completed(400) + ]) + + let res = scheduler.start { + xs >- throttle(20, scheduler) + } + + let correct = [ + next(230, 1), + next(260, 2), + next(290, 3), + next(320, 4), + completed(400) + ] + + XCTAssertEqual(res.messages, correct) + + let subscriptions = [ + Subscription(200, 400) + ] + + XCTAssertEqual(xs.subscriptions, subscriptions) + } + + func test_ThrottleTimeSpan_AllPass_ErrorEnd() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 0), + next(210, 1), + next(240, 2), + next(270, 3), + next(300, 4), + error(400, testError) + ]) + + let res = scheduler.start { + xs >- throttle(20, scheduler) + } + + let correct = [ + next(230, 1), + next(260, 2), + next(290, 3), + next(320, 4), + error(400, testError) + ] + + XCTAssertEqual(res.messages, correct) + + let subscriptions = [ + Subscription(200, 400) + ] + + XCTAssertEqual(xs.subscriptions, subscriptions) + } + + func test_ThrottleTimeSpan_AllDrop() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 0), + next(210, 1), + next(240, 2), + next(270, 3), + next(300, 4), + next(330, 5), + next(360, 6), + next(390, 7), + completed(400) + ]) + + let res = scheduler.start { + xs >- throttle(40, scheduler) + } + + let correct = [ + next(400, 7), + completed(400) + ] + + XCTAssertEqual(res.messages, correct) + + let subscriptions = [ + Subscription(200, 400) + ] + + XCTAssertEqual(xs.subscriptions, subscriptions) + } + + func test_ThrottleTimeSpan_AllDrop_ErrorEnd() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 0), + next(210, 1), + next(240, 2), + next(270, 3), + next(300, 4), + next(330, 5), + next(360, 6), + next(390, 7), + error(400, testError) + ]) + + let res = scheduler.start { + xs >- throttle(40, scheduler) + } + + let correct: [Recorded] = [ + error(400, testError) + ] + + XCTAssertEqual(res.messages, correct) + + let subscriptions = [ + Subscription(200, 400) + ] + + XCTAssertEqual(xs.subscriptions, subscriptions) + } + + func test_ThrottleEmpty() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 0), + completed(300) + ]) + + let res = scheduler.start { + xs >- throttle(10, scheduler) + } + + let correct: [Recorded] = [ + completed(300) + ] + + XCTAssertEqual(res.messages, correct) + + let subscriptions = [ + Subscription(200, 300) + ] + + XCTAssertEqual(xs.subscriptions, subscriptions) + } + + func test_ThrottleError() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 0), + error(300, testError) + ]) + + let res = scheduler.start { + xs >- throttle(10, scheduler) + } + + let correct: [Recorded] = [ + error(300, testError) + ] + + XCTAssertEqual(res.messages, correct) + + let subscriptions = [ + Subscription(200, 300) + ] + + XCTAssertEqual(xs.subscriptions, subscriptions) + } + + func test_ThrottleNever() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 0), + ]) + + let res = scheduler.start { + xs >- throttle(10, scheduler) + } + + let correct: [Recorded] = [ + ] + + XCTAssertEqual(res.messages, correct) + + let subscriptions = [ + Subscription(200, 1000) + ] + + XCTAssertEqual(xs.subscriptions, subscriptions) + } + + func test_ThrottleSimple() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + next(150, 0), + next(210, 1), + next(240, 2), + next(250, 3), + next(280, 4), + completed(300) + ]) + + let res = scheduler.start { + xs >- throttle(20, scheduler) + } + + let correct: [Recorded] = [ + next(230, 1), + next(270, 3), + next(300, 4), + completed(300) + ] + + XCTAssertEqual(res.messages, correct) + + let subscriptions = [ + Subscription(200, 300) + ] + + XCTAssertEqual(xs.subscriptions, subscriptions) + } +} \ No newline at end of file diff --git a/Rx/RxTests/Tests/QueueTests.swift b/Rx/RxTests/Tests/QueueTests.swift new file mode 100644 index 00000000..d2554c42 --- /dev/null +++ b/Rx/RxTests/Tests/QueueTests.swift @@ -0,0 +1,42 @@ +// +// QueueTests.swift +// Rx +// +// Created by Krunoslav Zaher on 3/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx +import XCTest + +class QueueTest : RxTest { + + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } +} + +extension QueueTest { + func test() { + var queue: Queue = Queue(capacity: 2) + + XCTAssertEqual(queue.count, 0) + + for var i = 100; i < 200; ++i { + queue.enqueue(i) + + XCTAssertEqual(queue.peek(), 100) + XCTAssertEqual(queue.count, i - 100 + 1) + } + + for var i = 100; i < 200; ++i { + XCTAssertEqual(queue.dequeue(), i) + XCTAssertEqual(queue.count, 200 - i - 1) + } + } +} \ No newline at end of file diff --git a/Rx/RxTests/Tests/RxTest.swift b/Rx/RxTests/Tests/RxTest.swift new file mode 100644 index 00000000..9e813e18 --- /dev/null +++ b/Rx/RxTests/Tests/RxTest.swift @@ -0,0 +1,61 @@ +// +// RxTest.swift +// RxTests +// +// Created by Krunoslav Zaher on 2/8/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import UIKit +import XCTest +import Rx + +typealias Time = Int + +let testError = NSError(domain: "dummyError", code: -232, userInfo: nil) +let testError1 = NSError(domain: "dummyError1", code: -233, userInfo: nil) +let testError2 = NSError(domain: "dummyError2", code: -234, userInfo: nil) + +class RxTest: XCTestCase { + struct Defaults { + static let created = 100 + static let subscribed = 200 + static let disposed = 1000 + } + + private var startResourceCount: Int32 = 0 + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + +#if DEBUG + self.startResourceCount = resourceCount +#endif + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + +#if DEBUG + XCTAssertEqual(self.startResourceCount, resourceCount) +#endif + } + + func on(time: Time, _ event: Event) -> Recorded { + return Recorded(time: time, event: event) + } + + func next(time: Time, _ value: T) -> Recorded { + return Recorded(time: time, event: .Next(Box(value))) + } + + func completed(time: Time) -> Recorded { + return Recorded(time: time, event: .Completed) + } + + func error(time: Time, _ error: NSError) -> Recorded { + return Recorded(time: time, event: .Error(error)) + } +} diff --git a/RxCocoa/RxCocoa.xcodeproj/project.pbxproj b/RxCocoa/RxCocoa.xcodeproj/project.pbxproj new file mode 100644 index 00000000..a7343bd3 --- /dev/null +++ b/RxCocoa/RxCocoa.xcodeproj/project.pbxproj @@ -0,0 +1,507 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + C81553E41A98AB4A00C63152 /* RxCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = C81553E31A98AB4A00C63152 /* RxCocoa.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C81553EA1A98AB4A00C63152 /* RxCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C81553DE1A98AB4A00C63152 /* RxCocoa.framework */; }; + C81553F11A98AB4A00C63152 /* RxCocoaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81553F01A98AB4A00C63152 /* RxCocoaTests.swift */; }; + C81553FF1A98AC3700C63152 /* KVOObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81553FE1A98AC3700C63152 /* KVOObservable.swift */; }; + C81554031A9900F700C63152 /* ControlTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81554021A9900F700C63152 /* ControlTarget.swift */; }; + C81554051A99037500C63152 /* RxCocoa.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81554041A99037500C63152 /* RxCocoa.swift */; }; + C865B89A1ACF38A0008BE3B3 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = C865B8991ACF38A0008BE3B3 /* Logging.swift */; }; + C8813D9D1AD2988A0072A050 /* UIButton+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D951AD2988A0072A050 /* UIButton+Rx.swift */; }; + C8813D9E1AD2988A0072A050 /* UICollectionView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D961AD2988A0072A050 /* UICollectionView+Rx.swift */; }; + C8813D9F1AD2988A0072A050 /* UIImageView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D971AD2988A0072A050 /* UIImageView+Rx.swift */; }; + C8813DA01AD2988A0072A050 /* UILabel+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D981AD2988A0072A050 /* UILabel+Rx.swift */; }; + C8813DA11AD2988A0072A050 /* UIScrollView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D991AD2988A0072A050 /* UIScrollView+Rx.swift */; }; + C8813DA21AD2988A0072A050 /* UISearchBar+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D9A1AD2988A0072A050 /* UISearchBar+Rx.swift */; }; + C8813DA31AD2988A0072A050 /* UITableView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D9B1AD2988A0072A050 /* UITableView+Rx.swift */; }; + C8813DA41AD2988A0072A050 /* UITextField+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813D9C1AD2988A0072A050 /* UITextField+Rx.swift */; }; + C8813DA61AD298B20072A050 /* Observable+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813DA51AD298B20072A050 /* Observable+UI.swift */; }; + C8813DA81AD298BF0072A050 /* Prefix.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813DA71AD298BF0072A050 /* Prefix.swift */; }; + C885B6391AC01F6100AD34DA /* NSURLSession+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C885B6381AC01F6100AD34DA /* NSURLSession+Rx.swift */; }; + C8F57F471AD02DE5006B8702 /* Rx.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8F57F461AD02DE5006B8702 /* Rx.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + C81553EB1A98AB4A00C63152 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C81553D51A98AB4A00C63152 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C81553DD1A98AB4A00C63152; + remoteInfo = RxCocoa; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + C81553DE1A98AB4A00C63152 /* RxCocoa.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RxCocoa.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C81553E21A98AB4A00C63152 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C81553E31A98AB4A00C63152 /* RxCocoa.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RxCocoa.h; sourceTree = ""; }; + C81553E91A98AB4A00C63152 /* RxCocoaTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RxCocoaTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + C81553EF1A98AB4A00C63152 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C81553F01A98AB4A00C63152 /* RxCocoaTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxCocoaTests.swift; sourceTree = ""; }; + C81553FE1A98AC3700C63152 /* KVOObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KVOObservable.swift; sourceTree = ""; }; + C81554021A9900F700C63152 /* ControlTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlTarget.swift; sourceTree = ""; }; + C81554041A99037500C63152 /* RxCocoa.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxCocoa.swift; sourceTree = ""; }; + C865B8991ACF38A0008BE3B3 /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; + C8813D951AD2988A0072A050 /* UIButton+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIButton+Rx.swift"; sourceTree = ""; }; + C8813D961AD2988A0072A050 /* UICollectionView+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Rx.swift"; sourceTree = ""; }; + C8813D971AD2988A0072A050 /* UIImageView+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImageView+Rx.swift"; sourceTree = ""; }; + C8813D981AD2988A0072A050 /* UILabel+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+Rx.swift"; sourceTree = ""; }; + C8813D991AD2988A0072A050 /* UIScrollView+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Rx.swift"; sourceTree = ""; }; + C8813D9A1AD2988A0072A050 /* UISearchBar+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UISearchBar+Rx.swift"; sourceTree = ""; }; + C8813D9B1AD2988A0072A050 /* UITableView+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+Rx.swift"; sourceTree = ""; }; + C8813D9C1AD2988A0072A050 /* UITextField+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextField+Rx.swift"; sourceTree = ""; }; + C8813DA51AD298B20072A050 /* Observable+UI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Observable+UI.swift"; path = "RxCocoa/Observables/Observable+UI.swift"; sourceTree = SOURCE_ROOT; }; + C8813DA71AD298BF0072A050 /* Prefix.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Prefix.swift; sourceTree = ""; }; + C885B6381AC01F6100AD34DA /* NSURLSession+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSURLSession+Rx.swift"; sourceTree = ""; }; + C8F57F461AD02DE5006B8702 /* Rx.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Rx.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + C81553DA1A98AB4A00C63152 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C8F57F471AD02DE5006B8702 /* Rx.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C81553E61A98AB4A00C63152 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C81553EA1A98AB4A00C63152 /* RxCocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + C81553D41A98AB4A00C63152 = { + isa = PBXGroup; + children = ( + C8F57F461AD02DE5006B8702 /* Rx.framework */, + C81553E01A98AB4A00C63152 /* RxCocoa */, + C81553ED1A98AB4A00C63152 /* RxCocoaTests */, + C81553DF1A98AB4A00C63152 /* Products */, + ); + sourceTree = ""; + }; + C81553DF1A98AB4A00C63152 /* Products */ = { + isa = PBXGroup; + children = ( + C81553DE1A98AB4A00C63152 /* RxCocoa.framework */, + C81553E91A98AB4A00C63152 /* RxCocoaTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + C81553E01A98AB4A00C63152 /* RxCocoa */ = { + isa = PBXGroup; + children = ( + C8813D931AD2988A0072A050 /* Observables */, + C8813D941AD2988A0072A050 /* UI+Rx */, + C81553E31A98AB4A00C63152 /* RxCocoa.h */, + C81553E11A98AB4A00C63152 /* Supporting Files */, + C81554041A99037500C63152 /* RxCocoa.swift */, + C885B6381AC01F6100AD34DA /* NSURLSession+Rx.swift */, + C81553FE1A98AC3700C63152 /* KVOObservable.swift */, + C81554021A9900F700C63152 /* ControlTarget.swift */, + C865B8991ACF38A0008BE3B3 /* Logging.swift */, + ); + path = RxCocoa; + sourceTree = ""; + }; + C81553E11A98AB4A00C63152 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + C81553E21A98AB4A00C63152 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + C81553ED1A98AB4A00C63152 /* RxCocoaTests */ = { + isa = PBXGroup; + children = ( + C81553F01A98AB4A00C63152 /* RxCocoaTests.swift */, + C81553EE1A98AB4A00C63152 /* Supporting Files */, + ); + path = RxCocoaTests; + sourceTree = ""; + }; + C81553EE1A98AB4A00C63152 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + C81553EF1A98AB4A00C63152 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + C8813D931AD2988A0072A050 /* Observables */ = { + isa = PBXGroup; + children = ( + C8813DA51AD298B20072A050 /* Observable+UI.swift */, + C8813DA71AD298BF0072A050 /* Prefix.swift */, + ); + path = Observables; + sourceTree = ""; + }; + C8813D941AD2988A0072A050 /* UI+Rx */ = { + isa = PBXGroup; + children = ( + C8813D951AD2988A0072A050 /* UIButton+Rx.swift */, + C8813D961AD2988A0072A050 /* UICollectionView+Rx.swift */, + C8813D971AD2988A0072A050 /* UIImageView+Rx.swift */, + C8813D981AD2988A0072A050 /* UILabel+Rx.swift */, + C8813D991AD2988A0072A050 /* UIScrollView+Rx.swift */, + C8813D9A1AD2988A0072A050 /* UISearchBar+Rx.swift */, + C8813D9B1AD2988A0072A050 /* UITableView+Rx.swift */, + C8813D9C1AD2988A0072A050 /* UITextField+Rx.swift */, + ); + path = "UI+Rx"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + C81553DB1A98AB4A00C63152 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + C81553E41A98AB4A00C63152 /* RxCocoa.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + C81553DD1A98AB4A00C63152 /* RxCocoa */ = { + isa = PBXNativeTarget; + buildConfigurationList = C81553F41A98AB4A00C63152 /* Build configuration list for PBXNativeTarget "RxCocoa" */; + buildPhases = ( + C81553D91A98AB4A00C63152 /* Sources */, + C81553DA1A98AB4A00C63152 /* Frameworks */, + C81553DB1A98AB4A00C63152 /* Headers */, + C81553DC1A98AB4A00C63152 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RxCocoa; + productName = RxCocoa; + productReference = C81553DE1A98AB4A00C63152 /* RxCocoa.framework */; + productType = "com.apple.product-type.framework"; + }; + C81553E81A98AB4A00C63152 /* RxCocoaTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = C81553F71A98AB4A00C63152 /* Build configuration list for PBXNativeTarget "RxCocoaTests" */; + buildPhases = ( + C81553E51A98AB4A00C63152 /* Sources */, + C81553E61A98AB4A00C63152 /* Frameworks */, + C81553E71A98AB4A00C63152 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + C81553EC1A98AB4A00C63152 /* PBXTargetDependency */, + ); + name = RxCocoaTests; + productName = RxCocoaTests; + productReference = C81553E91A98AB4A00C63152 /* RxCocoaTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + C81553D51A98AB4A00C63152 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = "Krunoslav Zaher"; + TargetAttributes = { + C81553DD1A98AB4A00C63152 = { + CreatedOnToolsVersion = 6.1.1; + }; + C81553E81A98AB4A00C63152 = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = C81553D81A98AB4A00C63152 /* Build configuration list for PBXProject "RxCocoa" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = C81553D41A98AB4A00C63152; + productRefGroup = C81553DF1A98AB4A00C63152 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C81553DD1A98AB4A00C63152 /* RxCocoa */, + C81553E81A98AB4A00C63152 /* RxCocoaTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + C81553DC1A98AB4A00C63152 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C81553E71A98AB4A00C63152 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + C81553D91A98AB4A00C63152 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C8813DA31AD2988A0072A050 /* UITableView+Rx.swift in Sources */, + C8813DA41AD2988A0072A050 /* UITextField+Rx.swift in Sources */, + C865B89A1ACF38A0008BE3B3 /* Logging.swift in Sources */, + C8813D9D1AD2988A0072A050 /* UIButton+Rx.swift in Sources */, + C8813DA81AD298BF0072A050 /* Prefix.swift in Sources */, + C8813DA01AD2988A0072A050 /* UILabel+Rx.swift in Sources */, + C81554031A9900F700C63152 /* ControlTarget.swift in Sources */, + C8813DA61AD298B20072A050 /* Observable+UI.swift in Sources */, + C81553FF1A98AC3700C63152 /* KVOObservable.swift in Sources */, + C81554051A99037500C63152 /* RxCocoa.swift in Sources */, + C885B6391AC01F6100AD34DA /* NSURLSession+Rx.swift in Sources */, + C8813D9E1AD2988A0072A050 /* UICollectionView+Rx.swift in Sources */, + C8813DA21AD2988A0072A050 /* UISearchBar+Rx.swift in Sources */, + C8813D9F1AD2988A0072A050 /* UIImageView+Rx.swift in Sources */, + C8813DA11AD2988A0072A050 /* UIScrollView+Rx.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C81553E51A98AB4A00C63152 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C81553F11A98AB4A00C63152 /* RxCocoaTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + C81553EC1A98AB4A00C63152 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C81553DD1A98AB4A00C63152 /* RxCocoa */; + targetProxy = C81553EB1A98AB4A00C63152 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + C81553F21A98AB4A00C63152 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + C81553F31A98AB4A00C63152 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + CURRENT_PROJECT_VERSION = 1; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + C81553F51A98AB4A00C63152 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/Rx-cfkyozdvlaegqibzixjokeysigeo/Build/Products/Debug-iphoneos", + "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/Rx-cfkyozdvlaegqibzixjokeysigeo/Build/Products/Release-iphoneos", + ); + INFOPLIST_FILE = RxCocoa/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = "-D DEBUG"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + C81553F61A98AB4A00C63152 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/Rx-cfkyozdvlaegqibzixjokeysigeo/Build/Products/Debug-iphoneos", + "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/Rx-cfkyozdvlaegqibzixjokeysigeo/Build/Products/Release-iphoneos", + ); + INFOPLIST_FILE = RxCocoa/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; + C81553F81A98AB4A00C63152 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = RxCocoaTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + C81553F91A98AB4A00C63152 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = RxCocoaTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C81553D81A98AB4A00C63152 /* Build configuration list for PBXProject "RxCocoa" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C81553F21A98AB4A00C63152 /* Debug */, + C81553F31A98AB4A00C63152 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C81553F41A98AB4A00C63152 /* Build configuration list for PBXNativeTarget "RxCocoa" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C81553F51A98AB4A00C63152 /* Debug */, + C81553F61A98AB4A00C63152 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C81553F71A98AB4A00C63152 /* Build configuration list for PBXNativeTarget "RxCocoaTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C81553F81A98AB4A00C63152 /* Debug */, + C81553F91A98AB4A00C63152 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = C81553D51A98AB4A00C63152 /* Project object */; +} diff --git a/RxCocoa/RxCocoa.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/RxCocoa/RxCocoa.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..af03184a --- /dev/null +++ b/RxCocoa/RxCocoa.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/RxCocoa/RxCocoa.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/RxCocoa.xcscheme b/RxCocoa/RxCocoa.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/RxCocoa.xcscheme new file mode 100644 index 00000000..1f7d19c7 --- /dev/null +++ b/RxCocoa/RxCocoa.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/RxCocoa.xcscheme @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RxCocoa/RxCocoa.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/xcschememanagement.plist b/RxCocoa/RxCocoa.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000..407bf7f9 --- /dev/null +++ b/RxCocoa/RxCocoa.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,27 @@ + + + + + SchemeUserState + + RxCocoa.xcscheme + + orderHint + 1 + + + SuppressBuildableAutocreation + + C81553DD1A98AB4A00C63152 + + primary + + + C81553E81A98AB4A00C63152 + + primary + + + + + diff --git a/RxCocoa/RxCocoa/ControlTarget.swift b/RxCocoa/RxCocoa/ControlTarget.swift new file mode 100644 index 00000000..d101e3f8 --- /dev/null +++ b/RxCocoa/RxCocoa/ControlTarget.swift @@ -0,0 +1,52 @@ +// +// ControlTarget.swift +// RxCocoa +// +// Created by Krunoslav Zaher on 2/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +class ControlTarget: NSObject, Disposable { + typealias Callback = (UIControl) -> Void + + let selector: Selector = "eventHandler:" + + let control: UIControl + let controlEvents: UIControlEvents + var callback: Callback? + + init(control: UIControl, controlEvents: UIControlEvents, callback: Callback) { + self.control = control + self.controlEvents = controlEvents + self.callback = callback + + super.init() + + control.addTarget(self, action: selector, forControlEvents: controlEvents) + + let method = self.methodForSelector(selector) + if method == nil { + rxFatalError("Can't find method") + } + } + + func eventHandler(sender: UIControl!) { + if let callback = self.callback { + callback(self.control) + } + } + + func dispose() { + MainScheduler.ensureExecutingOnScheduler() + + self.control.removeTarget(self, action: self.selector, forControlEvents: self.controlEvents) + self.callback = nil + } + + deinit { + self.control.removeTarget(self, action: selector, forControlEvents: controlEvents) + } +} \ No newline at end of file diff --git a/RxCocoa/RxCocoa/Info.plist b/RxCocoa/RxCocoa/Info.plist new file mode 100644 index 00000000..c1620d0a --- /dev/null +++ b/RxCocoa/RxCocoa/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + Krunoslav-Zaher.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/RxCocoa/RxCocoa/KVOObservable.swift b/RxCocoa/RxCocoa/KVOObservable.swift new file mode 100644 index 00000000..462f61fd --- /dev/null +++ b/RxCocoa/RxCocoa/KVOObservable.swift @@ -0,0 +1,78 @@ +// +// KVOObservable.swift +// RxCocoa +// +// Created by Krunoslav Zaher on 2/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +class KVOObserver : NSObject { + typealias Callback = (Element) -> Void + + let object: NSObject + let callback: Callback! + let path: String + + init(object: NSObject, path: String, callback: Callback) { + self.object = object + self.path = path + self.callback = nil + + super.init() + + self.object.addObserver(self, forKeyPath: self.path, options: NSKeyValueObservingOptions.New, context: nil) + } + + override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer) { + let newValue = change[NSKeyValueChangeNewKey] as! Element + + self.callback(newValue) + } + + deinit { + self.object.removeObserver(self, forKeyPath: self.path) + } +} + +/** +* This class should be used from main thread only +*/ +public class KVOObservable : Observable { + var observer: KVOObserver! + + var observers: Bag> + + var lock = Lock() + + public init(object: NSObject, path: String) { + self.observers = Bag() + + self.observer = nil + + super.init() + + self.observer = KVOObserver(object: object, path: path) { [unowned self] value in + let observers = self.observers + let invokeResult = doAll(observers.all.map { observer in + observer.on(.Next(Box(value))) + }) + + handleObserverResult(invokeResult) + } + } + + public override func subscribe(observer: ObserverOf) -> Result { + return lock.calculateLocked { + let key = self.observers.put(observer) + + return success(AnonymousDisposable { () in + self.lock.performLocked { + _ = self.observers.removeKey(key) + } + }) + } + } +} \ No newline at end of file diff --git a/RxCocoa/RxCocoa/Logging.swift b/RxCocoa/RxCocoa/Logging.swift new file mode 100644 index 00000000..590859dc --- /dev/null +++ b/RxCocoa/RxCocoa/Logging.swift @@ -0,0 +1,18 @@ +// +// Logging.swift +// RxCocoa +// +// Created by Krunoslav Zaher on 4/3/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +// read your own configuration +struct Logging { + #if DEBUG + static let URLRequests = true + #else + static let URLRequests = false + #endif +} \ No newline at end of file diff --git a/RxCocoa/RxCocoa/NSURLSession+Rx.swift b/RxCocoa/RxCocoa/NSURLSession+Rx.swift new file mode 100644 index 00000000..b51b854b --- /dev/null +++ b/RxCocoa/RxCocoa/NSURLSession+Rx.swift @@ -0,0 +1,135 @@ +// +// NSURLSession+Rx.swift +// RxCocoa +// +// Created by Krunoslav Zaher on 3/23/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import UIKit +import Rx + +func escapeTerminalString(value: String) -> String { + return value.stringByReplacingOccurrencesOfString("\"", withString: "\\\"", options: NSStringCompareOptions.allZeros, range: nil) +} + +func convertURLRequestToCurlCommand(request: NSURLRequest) -> String { + let method = request.HTTPMethod ?? "GET" + var returnValue = "curl -i -v -X \(method) " + + if request.HTTPMethod == "POST" && request.HTTPBody != nil { + let maybeBody = NSString(data: request.HTTPBody!, encoding: NSUTF8StringEncoding) as? String + if let body = maybeBody { + returnValue += "-d \"\(maybeBody)\"" + } + } + + for (key, value) in request.allHTTPHeaderFields ?? [:] { + let escapedKey = escapeTerminalString((key as? String) ?? "") + let escapedValue = escapeTerminalString((value as? String) ?? "") + returnValue += "-H \"\(escapedKey): \(escapedValue)\" " + } + + let URLString = request.URL?.absoluteString ?? "" + + returnValue += "\"\(escapeTerminalString(URLString))\"" + + return returnValue +} + +func convertResponseToString(data: NSData!, response: NSURLResponse!, error: NSError!, interval: NSTimeInterval) -> String { + let ms = Int(interval * 1000) + + if let response = response as? NSHTTPURLResponse { + if 200 ..< 300 ~= response.statusCode { + return "Success (\(ms)ms): Status \(response.statusCode)" + } + else { + return "Failure (\(ms)ms): Status \(response.statusCode)" + } + } + + if let error = error { + if error.domain == NSURLErrorDomain && error.code == NSURLErrorCancelled { + return "Cancelled (\(ms)ms)" + } + return "Failure (\(ms)ms): NSError > \(error)" + } + + return "" +} + +extension NSURLSession { + public func rx_observableRequest(request: NSURLRequest) -> Observable<(NSData!, NSURLResponse!, NSError!)> { + return create { observer in + + // smart compiler should be able to optimize this out + var d: NSDate! + + if Logging.URLRequests { + d = NSDate() + } + + let task = self.dataTaskWithRequest(request) { (data, response, error) in + + if Logging.URLRequests { + let interval = NSDate().timeIntervalSinceDate(d) + println(convertURLRequestToCurlCommand(request)) + println(convertResponseToString(data, response, error, interval)) + } + + handleObserverResult(observer.on(.Next(Box(data, response, error)))) + handleObserverResult(observer.on(.Completed)) + } + + task.resume() + + return success(AnonymousDisposable { + task.cancel() + }) + } + } + + public func rx_observableDataRequest(request: NSURLRequest) -> Observable> { + return rx_observableRequest(request) >- map { (data, response, e) -> Result in + if e != nil { + return .Error(e) + } + + if let response = response as? NSHTTPURLResponse { + if 200 ..< 300 ~= response.statusCode { + return success(data!) + } + else { + return .Error(rxError(.NetworkError, "Server return failure", [RxCocoaErrorHTTPResponseKey: response])) + } + } + else { + rxFatalError("response = nil") + + return .Error(UnknownError) + } + } + } + + public func rx_observableJSONWithRequest(request: NSURLRequest) -> Observable> { + return rx_observableDataRequest(request) >- map { (maybeData) -> Result in + maybeData >== { data in + var serializationError: NSError? + let result: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.allZeros, error: &serializationError) + + if let result: AnyObject = result { + return success(result) + } + else { + return .Error(serializationError!) + } + } + } + } + + public func rx_observableJSONWithURL(URL: NSURL) -> Observable> { + return rx_observableJSONWithRequest(NSURLRequest(URL: URL)) + } +} \ No newline at end of file diff --git a/RxCocoa/RxCocoa/Observables/Observable+UI.swift b/RxCocoa/RxCocoa/Observables/Observable+UI.swift new file mode 100644 index 00000000..675af495 --- /dev/null +++ b/RxCocoa/RxCocoa/Observables/Observable+UI.swift @@ -0,0 +1,59 @@ +// +// Observable+UI.swift +// RxCocoa +// +// Created by Krunoslav Zaher on 4/2/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +// In Rx every subscription uses it's own set of resources. +// In case of UI, asynchronous operations are usually used to fetch data from server. +// In case data is fetched from server, stale data can be server first, and then updated with +// fresh data from server. + +public func sharedSubscriptionWithCachedResult(source: Observable) + -> Observable { + return source >- multicast(Variable()) >- refCount +} + +public func sharedSubscriptionWithCachedResult + (initialValue: E) + -> (Observable -> Observable) { + return { source in + return source >- multicast(Variable(.Next(Box(initialValue)))) >- refCount + } +} + +// prefix with + +// Prefixes observable sequence with `prefix` element. +// The same functionality could be achieved using `concat([returnElement(prefix), source])`, +// but this is significantly more efficient implementation. +public func prefixWith + (prefix: E) + -> (Observable -> Observable) { + return { source in + return Prefix(source: source, element: prefix) + } +} + +// doOnNext + +public func doOnNext + (actionOnNext: E -> Void) + -> (Observable -> Observable) { + return { source in + return source >- `do` { event in + switch event { + case .Next(let boxedValue): + let value = boxedValue.value + actionOnNext(value) + default: + break + } + } + } +} \ No newline at end of file diff --git a/RxCocoa/RxCocoa/Observables/Prefix.swift b/RxCocoa/RxCocoa/Observables/Prefix.swift new file mode 100644 index 00000000..3fea0944 --- /dev/null +++ b/RxCocoa/RxCocoa/Observables/Prefix.swift @@ -0,0 +1,31 @@ +// +// Prefix.swift +// RxCocoa +// +// Created by Krunoslav Zaher on 4/6/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +class Prefix: Observable { + let element: Element + let source: Observable + + init(source: Observable, element: Element) { + self.source = source + self.element = element + } + + override func subscribe(observer: ObserverOf) -> Result { + let result = observer.on(.Next(Box(element))) + + if let error = result.error { + observer.on(.Error(error)) + return .Error(error) + } + + return source.subscribe(observer) + } +} \ No newline at end of file diff --git a/RxCocoa/RxCocoa/RxCocoa.h b/RxCocoa/RxCocoa/RxCocoa.h new file mode 100644 index 00000000..0df11f10 --- /dev/null +++ b/RxCocoa/RxCocoa/RxCocoa.h @@ -0,0 +1,19 @@ +// +// RxCocoa.h +// RxCocoa +// +// Created by Krunoslav Zaher on 2/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +#import + +//! Project version number for RxCocoa. +FOUNDATION_EXPORT double RxCocoaVersionNumber; + +//! Project version string for RxCocoa. +FOUNDATION_EXPORT const unsigned char RxCocoaVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/RxCocoa/RxCocoa/RxCocoa.swift b/RxCocoa/RxCocoa/RxCocoa.swift new file mode 100644 index 00000000..ff22f47f --- /dev/null +++ b/RxCocoa/RxCocoa/RxCocoa.swift @@ -0,0 +1,54 @@ +// +// RxCocoa.swift +// RxCocoa +// +// Created by Krunoslav Zaher on 2/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +public enum RxCocoaError : Int { + case Unknown = 0 + case NetworkError = 1 + case InvalidOperation = 2 +} + +public let RxCocoaErrorDomain = "RxCocoaError" + +public let RxCocoaErrorHTTPResponseKey = "RxCocoaErrorHTTPResponseKey" + +func rxError(errorCode: RxCocoaError, message: String) -> NSError { + return NSError(domain: RxCocoaErrorDomain, code: errorCode.rawValue, userInfo: [NSLocalizedDescriptionKey: message]) +} + +func rxError(errorCode: RxCocoaError, message: String, userInfo: NSDictionary) -> NSError { + let mutableDictionary = NSMutableDictionary(dictionary: userInfo as! [NSObject : AnyObject]) + mutableDictionary[NSLocalizedDescriptionKey] = message + // swift compiler :( + let resultInfo: [NSObject: AnyObject] = (userInfo as NSObject) as! [NSObject: AnyObject] + return NSError(domain: RxCocoaErrorDomain, code: Int(errorCode.rawValue), userInfo: resultInfo) +} + +func removingObserverFailed() { + rxFatalError("Removing observer for key failed") +} + +func handleVoidObserverResult(result: Result) { + handleObserverResult(result) +} + +func rxFatalError(lastMessage: String) { + // The temptation to comment this line is great, but please don't, it's for your own good. The choice is yours. + fatalError(lastMessage) +} + +func handleObserverResult(result: Result) { + switch result { + case .Error(let error): + print("Error happened \(error)") + rxFatalError("Error '\(error)' happened while "); + default: break + } +} \ No newline at end of file diff --git a/RxCocoa/RxCocoa/UI+Rx/UIButton+Rx.swift b/RxCocoa/RxCocoa/UI+Rx/UIButton+Rx.swift new file mode 100644 index 00000000..05277341 --- /dev/null +++ b/RxCocoa/RxCocoa/UI+Rx/UIButton+Rx.swift @@ -0,0 +1,23 @@ +// +// UIButton+Rx.swift +// RxCocoa +// +// Created by Krunoslav Zaher on 3/28/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +extension UIButton { + public func rx_observableTap() -> Observable { + return AnonymousObservable { subscriber in + let observer = ControlTarget(control: self, controlEvents: UIControlEvents.TouchUpInside) { control in + let result = subscriber.on(.Next(Box(()))) + handleObserverResult(result) + } + + return success(observer) + } + } +} \ No newline at end of file diff --git a/RxCocoa/RxCocoa/UI+Rx/UICollectionView+Rx.swift b/RxCocoa/RxCocoa/UI+Rx/UICollectionView+Rx.swift new file mode 100644 index 00000000..ee2af37f --- /dev/null +++ b/RxCocoa/RxCocoa/UI+Rx/UICollectionView+Rx.swift @@ -0,0 +1,260 @@ +// +// UICollectionView+Rx.swift +// RxCocoa +// +// Created by Krunoslav Zaher on 4/2/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +// This cannot be a generic class because of collection view objc runtime that checks for +// implemented selectors in data source +public class CollectionViewDataSource : NSObject, UICollectionViewDataSource { + public typealias CellFactory = (UICollectionView, NSIndexPath, AnyObject) -> UICollectionViewCell + + public var items: [AnyObject] { + get { + return _items + } + } + + var _items: [AnyObject] + + let cellFactory: CellFactory + + public init(cellFactory: CellFactory) { + self._items = [] + self.cellFactory = cellFactory + } + + public func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { + return 1 + } + + public func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return _items.count + } + + public func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { + if indexPath.item < _items.count { + return cellFactory(collectionView, indexPath, self._items[indexPath.item]) + } + else { + rxFatalError("something went wrong") + let cell: UICollectionViewCell? = nil + return cell! + } + } +} + +public class CollectionViewDelegate: ScrollViewDelegate, UICollectionViewDelegate { + public typealias Observer = ObserverOf<(UICollectionView, Int)> + public typealias DisposeKey = Bag.KeyType + + var collectionViewObservers: Bag + + override public init() { + collectionViewObservers = Bag() + } + + public func addCollectionViewObserver(observer: Observer) -> DisposeKey { + MainScheduler.ensureExecutingOnScheduler() + + return collectionViewObservers.put(observer) + } + + public func removeCollectionViewObserver(key: DisposeKey) { + MainScheduler.ensureExecutingOnScheduler() + + let element = collectionViewObservers.removeKey(key) + if element == nil { + removingObserverFailed() + } + } + + public func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { + collectionView.deselectItemAtIndexPath(indexPath, animated: true) + + let event = Event.Next(Box((collectionView, indexPath.item))) + + handleObserverResult(dispatch(event, collectionViewObservers.all)) + } + + deinit { + if collectionViewObservers.count > 0 { + handleVoidObserverResult(.Error(rxError(RxCocoaError.InvalidOperation, "Something went wrong. Deallocating collection view delegate while there are still subscribed observers means that some subscription was left undisposed."))) + } + } +} + +// This is the most simple (but probably most common) way of using rx with UICollectionView. +extension UICollectionView { + override func rx_createDelegate() -> ScrollViewDelegate { + return CollectionViewDelegate() + } + + public func rx_subscribeItemsTo + (dataSource: CollectionViewDataSource) + (source: Observable<[E]>) + -> Result { + + MainScheduler.ensureExecutingOnScheduler() + + if self.dataSource != nil && self.dataSource !== dataSource { + rxFatalError("Data source is different") + } + + self.dataSource = dataSource + + let clearDataSource = AnonymousDisposable { + if self.dataSource != nil && self.dataSource !== dataSource { + rxFatalError("Data source is different") + } + + self.dataSource = nil + } + + return source.subscribe(ObserverOf(AnonymousObserver { event in + switch event { + case .Next(let boxedValue): + let value = boxedValue.value + dataSource._items = value + self.reloadData() + case .Error(let error): + rxFatalError("Something went wrong: \(error)") + case .Completed: + break + } + + return SuccessResult + })) >== { disposable in + return success(CompositeDisposable(clearDataSource, disposable)) + } >>! { e in + clearDataSource.dispose() + return .Error(e) + } + } + + public func rx_subscribeItemsTo + (cellFactory: (UICollectionView, NSIndexPath, E) -> UICollectionViewCell) + (source: Observable<[E]>) + -> Result { + + let dataSource = CollectionViewDataSource(cellFactory: { + cellFactory($0, $1, $2 as! E) + }) + + return self.rx_subscribeItemsTo(dataSource)(source: source) + } + + public func rx_subscribeItemsWithIdentifierTo + (cellIdentifier: String, configureCell: (UICollectionView, NSIndexPath, E, Cell) -> Void) + (source: Observable<[E]>) + -> Result { + + let dataSource = CollectionViewDataSource { + let cell = $0.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: $1) as! Cell + configureCell($0, $1, $2 as! E, cell) + + return cell + } + + return self.rx_subscribeItemsTo(dataSource)(source: source) + } + + + public func rx_observableItemTap() -> Observable<(UICollectionView, Int)> { + _ = rx_checkCollectionViewDelegate() + + return AnonymousObservable { observer in + var maybeDelegate = self.rx_checkCollectionViewDelegate() + + if maybeDelegate == nil { + let delegate = self.rx_createDelegate() as! CollectionViewDelegate + maybeDelegate = delegate + self.delegate = maybeDelegate + } + + let delegate = maybeDelegate! + + let key = delegate.addCollectionViewObserver(observer) + + return success(AnonymousDisposable { + _ = self.rx_checkCollectionViewDelegate() + + delegate.removeCollectionViewObserver(key) + + if delegate.collectionViewObservers.count == 0 { + self.delegate = nil + } + }) + } + } + + public func rx_observableElementTap() -> Observable { + + return rx_observableItemTap() >- Rx.map { (tableView, rowIndex) -> E in + let maybeDataSource: CollectionViewDataSource? = self.rx_getCollectionViewDataSource() + + if maybeDataSource == nil { + rxFatalError("To use element tap table view needs to use table view data source. You can still use `rx_observableItemTap`.") + } + + let dataSource = maybeDataSource! + + return dataSource.items[rowIndex] as! E + } + } + + // private methods + + private func rx_getCollectionViewDataSource() -> CollectionViewDataSource? { + MainScheduler.ensureExecutingOnScheduler() + + if self.dataSource == nil { + return nil + } + + let maybeDataSource = self.dataSource as? CollectionViewDataSource + + if maybeDataSource == nil { + rxFatalError("View already has incompatible data source set. Please remove earlier delegate registration.") + } + + return maybeDataSource! + } + + private func rx_checkCollectionViewDataSource() -> CollectionViewDataSource? { + MainScheduler.ensureExecutingOnScheduler() + + if self.dataSource == nil { + return nil + } + + let maybeDataSource = self.dataSource as? CollectionViewDataSource + + if maybeDataSource == nil { + rxFatalError("View already has incompatible data source set. Please remove earlier delegate registration.") + } + + return maybeDataSource! + } + + private func rx_checkCollectionViewDelegate() -> CollectionViewDelegate? { + MainScheduler.ensureExecutingOnScheduler() + + if self.delegate == nil { + return nil + } + + let maybeDelegate = self.delegate as? CollectionViewDelegate + + if maybeDelegate == nil { + rxFatalError("View already has incompatible delegate set. To use rx observable (for now) please remove earlier delegate registration.") + } + + return maybeDelegate! + } +} \ No newline at end of file diff --git a/RxCocoa/RxCocoa/UI+Rx/UIImageView+Rx.swift b/RxCocoa/RxCocoa/UI+Rx/UIImageView+Rx.swift new file mode 100644 index 00000000..94c297fd --- /dev/null +++ b/RxCocoa/RxCocoa/UI+Rx/UIImageView+Rx.swift @@ -0,0 +1,46 @@ +// +// UIImageView+Rx.swift +// RxCocoa +// +// Created by Krunoslav Zaher on 4/1/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx +import UIKit + +extension UIImageView { + public func rx_subscribeImageTo(source: Observable) -> Result { + return rx_subscribeImageTo(false)(source: source) + } + + public func rx_subscribeImageTo + (animated: Bool) + (source: Observable) -> Result { + return source.subscribe(ObserverOf(AnonymousObserver { event in + switch event { + case .Next(let boxedValue): + let value = boxedValue.value + if animated && value != nil { + let transition = CATransition() + transition.duration = 0.25 + transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + transition.type = kCATransitionFade + self.layer.addAnimation(transition, forKey: kCATransition) + } + else { + self.layer.removeAllAnimations() + } + self.image = value + case .Error(let error): + rxFatalError("Binding error to textbox: \(error)") + break + case .Completed: + break + } + + return SuccessResult + })) + } +} \ No newline at end of file diff --git a/RxCocoa/RxCocoa/UI+Rx/UILabel+Rx.swift b/RxCocoa/RxCocoa/UI+Rx/UILabel+Rx.swift new file mode 100644 index 00000000..26c1cf0a --- /dev/null +++ b/RxCocoa/RxCocoa/UI+Rx/UILabel+Rx.swift @@ -0,0 +1,30 @@ +// +// UILabel+Rx.swift +// RxCocoa +// +// Created by Krunoslav Zaher on 4/1/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx +import UIKit + +extension UILabel { + public func rx_subscribeTextTo(source: Observable) -> Result { + return source.subscribe(ObserverOf(AnonymousObserver { event in + switch event { + case .Next(let boxedValue): + let value = boxedValue.value + self.text = value + case .Error(let error): + rxFatalError("Binding error to textbox: \(error)") + break + case .Completed: + break + } + + return SuccessResult + })) + } +} \ No newline at end of file diff --git a/RxCocoa/RxCocoa/UI+Rx/UIScrollView+Rx.swift b/RxCocoa/RxCocoa/UI+Rx/UIScrollView+Rx.swift new file mode 100644 index 00000000..e3df02a2 --- /dev/null +++ b/RxCocoa/RxCocoa/UI+Rx/UIScrollView+Rx.swift @@ -0,0 +1,100 @@ +// +// UIScrollView+Rx.swift +// RxCocoa +// +// Created by Krunoslav Zaher on 4/3/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +public class ScrollViewDelegate: NSObject, UIScrollViewDelegate { + public typealias ScrollViewObserver = ObserverOf + + public typealias ScrollViewDisposeKey = Bag.KeyType + + var scrollViewObsevers: Bag + + override public init() { + scrollViewObsevers = Bag() + } + + public func addScrollViewObserver(observer: ScrollViewObserver) -> ScrollViewDisposeKey { + MainScheduler.ensureExecutingOnScheduler() + + return scrollViewObsevers.put(observer) + } + + public func removeScrollViewObserver(key: ScrollViewDisposeKey) { + MainScheduler.ensureExecutingOnScheduler() + + let element = scrollViewObsevers.removeKey(key) + if element == nil { + removingObserverFailed() + } + } + + public func scrollViewDidScroll(scrollView: UIScrollView) { + let event = Event.Next(Box(scrollView.contentOffset)) + + handleObserverResult(dispatch(event, scrollViewObsevers.all)) + } + + deinit { + if scrollViewObsevers.count > 0 { + handleVoidObserverResult(.Error(rxError(RxCocoaError.InvalidOperation, "Something went wrong. Deallocating scroll delegate while there are still subscribed observers means that some subscription was left undisposed."))) + } + } +} + +extension UIScrollView { + func rx_createDelegate() -> ScrollViewDelegate { + return ScrollViewDelegate() + } + + public func rx_observableContentOffset() -> Observable { + _ = rx_checkScrollViewDelegate() + + return AnonymousObservable { observer in + var maybeDelegate = self.rx_checkScrollViewDelegate() + + if maybeDelegate == nil { + let delegate = self.rx_createDelegate() as ScrollViewDelegate + maybeDelegate = delegate + self.delegate = maybeDelegate + } + + let delegate = maybeDelegate! + + let key = delegate.addScrollViewObserver(observer) + + return success(AnonymousDisposable { + _ = self.rx_checkScrollViewDelegate() + + delegate.removeScrollViewObserver(key) + + if delegate.scrollViewObsevers.count == 0 { + self.delegate = nil + } + }) + } + } + + // private + private func rx_checkScrollViewDelegate() -> ScrollViewDelegate? { + MainScheduler.ensureExecutingOnScheduler() + + if self.delegate == nil { + return nil + } + + let maybeDelegate = self.delegate as? ScrollViewDelegate + + if maybeDelegate == nil { + rxFatalError("View already has incompatible delegate set. To use rx observable (for now) please remove earlier delegate registration.") + } + + return maybeDelegate! + } +} \ No newline at end of file diff --git a/RxCocoa/RxCocoa/UI+Rx/UISearchBar+Rx.swift b/RxCocoa/RxCocoa/UI+Rx/UISearchBar+Rx.swift new file mode 100644 index 00000000..46e32c48 --- /dev/null +++ b/RxCocoa/RxCocoa/UI+Rx/UISearchBar+Rx.swift @@ -0,0 +1,82 @@ +// +// UISearchBar+Rx.swift +// RxCocoa +// +// Created by Krunoslav Zaher on 3/28/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +class SearchBarDelegate: NSObject, UISearchBarDelegate { + typealias Observer = ObserverOf + typealias DisposeKey = Bag>.KeyType + + var observers: Bag = Bag() + + func addObserver(observer: Observer) -> DisposeKey { + MainScheduler.ensureExecutingOnScheduler() + + return observers.put(observer) + } + + func removeObserver(key: DisposeKey) { + MainScheduler.ensureExecutingOnScheduler() + + let observer = observers.removeKey(key) + + if observer == nil { + removingObserverFailed() + } + } + + func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { + let event = Event.Next(Box(searchText)) + + handleObserverResult(dispatch(event, self.observers.all)) + } +} + +extension UISearchBar { + + public func rx_observableSearchText() -> Observable { + + rx_checkSearchBarDelegate() + + return AnonymousObservable { observer in + var maybeDelegate = self.rx_checkSearchBarDelegate() + + if maybeDelegate == nil { + maybeDelegate = SearchBarDelegate() + self.delegate = maybeDelegate + } + + let delegate = maybeDelegate! + + let key = delegate.addObserver(observer) + + return success(AnonymousDisposable { + delegate.removeObserver(key) + }) + } + } + + // private + + private func rx_checkSearchBarDelegate() -> SearchBarDelegate? { + MainScheduler.ensureExecutingOnScheduler() + + if self.delegate == nil { + return nil + } + + let maybeDelegate = self.delegate as? SearchBarDelegate + + if maybeDelegate == nil { + rxFatalError("Search bar already has incompatible delegate set. To use rx observable (for now) please remove earlier delegate registration.") + } + + return maybeDelegate! + } +} \ No newline at end of file diff --git a/RxCocoa/RxCocoa/UI+Rx/UITableView+Rx.swift b/RxCocoa/RxCocoa/UI+Rx/UITableView+Rx.swift new file mode 100644 index 00000000..be9ccd70 --- /dev/null +++ b/RxCocoa/RxCocoa/UI+Rx/UITableView+Rx.swift @@ -0,0 +1,259 @@ +// +// UITableView+Rx.swift +// RxCocoa +// +// Created by Krunoslav Zaher on 4/2/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +// This cannot be a generic class because of table view objc runtime that checks for +// implemented selectors in data source +public class TableViewDataSource : NSObject, UITableViewDataSource { + public typealias CellFactory = (UITableView, NSIndexPath, AnyObject) -> UITableViewCell + + public var rows: [AnyObject] { + get { + return _rows + } + } + + var _rows: [AnyObject] + + let cellFactory: CellFactory + + public init(cellFactory: CellFactory) { + self._rows = [] + self.cellFactory = cellFactory + } + + public func numberOfSectionsInTableView(tableView: UITableView) -> Int { + return 1 + } + + public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return _rows.count + } + + public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + if indexPath.row < _rows.count { + let row = indexPath.row + return cellFactory(tableView, indexPath, self._rows[row]) + } + else { + rxFatalError("something went wrong") + let cell: UITableViewCell? = nil + return cell! + } + } +} + +public class TableViewDelegate: ScrollViewDelegate, UITableViewDelegate { + public typealias Observer = ObserverOf<(UITableView, Int)> + public typealias DisposeKey = Bag.KeyType + + var tableViewObservers: Bag + + override public init() { + tableViewObservers = Bag() + } + + public func addTableViewObserver(observer: Observer) -> DisposeKey { + MainScheduler.ensureExecutingOnScheduler() + + return tableViewObservers.put(observer) + } + + public func removeTableViewObserver(key: DisposeKey) { + MainScheduler.ensureExecutingOnScheduler() + + let element = tableViewObservers.removeKey(key) + if element == nil { + removingObserverFailed() + } + } + + public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + tableView.deselectRowAtIndexPath(indexPath, animated: true) + + let event = Event.Next(Box((tableView, indexPath.row))) + + handleObserverResult(dispatch(event, tableViewObservers.all)) + } + + deinit { + if tableViewObservers.count > 0 { + handleVoidObserverResult(.Error(rxError(RxCocoaError.InvalidOperation, "Something went wrong. Deallocating table view delegate while there are still subscribed observers means that some subscription was left undisposed."))) + } + } +} + +// This is the most simple (but probably most common) way of using rx with UITableView. +extension UITableView { + override func rx_createDelegate() -> ScrollViewDelegate { + return TableViewDelegate() + } + + public func rx_subscribeRowsTo + (dataSource: TableViewDataSource) + (source: Observable<[E]>) + -> Result { + + MainScheduler.ensureExecutingOnScheduler() + + if self.dataSource != nil && self.dataSource !== dataSource { + rxFatalError("Data source is different") + } + + self.dataSource = dataSource + + let clearDataSource = AnonymousDisposable { + if self.dataSource != nil && self.dataSource !== dataSource { + rxFatalError("Data source is different") + } + + self.dataSource = nil + } + + return source.subscribe(ObserverOf(AnonymousObserver { event in + switch event { + case .Next(let boxedValue): + let value = boxedValue.value + dataSource._rows = value + self.reloadData() + case .Error(let error): + rxFatalError("Something went wrong: \(error)") + case .Completed: + break + } + + return SuccessResult + })) >== { disposable in + return success(CompositeDisposable(clearDataSource, disposable)) + } >>! { e in + clearDataSource.dispose() + return .Error(e) + } + } + + public func rx_subscribeRowsTo + (cellFactory: (UITableView, NSIndexPath, E) -> UITableViewCell) + (source: Observable<[E]>) + -> Result { + + let dataSource = TableViewDataSource { + cellFactory($0, $1, $2 as! E) + } + + return self.rx_subscribeRowsTo(dataSource)(source: source) + } + + public func rx_subscribeRowsToCellWithIdentifier + (cellIdentifier: String, configureCell: (UITableView, NSIndexPath, E, Cell) -> Void) + (source: Observable<[E]>) + -> Result { + + let dataSource = TableViewDataSource { + let cell = $0.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: $1) as! Cell + configureCell($0, $1, $2 as! E, cell) + return cell + } + + return self.rx_subscribeRowsTo(dataSource)(source: source) + } + + public func rx_observableRowTap() -> Observable<(UITableView, Int)> { + _ = rx_checkTableViewDelegate() + + return AnonymousObservable { observer in + var maybeDelegate = self.rx_checkTableViewDelegate() + + if maybeDelegate == nil { + let delegate = self.rx_createDelegate() as! TableViewDelegate + maybeDelegate = delegate + self.delegate = maybeDelegate + } + + let delegate = maybeDelegate! + + let key = delegate.addTableViewObserver(observer) + + return success(AnonymousDisposable { + _ = self.rx_checkTableViewDelegate() + + delegate.removeTableViewObserver(key) + + if delegate.tableViewObservers.count == 0 { + self.delegate = nil + } + }) + } + } + + public func rx_observableElementTap() -> Observable { + + return rx_observableRowTap() >- Rx.map { (tableView, rowIndex) -> E in + let maybeDataSource: TableViewDataSource? = self.rx_getTableViewDataSource() + + if maybeDataSource == nil { + rxFatalError("To use element tap table view needs to use table view data source. You can still use `rx_observableRowTap`.") + } + + let dataSource = maybeDataSource! + + return dataSource.rows[rowIndex] as! E + } + } + + // private methods + + private func rx_getTableViewDataSource() -> TableViewDataSource? { + MainScheduler.ensureExecutingOnScheduler() + + if self.dataSource == nil { + return nil + } + + let maybeDataSource = self.dataSource as? TableViewDataSource + + if maybeDataSource == nil { + rxFatalError("View already has incompatible data source set. Please remove earlier delegate registration.") + } + + return maybeDataSource! + } + + private func rx_checkTableViewDataSource() -> TableViewDataSource? { + MainScheduler.ensureExecutingOnScheduler() + + if self.dataSource == nil { + return nil + } + + let maybeDataSource = self.dataSource as? TableViewDataSource + + if maybeDataSource == nil { + rxFatalError("View already has incompatible data source set. Please remove earlier delegate registration.") + } + + return maybeDataSource! + } + + private func rx_checkTableViewDelegate() -> TableViewDelegate? { + MainScheduler.ensureExecutingOnScheduler() + + if self.delegate == nil { + return nil + } + + let maybeDelegate = self.delegate as? TableViewDelegate + + if maybeDelegate == nil { + rxFatalError("View already has incompatible delegate set. To use rx observable (for now) please remove earlier delegate registration.") + } + + return maybeDelegate! + } +} \ No newline at end of file diff --git a/RxCocoa/RxCocoa/UI+Rx/UITextField+Rx.swift b/RxCocoa/RxCocoa/UI+Rx/UITextField+Rx.swift new file mode 100644 index 00000000..536afda5 --- /dev/null +++ b/RxCocoa/RxCocoa/UI+Rx/UITextField+Rx.swift @@ -0,0 +1,24 @@ +// +// UITextField+Rx.swift +// RxCocoa +// +// Created by Krunoslav Zaher on 2/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +extension UITextField { + public func rx_observableText() -> Observable { + return AnonymousObservable { subscriber in + let observer = ControlTarget(control: self, controlEvents: UIControlEvents.EditingChanged) { control in + let text: String = (control as! UITextField).text + let result = subscriber.on(.Next(Box(text))) + handleObserverResult(result) + } + + return success(observer) + } + } +} \ No newline at end of file diff --git a/RxCocoa/RxCocoaTests/Info.plist b/RxCocoa/RxCocoaTests/Info.plist new file mode 100644 index 00000000..7f80ab77 --- /dev/null +++ b/RxCocoa/RxCocoaTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + Krunoslav-Zaher.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/RxTests/RxTests.swift b/RxCocoa/RxCocoaTests/RxCocoaTests.swift similarity index 87% rename from RxTests/RxTests.swift rename to RxCocoa/RxCocoaTests/RxCocoaTests.swift index bf239e05..99b7e21e 100644 --- a/RxTests/RxTests.swift +++ b/RxCocoa/RxCocoaTests/RxCocoaTests.swift @@ -1,15 +1,15 @@ // -// RxTests.swift -// RxTests +// RxCocoaTests.swift +// RxCocoaTests // -// Created by Krunoslav Zaher on 2/8/15. +// Created by Krunoslav Zaher on 2/21/15. // Copyright (c) 2015 Krunoslav Zaher. All rights reserved. // import UIKit import XCTest -class RxTests: XCTestCase { +class RxCocoaTests: XCTestCase { override func setUp() { super.setUp() diff --git a/RxExample/RxExample.xcodeproj/project.pbxproj b/RxExample/RxExample.xcodeproj/project.pbxproj new file mode 100644 index 00000000..6ca68495 --- /dev/null +++ b/RxExample/RxExample.xcodeproj/project.pbxproj @@ -0,0 +1,532 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + C83366ED1AD0293800C668A7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C83366EC1AD0293800C668A7 /* Images.xcassets */; }; + C83366FC1AD0293900C668A7 /* RxExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83366FB1AD0293900C668A7 /* RxExampleTests.swift */; }; + C83367221AD029AE00C668A7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C833670E1AD029AE00C668A7 /* AppDelegate.swift */; }; + C83367231AD029AE00C668A7 /* Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = C833670F1AD029AE00C668A7 /* Example.swift */; }; + C83367241AD029AE00C668A7 /* HtmlParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83367111AD029AE00C668A7 /* HtmlParsing.swift */; }; + C83367251AD029AE00C668A7 /* ImageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83367121AD029AE00C668A7 /* ImageService.swift */; }; + C83367261AD029AE00C668A7 /* WikipediaAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83367141AD029AE00C668A7 /* WikipediaAPI.swift */; }; + C83367271AD029AE00C668A7 /* WikipediaPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83367151AD029AE00C668A7 /* WikipediaPage.swift */; }; + C83367281AD029AE00C668A7 /* WikipediaSearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83367161AD029AE00C668A7 /* WikipediaSearchResult.swift */; }; + C833672A1AD029AE00C668A7 /* SearchResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83367191AD029AE00C668A7 /* SearchResultViewModel.swift */; }; + C833672B1AD029AE00C668A7 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C833671A1AD029AE00C668A7 /* SearchViewModel.swift */; }; + C833672C1AD029AE00C668A7 /* CollectionViewImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C833671C1AD029AE00C668A7 /* CollectionViewImageCell.swift */; }; + C833672D1AD029AE00C668A7 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = C833671D1AD029AE00C668A7 /* LaunchScreen.xib */; }; + C833672E1AD029AE00C668A7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C833671E1AD029AE00C668A7 /* Main.storyboard */; }; + C833672F1AD029AE00C668A7 /* WikipediaSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C833671F1AD029AE00C668A7 /* WikipediaSearchViewController.swift */; }; + C83367301AD029AE00C668A7 /* WikipediaSearchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83367201AD029AE00C668A7 /* WikipediaSearchCell.swift */; }; + C83367311AD029AE00C668A7 /* Wireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83367211AD029AE00C668A7 /* Wireframe.swift */; }; + C8813DAA1AD2EBF30072A050 /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8813DA91AD2EBF30072A050 /* RootViewController.swift */; }; + C8813DAE1AD2F5A40072A050 /* WikipediaSearchCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C8813DAD1AD2F5A40072A050 /* WikipediaSearchCell.xib */; }; + C8813DB01AD2F8800072A050 /* WikipediaImageCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C8813DAF1AD2F8800072A050 /* WikipediaImageCell.xib */; }; + C8F57F411AD02DBE006B8702 /* Rx.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8F57F401AD02DBE006B8702 /* Rx.framework */; }; + C8F57F421AD02DBE006B8702 /* Rx.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C8F57F401AD02DBE006B8702 /* Rx.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C8F57F441AD02DBE006B8702 /* RxCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8F57F431AD02DBE006B8702 /* RxCocoa.framework */; }; + C8F57F451AD02DBE006B8702 /* RxCocoa.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C8F57F431AD02DBE006B8702 /* RxCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + C83366F61AD0293900C668A7 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C83366D51AD0293800C668A7 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C83366DC1AD0293800C668A7; + remoteInfo = RxExample; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + C83367351AD029C700C668A7 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + C8F57F451AD02DBE006B8702 /* RxCocoa.framework in Embed Frameworks */, + C8F57F421AD02DBE006B8702 /* Rx.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + C83366DD1AD0293800C668A7 /* RxExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RxExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + C83366E11AD0293800C668A7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C83366EC1AD0293800C668A7 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + C83366F51AD0293900C668A7 /* RxExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RxExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + C83366FA1AD0293900C668A7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C83366FB1AD0293900C668A7 /* RxExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxExampleTests.swift; sourceTree = ""; }; + C833670E1AD029AE00C668A7 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + C833670F1AD029AE00C668A7 /* Example.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Example.swift; sourceTree = ""; }; + C83367111AD029AE00C668A7 /* HtmlParsing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HtmlParsing.swift; sourceTree = ""; }; + C83367121AD029AE00C668A7 /* ImageService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageService.swift; sourceTree = ""; }; + C83367141AD029AE00C668A7 /* WikipediaAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WikipediaAPI.swift; sourceTree = ""; }; + C83367151AD029AE00C668A7 /* WikipediaPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WikipediaPage.swift; sourceTree = ""; }; + C83367161AD029AE00C668A7 /* WikipediaSearchResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WikipediaSearchResult.swift; sourceTree = ""; }; + C83367191AD029AE00C668A7 /* SearchResultViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchResultViewModel.swift; sourceTree = ""; }; + C833671A1AD029AE00C668A7 /* SearchViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = ""; }; + C833671C1AD029AE00C668A7 /* CollectionViewImageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewImageCell.swift; sourceTree = ""; }; + C833671D1AD029AE00C668A7 /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; + C833671E1AD029AE00C668A7 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; + C833671F1AD029AE00C668A7 /* WikipediaSearchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WikipediaSearchViewController.swift; sourceTree = ""; }; + C83367201AD029AE00C668A7 /* WikipediaSearchCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WikipediaSearchCell.swift; sourceTree = ""; }; + C83367211AD029AE00C668A7 /* Wireframe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Wireframe.swift; sourceTree = ""; }; + C8813DA91AD2EBF30072A050 /* RootViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootViewController.swift; sourceTree = ""; }; + C8813DAD1AD2F5A40072A050 /* WikipediaSearchCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = WikipediaSearchCell.xib; sourceTree = ""; }; + C8813DAF1AD2F8800072A050 /* WikipediaImageCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = WikipediaImageCell.xib; sourceTree = ""; }; + C8F57F401AD02DBE006B8702 /* Rx.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Rx.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C8F57F431AD02DBE006B8702 /* RxCocoa.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RxCocoa.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + C83366DA1AD0293800C668A7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C8F57F441AD02DBE006B8702 /* RxCocoa.framework in Frameworks */, + C8F57F411AD02DBE006B8702 /* Rx.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C83366F21AD0293900C668A7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + C83366D41AD0293800C668A7 = { + isa = PBXGroup; + children = ( + C8F57F431AD02DBE006B8702 /* RxCocoa.framework */, + C8F57F401AD02DBE006B8702 /* Rx.framework */, + C83366DF1AD0293800C668A7 /* RxExample */, + C83366F81AD0293900C668A7 /* RxExampleTests */, + C83366DE1AD0293800C668A7 /* Products */, + ); + sourceTree = ""; + }; + C83366DE1AD0293800C668A7 /* Products */ = { + isa = PBXGroup; + children = ( + C83366DD1AD0293800C668A7 /* RxExample.app */, + C83366F51AD0293900C668A7 /* RxExampleTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + C83366DF1AD0293800C668A7 /* RxExample */ = { + isa = PBXGroup; + children = ( + C833670E1AD029AE00C668A7 /* AppDelegate.swift */, + C833670F1AD029AE00C668A7 /* Example.swift */, + C83367101AD029AE00C668A7 /* Services */, + C83367181AD029AE00C668A7 /* ViewModels */, + C833671B1AD029AE00C668A7 /* Views */, + C83367211AD029AE00C668A7 /* Wireframe.swift */, + C83366EC1AD0293800C668A7 /* Images.xcassets */, + C83366E01AD0293800C668A7 /* Supporting Files */, + ); + path = RxExample; + sourceTree = ""; + }; + C83366E01AD0293800C668A7 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + C83366E11AD0293800C668A7 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + C83366F81AD0293900C668A7 /* RxExampleTests */ = { + isa = PBXGroup; + children = ( + C83366FB1AD0293900C668A7 /* RxExampleTests.swift */, + C83366F91AD0293900C668A7 /* Supporting Files */, + ); + path = RxExampleTests; + sourceTree = ""; + }; + C83366F91AD0293900C668A7 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + C83366FA1AD0293900C668A7 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + C83367101AD029AE00C668A7 /* Services */ = { + isa = PBXGroup; + children = ( + C83367111AD029AE00C668A7 /* HtmlParsing.swift */, + C83367121AD029AE00C668A7 /* ImageService.swift */, + C83367131AD029AE00C668A7 /* WikipediaAPI */, + ); + path = Services; + sourceTree = ""; + }; + C83367131AD029AE00C668A7 /* WikipediaAPI */ = { + isa = PBXGroup; + children = ( + C83367141AD029AE00C668A7 /* WikipediaAPI.swift */, + C83367151AD029AE00C668A7 /* WikipediaPage.swift */, + C83367161AD029AE00C668A7 /* WikipediaSearchResult.swift */, + ); + path = WikipediaAPI; + sourceTree = ""; + }; + C83367181AD029AE00C668A7 /* ViewModels */ = { + isa = PBXGroup; + children = ( + C83367191AD029AE00C668A7 /* SearchResultViewModel.swift */, + C833671A1AD029AE00C668A7 /* SearchViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + C833671B1AD029AE00C668A7 /* Views */ = { + isa = PBXGroup; + children = ( + C833671C1AD029AE00C668A7 /* CollectionViewImageCell.swift */, + C833671D1AD029AE00C668A7 /* LaunchScreen.xib */, + C833671E1AD029AE00C668A7 /* Main.storyboard */, + C833671F1AD029AE00C668A7 /* WikipediaSearchViewController.swift */, + C83367201AD029AE00C668A7 /* WikipediaSearchCell.swift */, + C8813DA91AD2EBF30072A050 /* RootViewController.swift */, + C8813DAD1AD2F5A40072A050 /* WikipediaSearchCell.xib */, + C8813DAF1AD2F8800072A050 /* WikipediaImageCell.xib */, + ); + path = Views; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + C83366DC1AD0293800C668A7 /* RxExample */ = { + isa = PBXNativeTarget; + buildConfigurationList = C83366FF1AD0293900C668A7 /* Build configuration list for PBXNativeTarget "RxExample" */; + buildPhases = ( + C83366D91AD0293800C668A7 /* Sources */, + C83366DA1AD0293800C668A7 /* Frameworks */, + C83366DB1AD0293800C668A7 /* Resources */, + C83367351AD029C700C668A7 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RxExample; + productName = RxExample; + productReference = C83366DD1AD0293800C668A7 /* RxExample.app */; + productType = "com.apple.product-type.application"; + }; + C83366F41AD0293900C668A7 /* RxExampleTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = C83367021AD0293900C668A7 /* Build configuration list for PBXNativeTarget "RxExampleTests" */; + buildPhases = ( + C83366F11AD0293900C668A7 /* Sources */, + C83366F21AD0293900C668A7 /* Frameworks */, + C83366F31AD0293900C668A7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + C83366F71AD0293900C668A7 /* PBXTargetDependency */, + ); + name = RxExampleTests; + productName = RxExampleTests; + productReference = C83366F51AD0293900C668A7 /* RxExampleTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + C83366D51AD0293800C668A7 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0620; + ORGANIZATIONNAME = "Krunoslav Zaher"; + TargetAttributes = { + C83366DC1AD0293800C668A7 = { + CreatedOnToolsVersion = 6.2; + }; + C83366F41AD0293900C668A7 = { + CreatedOnToolsVersion = 6.2; + TestTargetID = C83366DC1AD0293800C668A7; + }; + }; + }; + buildConfigurationList = C83366D81AD0293800C668A7 /* Build configuration list for PBXProject "RxExample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = C83366D41AD0293800C668A7; + productRefGroup = C83366DE1AD0293800C668A7 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C83366DC1AD0293800C668A7 /* RxExample */, + C83366F41AD0293900C668A7 /* RxExampleTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + C83366DB1AD0293800C668A7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C833672E1AD029AE00C668A7 /* Main.storyboard in Resources */, + C8813DAE1AD2F5A40072A050 /* WikipediaSearchCell.xib in Resources */, + C833672D1AD029AE00C668A7 /* LaunchScreen.xib in Resources */, + C8813DB01AD2F8800072A050 /* WikipediaImageCell.xib in Resources */, + C83366ED1AD0293800C668A7 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C83366F31AD0293900C668A7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + C83366D91AD0293800C668A7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C83367271AD029AE00C668A7 /* WikipediaPage.swift in Sources */, + C83367241AD029AE00C668A7 /* HtmlParsing.swift in Sources */, + C83367251AD029AE00C668A7 /* ImageService.swift in Sources */, + C83367261AD029AE00C668A7 /* WikipediaAPI.swift in Sources */, + C83367281AD029AE00C668A7 /* WikipediaSearchResult.swift in Sources */, + C83367231AD029AE00C668A7 /* Example.swift in Sources */, + C833672F1AD029AE00C668A7 /* WikipediaSearchViewController.swift in Sources */, + C833672C1AD029AE00C668A7 /* CollectionViewImageCell.swift in Sources */, + C83367311AD029AE00C668A7 /* Wireframe.swift in Sources */, + C833672B1AD029AE00C668A7 /* SearchViewModel.swift in Sources */, + C8813DAA1AD2EBF30072A050 /* RootViewController.swift in Sources */, + C83367301AD029AE00C668A7 /* WikipediaSearchCell.swift in Sources */, + C833672A1AD029AE00C668A7 /* SearchResultViewModel.swift in Sources */, + C83367221AD029AE00C668A7 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C83366F11AD0293900C668A7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C83366FC1AD0293900C668A7 /* RxExampleTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + C83366F71AD0293900C668A7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C83366DC1AD0293800C668A7 /* RxExample */; + targetProxy = C83366F61AD0293900C668A7 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + C83366FD1AD0293900C668A7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C83366FE1AD0293900C668A7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + C83367001AD0293900C668A7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/Rx-cfkyozdvlaegqibzixjokeysigeo/Build/Products/Release-iphoneos", + "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/Rx-cfkyozdvlaegqibzixjokeysigeo/Build/Products/Debug-iphoneos", + ); + INFOPLIST_FILE = RxExample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = "-objc_loadall"; + OTHER_SWIFT_FLAGS = "-D DEBUG"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + C83367011AD0293900C668A7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/Rx-cfkyozdvlaegqibzixjokeysigeo/Build/Products/Release-iphoneos", + "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/Rx-cfkyozdvlaegqibzixjokeysigeo/Build/Products/Debug-iphoneos", + ); + INFOPLIST_FILE = RxExample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = "-objc_loadall"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + C83367031AD0293900C668A7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = RxExampleTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RxExample.app/RxExample"; + }; + name = Debug; + }; + C83367041AD0293900C668A7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = RxExampleTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RxExample.app/RxExample"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C83366D81AD0293800C668A7 /* Build configuration list for PBXProject "RxExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C83366FD1AD0293900C668A7 /* Debug */, + C83366FE1AD0293900C668A7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C83366FF1AD0293900C668A7 /* Build configuration list for PBXNativeTarget "RxExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C83367001AD0293900C668A7 /* Debug */, + C83367011AD0293900C668A7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C83367021AD0293900C668A7 /* Build configuration list for PBXNativeTarget "RxExampleTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C83367031AD0293900C668A7 /* Debug */, + C83367041AD0293900C668A7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = C83366D51AD0293800C668A7 /* Project object */; +} diff --git a/RxExample/RxExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/RxExample/RxExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..89c96f27 --- /dev/null +++ b/RxExample/RxExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/RxExample/RxExample.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/RxExample.xcscheme b/RxExample/RxExample.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/RxExample.xcscheme new file mode 100644 index 00000000..75a4d0fd --- /dev/null +++ b/RxExample/RxExample.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/RxExample.xcscheme @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RxExample/RxExample.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/xcschememanagement.plist b/RxExample/RxExample.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000..91195bff --- /dev/null +++ b/RxExample/RxExample.xcodeproj/xcuserdata/kzaher.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,27 @@ + + + + + SchemeUserState + + RxExample.xcscheme + + orderHint + 0 + + + SuppressBuildableAutocreation + + C83366DC1AD0293800C668A7 + + primary + + + C83366F41AD0293900C668A7 + + primary + + + + + diff --git a/RxExample/RxExample/AppDelegate.swift b/RxExample/RxExample/AppDelegate.swift new file mode 100644 index 00000000..e674ce19 --- /dev/null +++ b/RxExample/RxExample/AppDelegate.swift @@ -0,0 +1,22 @@ +// +// AppDelegate.swift +// Example +// +// Created by Krunoslav Zaher on 2/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import UIKit +import CoreData + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + // Override point for customization after application launch. + return true + } +} + diff --git a/RxExample/RxExample/Example.swift b/RxExample/RxExample/Example.swift new file mode 100644 index 00000000..d2cdfbcb --- /dev/null +++ b/RxExample/RxExample/Example.swift @@ -0,0 +1,11 @@ +// +// Example.swift +// Example +// +// Created by Krunoslav Zaher on 3/28/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +let MB = 1024 * 1024 \ No newline at end of file diff --git a/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Contents.json b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..f391fddd --- /dev/null +++ b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,134 @@ +{ + "images" : [ + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-Small@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-Small@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-60@2x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-60@2x-1.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-60@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-Small.png", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-40.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-40@2x-1.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-76.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-76@2x.png", + "scale" : "2x" + }, + { + "size" : "24x24", + "idiom" : "watch", + "scale" : "2x", + "role" : "notificationCenter", + "subtype" : "38mm" + }, + { + "size" : "27.5x27.5", + "idiom" : "watch", + "scale" : "2x", + "role" : "notificationCenter", + "subtype" : "42mm" + }, + { + "size" : "29x29", + "idiom" : "watch", + "filename" : "Icon-Small@2x-1.png", + "role" : "companionSettings", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "watch", + "scale" : "2x", + "role" : "appLauncher", + "subtype" : "38mm" + }, + { + "size" : "44x44", + "idiom" : "watch", + "scale" : "2x", + "role" : "longLook", + "subtype" : "42mm" + }, + { + "size" : "86x86", + "idiom" : "watch", + "scale" : "2x", + "role" : "quickLook", + "subtype" : "38mm" + }, + { + "size" : "98x98", + "idiom" : "watch", + "scale" : "2x", + "role" : "quickLook", + "subtype" : "42mm" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-40.png b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-40.png new file mode 100644 index 00000000..9f76615b Binary files /dev/null and b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-40.png differ diff --git a/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-40@2x-1.png b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-40@2x-1.png new file mode 100644 index 00000000..925be220 Binary files /dev/null and b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-40@2x-1.png differ diff --git a/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png new file mode 100644 index 00000000..925be220 Binary files /dev/null and b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png differ diff --git a/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-60@2x-1.png b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-60@2x-1.png new file mode 100644 index 00000000..60a333ac Binary files /dev/null and b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-60@2x-1.png differ diff --git a/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png new file mode 100644 index 00000000..60a333ac Binary files /dev/null and b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png differ diff --git a/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png new file mode 100644 index 00000000..a70d9277 Binary files /dev/null and b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png differ diff --git a/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-76.png b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-76.png new file mode 100644 index 00000000..397007fd Binary files /dev/null and b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-76.png differ diff --git a/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png new file mode 100644 index 00000000..d62b4ebf Binary files /dev/null and b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png differ diff --git a/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-Small.png b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-Small.png new file mode 100644 index 00000000..73e2af14 Binary files /dev/null and b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-Small.png differ diff --git a/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png new file mode 100644 index 00000000..adde9610 Binary files /dev/null and b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png differ diff --git a/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png new file mode 100644 index 00000000..adde9610 Binary files /dev/null and b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png differ diff --git a/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png new file mode 100644 index 00000000..06ef9ffc Binary files /dev/null and b/RxExample/RxExample/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png differ diff --git a/RxExample/RxExample/Images.xcassets/ReactiveExtensionsLogo.imageset/Contents.json b/RxExample/RxExample/Images.xcassets/ReactiveExtensionsLogo.imageset/Contents.json new file mode 100644 index 00000000..a75b4e92 --- /dev/null +++ b/RxExample/RxExample/Images.xcassets/ReactiveExtensionsLogo.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "ReactiveExtensionsLogo.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "ReactiveExtensionsLogo-2.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/RxExample/RxExample/Images.xcassets/ReactiveExtensionsLogo.imageset/ReactiveExtensionsLogo-2.png b/RxExample/RxExample/Images.xcassets/ReactiveExtensionsLogo.imageset/ReactiveExtensionsLogo-2.png new file mode 100644 index 00000000..b4066e8a Binary files /dev/null and b/RxExample/RxExample/Images.xcassets/ReactiveExtensionsLogo.imageset/ReactiveExtensionsLogo-2.png differ diff --git a/RxExample/RxExample/Images.xcassets/ReactiveExtensionsLogo.imageset/ReactiveExtensionsLogo.png b/RxExample/RxExample/Images.xcassets/ReactiveExtensionsLogo.imageset/ReactiveExtensionsLogo.png new file mode 100644 index 00000000..b4066e8a Binary files /dev/null and b/RxExample/RxExample/Images.xcassets/ReactiveExtensionsLogo.imageset/ReactiveExtensionsLogo.png differ diff --git a/RxExample/RxExample/Info.plist b/RxExample/RxExample/Info.plist new file mode 100644 index 00000000..cc02affe --- /dev/null +++ b/RxExample/RxExample/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + Krunoslav-Zaher.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/RxExample/RxExample/RxExample.xcdatamodeld/.xccurrentversion b/RxExample/RxExample/RxExample.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..0c67376e --- /dev/null +++ b/RxExample/RxExample/RxExample.xcdatamodeld/.xccurrentversion @@ -0,0 +1,5 @@ + + + + + diff --git a/RxExample/RxExample/RxExample.xcdatamodeld/RxExample.xcdatamodel/contents b/RxExample/RxExample/RxExample.xcdatamodeld/RxExample.xcdatamodel/contents new file mode 100644 index 00000000..193f33c9 --- /dev/null +++ b/RxExample/RxExample/RxExample.xcdatamodeld/RxExample.xcdatamodel/contents @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/RxExample/RxExample/Services/HtmlParsing.swift b/RxExample/RxExample/Services/HtmlParsing.swift new file mode 100644 index 00000000..722bc42e --- /dev/null +++ b/RxExample/RxExample/Services/HtmlParsing.swift @@ -0,0 +1,41 @@ +// +// HtmlParsing.swift +// Example +// +// Created by Krunoslav Zaher on 3/28/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +func parseImageURLsfromHTML(html: NSString) -> [NSURL] { + let regularExpression = NSRegularExpression(pattern: "]*src=\"([^\"]+)\"[^>]*>", options: NSRegularExpressionOptions.allZeros, error: nil)! + + let matches = regularExpression.matchesInString(html as! String, options: NSMatchingOptions.allZeros, range: NSMakeRange(0, html.length)) as! [NSTextCheckingResult] + + return matches.map { match -> NSURL? in + if match.numberOfRanges != 2 { + return nil + } + + let url = html.substringWithRange(match.rangeAtIndex(1)) + + var absoluteURLString = url + if url.hasPrefix("//") { + absoluteURLString = "http:" + url + } + + return NSURL(string: absoluteURLString) + }.filter { $0 != nil }.map { $0! } +} + +func parseImageURLsfromHTMLSuitableForDisplay(html: NSString) -> [NSURL] { + return parseImageURLsfromHTML(html).filter { + if let absoluteString = $0.absoluteString { + return absoluteString.rangeOfString(".svg.") == nil + } + else { + return false + } + } +} \ No newline at end of file diff --git a/RxExample/RxExample/Services/ImageService.swift b/RxExample/RxExample/Services/ImageService.swift new file mode 100644 index 00000000..0845cecb --- /dev/null +++ b/RxExample/RxExample/Services/ImageService.swift @@ -0,0 +1,90 @@ +// +// ImageService.swift +// Example +// +// Created by Krunoslav Zaher on 3/28/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx +import RxCocoa + +protocol ImageService { + func imageFromURL(URL: NSURL) -> Observable> +} + +class DefaultImageService: ImageService { + typealias Dependencies = ( + URLSession: NSURLSession, + imageDecodeScheduler: ImmediateScheduler, + callbackScheduler: ImmediateScheduler + ) + + var $: Dependencies + + // 1rst level cache + let imageCache = NSCache() + + // 2nd level cache + let imageDataCache = NSCache() + + init($: Dependencies) { + self.$ = $ + + // cost is approx memory usage + self.imageDataCache.totalCostLimit = 10 * MB + + self.imageCache.countLimit = 20 + } + + func decodeImage(imageData: Observable>) -> Observable> { + return imageData >- observeSingleOn($.imageDecodeScheduler) >- map { maybeData in + return maybeData >== { data in + let maybeImage = UIImage(data: data) + + if maybeImage == nil { + // some error + return .Error(apiError("Decoding image error")) + } + + let image = maybeImage! + + return success(image) + } + } >- observeSingleOn($.callbackScheduler) + } + + func imageFromURL(URL: NSURL) -> Observable> { + let maybeImage = self.imageDataCache.objectForKey(URL) as? UIImage + + let decodedImage: Observable> + + // best case scenario, it's already decoded an in memory + if let image = maybeImage { + decodedImage = returnElement(success(image)) + } + else { + let cachedData = self.imageDataCache.objectForKey(URL) as? NSData + + // does image data cache contain anything + if let cachedData = cachedData { + decodedImage = returnElement(success(cachedData)) >- decodeImage + } + else { + // fetch from network + decodedImage = $.URLSession.rx_observableDataRequest(NSURLRequest(URL: URL)) >- doOnNext { maybeData in + _ = maybeData >== { data in + self.imageDataCache.setObject(data, forKey: URL) + } + } >- decodeImage + } + } + + return decodedImage >- doOnNext { maybeImage in + if let image = maybeImage.value { + self.imageCache.setObject(image, forKey: URL) + } + } + } +} diff --git a/RxExample/RxExample/Services/WikipediaAPI/WikipediaAPI.swift b/RxExample/RxExample/Services/WikipediaAPI/WikipediaAPI.swift new file mode 100644 index 00000000..73e0a5b1 --- /dev/null +++ b/RxExample/RxExample/Services/WikipediaAPI/WikipediaAPI.swift @@ -0,0 +1,69 @@ +// +// WikipediaAPI.swift +// Example +// +// Created by Krunoslav Zaher on 3/25/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx +import RxCocoa + +func apiError(error: String) -> NSError { + return NSError(domain: "WikipediaAPI", code: -1, userInfo: [NSLocalizedDescriptionKey: error]) +} + +public let WikipediaParseError = apiError("Error during parsing") + +protocol WikipediaAPI { + func getSearchResults(query: String) -> Observable> + func articleContent(searchResult: WikipediaSearchResult) -> Observable>; +} + +func URLEscape(pathSegment: String) -> String { + return pathSegment.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())! +} + +class DefaultWikipediaAPI: WikipediaAPI { + typealias Dependencies = ( + URLSession: NSURLSession, + callbackScheduler: ImmediateScheduler, + backgroundScheduler: ImmediateScheduler + ) + + var $: Dependencies + + init($: Dependencies) { + self.$ = $ + } + + // Example wikipedia response http://en.wikipedia.org/w/api.php?action=opensearch&search=Rx + func getSearchResults(query: String) -> Observable> { + let escapedQuery = URLEscape(query) + let urlContent = "http://en.wikipedia.org/w/api.php?action=opensearch&search=\(escapedQuery)" + let url = NSURL(string: urlContent)! + + return $.URLSession.rx_observableJSONWithURL(url) >- observeSingleOn($.backgroundScheduler) >- map { json in + return json >== castOrFail >== { (json: [AnyObject]) in + return WikipediaSearchResult.parseJSON(json) + } + } >- observeSingleOn($.callbackScheduler) + } + + // http://en.wikipedia.org/w/api.php?action=parse&page=rx&format=json + func articleContent(searchResult: WikipediaSearchResult) -> Observable> { + let escapedPage = URLEscape(searchResult.title) + let url = NSURL(string: "http://en.wikipedia.org/w/api.php?action=parse&page=\(escapedPage)&format=json") + + if url == nil { + return returnElement(.Error(apiError("Can't create url"))) + } + + return $.URLSession.rx_observableJSONWithURL(url!) >- map { jsonResult in + return jsonResult >== castOrFail >== { (json: NSDictionary) in + return WikipediaPage.parseJSON(json) + } + } >- observeSingleOn($.callbackScheduler) + } +} \ No newline at end of file diff --git a/RxExample/RxExample/Services/WikipediaAPI/WikipediaPage.swift b/RxExample/RxExample/Services/WikipediaAPI/WikipediaPage.swift new file mode 100644 index 00000000..509ef1a3 --- /dev/null +++ b/RxExample/RxExample/Services/WikipediaAPI/WikipediaPage.swift @@ -0,0 +1,32 @@ +// +// WikipediaPage.swift +// Example +// +// Created by Krunoslav Zaher on 3/28/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +struct WikipediaPage { + let title: String + let text: String + + init(title: String, text: String) { + self.title = title + self.text = text + } + + // tedious parsing part + static func parseJSON(json: NSDictionary) -> Result { + let title = json.valueForKey("parse")?.valueForKey("title") as? String + let text = json.valueForKey("parse")?.valueForKey("text")?.valueForKey("*") as? String + + if title == nil || text == nil { + return .Error(apiError("Error parsing page content")) + } + + return success(WikipediaPage(title: title!, text: text!)) + } +} \ No newline at end of file diff --git a/RxExample/RxExample/Services/WikipediaAPI/WikipediaSearchResult.swift b/RxExample/RxExample/Services/WikipediaAPI/WikipediaSearchResult.swift new file mode 100644 index 00000000..c41a3464 --- /dev/null +++ b/RxExample/RxExample/Services/WikipediaAPI/WikipediaSearchResult.swift @@ -0,0 +1,59 @@ +// +// WikipediaSearchResult.swift +// Example +// +// Created by Krunoslav Zaher on 3/28/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx + +struct WikipediaSearchResult: Printable { + let title: String + let description: String + let URL: NSURL + + init(title: String, description: String, URL: NSURL) { + self.title = title + self.description = description + self.URL = URL + } + + // tedious parsing part + static func parseJSON(json: [AnyObject]) -> Result<[WikipediaSearchResult]> { + let rootArrayTyped = json.map { $0 as? [AnyObject] } + .filter { $0 != nil } + .map { $0! } + + if rootArrayTyped.count != 3 { + return .Error(WikipediaParseError) + } + + let titleAndDescription = Array(Zip2(rootArrayTyped[0], rootArrayTyped[1])) + let titleDescriptionAndUrl: [((AnyObject, AnyObject), AnyObject)] = Array(Zip2(titleAndDescription, rootArrayTyped[2])) + + let searchResults: [Result] = titleDescriptionAndUrl.map ( { result -> Result in + let ((title: AnyObject, description: AnyObject), url: AnyObject) = result + + let titleString = title as? String, + descriptionString = description as? String, + urlString = url as? String + + if titleString == nil || descriptionString == nil || urlString == nil { + return .Error(WikipediaParseError) + } + + let URL = NSURL(string: urlString!) + if URL == nil { + return .Error(WikipediaParseError) + } + + return success(WikipediaSearchResult(title: titleString!, description: descriptionString!, URL: URL!)) + }) + + let values = (searchResults.filter { $0.value != nil }).map { *$0 } + + return success(values) + } +} diff --git a/RxExample/RxExample/ViewModels/SearchResultViewModel.swift b/RxExample/RxExample/ViewModels/SearchResultViewModel.swift new file mode 100644 index 00000000..c6b79983 --- /dev/null +++ b/RxExample/RxExample/ViewModels/SearchResultViewModel.swift @@ -0,0 +1,63 @@ +// +// SearchResultViewModel.swift +// Example +// +// Created by Krunoslav Zaher on 4/3/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx +import RxCocoa + +class SearchResultViewModel { + let searchResult: WikipediaSearchResult + + var title: Observable + var imageURLs: Observable> + + var $: SearchViewModel.Dependencies + + init($: SearchViewModel.Dependencies, searchResult: WikipediaSearchResult) { + self.searchResult = searchResult + + self.$ = $ + + self.title = never() + self.imageURLs = never() + + self.imageURLs = configureImageURLs() + self.title = configureTitle(self.imageURLs) + } + + // private methods + + func configureTitle(imageURLs: Observable>) -> Observable { + var searchResult = self.searchResult + + let loadingValue: [NSURL]? = nil + + return imageURLs >- map { + makeOptional(replaceErrorWith($0, [])) + } >- prefixWith(loadingValue) >- map { URLs in + if let URLs = URLs { + return "\(searchResult.title) (\(URLs.count)) pictures)" + } + else { + return "\(searchResult.title) loading ..." + } + } + } + + func configureImageURLs() -> Observable> { + let searchResult = self.searchResult + return $.API.articleContent(searchResult) >- observeSingleOn($.backgroundWorkScheduler) >- map { (maybePage) in + maybePage >== { page in + let URLs = success(parseImageURLsfromHTMLSuitableForDisplay(page.text)) + return URLs + } >>! { e in + return success([]) + } + } >- observeSingleOn($.mainScheduler) >- sharedSubscriptionWithCachedResult + } +} \ No newline at end of file diff --git a/RxExample/RxExample/ViewModels/SearchViewModel.swift b/RxExample/RxExample/ViewModels/SearchViewModel.swift new file mode 100644 index 00000000..b4b04c9a --- /dev/null +++ b/RxExample/RxExample/ViewModels/SearchViewModel.swift @@ -0,0 +1,69 @@ +// +// SearchViewModel.swift +// Example +// +// Created by Krunoslav Zaher on 3/28/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import Rx +import RxCocoa + + + +class SearchViewModel: Disposable { + typealias Dependencies = ( + API: WikipediaAPI, + imageService: ImageService, + backgroundWorkScheduler: ImmediateScheduler, + mainScheduler: DispatchQueueScheduler, + wireframe: Wireframe + ) + + // outputs + let rows: Observable> + + var $: Dependencies + + let disposeBag = DisposeBag() + + // public methods + + init($: Dependencies, + searchText: Observable, + selectedResult: Observable) { + + self.$ = $ + + let wireframe = $.wireframe + let API = $.API + + self.rows = searchText >- throttle(300, $.mainScheduler) >- distinctUntilChanged >- map { query in + $.API.getSearchResults(query) + } >- switchLatest >- map { resultsMaybe in + SearchViewModel.convertSearchResultModels($, resultsMaybe: resultsMaybe) + } + + selectedResult >- subscribeNext { searchResult in + $.wireframe.openURL(searchResult.searchResult.URL) + } >- disposeBag.addDisposable + } + + func dispose() { + disposeBag.dispose() + } + + // private methods + + class func convertSearchResultModels($: Dependencies, resultsMaybe: Result<[WikipediaSearchResult]>) -> Result<[SearchResultViewModel]> { + return resultsMaybe >== { results in + return success(results.map { SearchResultViewModel( + $: $, + searchResult: $0 + ) + }) + } + } + +} \ No newline at end of file diff --git a/RxExample/RxExample/Views/CollectionViewImageCell.swift b/RxExample/RxExample/Views/CollectionViewImageCell.swift new file mode 100644 index 00000000..364e8347 --- /dev/null +++ b/RxExample/RxExample/Views/CollectionViewImageCell.swift @@ -0,0 +1,35 @@ +// +// CollectionViewImageCell.swift +// Example +// +// Created by Krunoslav Zaher on 4/4/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import UIKit +import Rx +import RxCocoa + +public class CollectionViewImageCell: UICollectionViewCell { + @IBOutlet var imageOutlet: UIImageView! + + var disposeBag: DisposeBag! + + var image: Observable! { + didSet { + let disposeBag = DisposeBag() + + self.image >- imageOutlet.rx_subscribeImageTo(true) >- disposeBag.addDisposable + + self.disposeBag = disposeBag + } + } + + override public func prepareForReuse() { + super.prepareForReuse() + + disposeBag?.dispose() + disposeBag = nil + } +} \ No newline at end of file diff --git a/RxExample/RxExample/Views/LaunchScreen.xib b/RxExample/RxExample/Views/LaunchScreen.xib new file mode 100644 index 00000000..e2df5a9d --- /dev/null +++ b/RxExample/RxExample/Views/LaunchScreen.xib @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RxExample/RxExample/Views/Main.storyboard b/RxExample/RxExample/Views/Main.storyboard new file mode 100644 index 00000000..7bc754f9 --- /dev/null +++ b/RxExample/RxExample/Views/Main.storyboard @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RxExample/RxExample/Views/RootViewController.swift b/RxExample/RxExample/Views/RootViewController.swift new file mode 100644 index 00000000..969830d3 --- /dev/null +++ b/RxExample/RxExample/Views/RootViewController.swift @@ -0,0 +1,18 @@ +// +// RootViewController.swift +// RxExample +// +// Created by Krunoslav Zaher on 4/6/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import UIKit + +public class RootViewController : UITableViewController { + override public func awakeFromNib() { + super.awakeFromNib() + + self.performSegueWithIdentifier("ShowWikipediaSearch", sender: nil) + } +} \ No newline at end of file diff --git a/RxExample/RxExample/Views/WikipediaImageCell.xib b/RxExample/RxExample/Views/WikipediaImageCell.xib new file mode 100644 index 00000000..465425e7 --- /dev/null +++ b/RxExample/RxExample/Views/WikipediaImageCell.xib @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RxExample/RxExample/Views/WikipediaSearchCell.swift b/RxExample/RxExample/Views/WikipediaSearchCell.swift new file mode 100644 index 00000000..662c3f2a --- /dev/null +++ b/RxExample/RxExample/Views/WikipediaSearchCell.swift @@ -0,0 +1,59 @@ +// +// WikipediaSearchCell.swift +// Example +// +// Created by Krunoslav Zaher on 3/28/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import UIKit +import Rx +import RxCocoa + +public class WikipediaSearchCell: UITableViewCell { + + @IBOutlet var titleOutlet: UILabel! + @IBOutlet var URLOutlet: UILabel! + @IBOutlet var imagesOutlet: UICollectionView! + + var disposeBag: DisposeBag! + + public override func awakeFromNib() { + super.awakeFromNib() + + self.imagesOutlet.registerNib(UINib(nibName: "WikipediaImageCell", bundle: nil), forCellWithReuseIdentifier: "ImageCell") + } + + var viewModel: SearchResultViewModel! { + didSet { + let $ = viewModel.$ + + let disposeBag = DisposeBag() + + self.titleOutlet.rx_subscribeTextTo(viewModel?.title ?? returnElement("")) + self.URLOutlet.text = viewModel.searchResult.URL.absoluteString ?? "" + + viewModel.imageURLs >- map { maybeURLs -> [NSURL] in + replaceErrorWith(maybeURLs, []) + } >- self.imagesOutlet.rx_subscribeItemsWithIdentifierTo("ImageCell") { (_, _, URL, cell: CollectionViewImageCell) in + + let resultImage = $.imageService.imageFromURL(URL) >- map { maybeImage in + replaceErrorWithNil(maybeImage) + } + + let loadingPlaceholder: UIImage? = nil // usually not used, but since this is an example + + cell.image = resultImage >- prefixWith(loadingPlaceholder) + } >- disposeBag.addDisposable + + self.disposeBag = disposeBag + } + } + + public override func prepareForReuse() { + super.prepareForReuse() + + self.disposeBag = nil + } +} \ No newline at end of file diff --git a/RxExample/RxExample/Views/WikipediaSearchCell.xib b/RxExample/RxExample/Views/WikipediaSearchCell.xib new file mode 100644 index 00000000..70144214 --- /dev/null +++ b/RxExample/RxExample/Views/WikipediaSearchCell.xib @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RxExample/RxExample/Views/WikipediaSearchViewController.swift b/RxExample/RxExample/Views/WikipediaSearchViewController.swift new file mode 100644 index 00000000..e7ba3c5b --- /dev/null +++ b/RxExample/RxExample/Views/WikipediaSearchViewController.swift @@ -0,0 +1,100 @@ +// +// ViewController.swift +// Example +// +// Created by Krunoslav Zaher on 2/21/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import UIKit +import Rx +import RxCocoa + +public class WikipediaSearchViewController: UIViewController { + + private let disposeBag: DisposeBag = DisposeBag() + private var viewModel: SearchViewModel? = nil + + override public func awakeFromNib() { + super.awakeFromNib() + } + + // lifecycle + + override public func viewDidLoad() { + super.viewDidLoad() +#if DEBUG + if resourceCount != 1 { + println("Number of resources = \(resourceCount)") + assert(resourceCount == 1) + } +#endif + let operationQueue = NSOperationQueue() + operationQueue.maxConcurrentOperationCount = 2 + operationQueue.qualityOfService = NSQualityOfService.UserInitiated + + let backgroundScheduler = OperationQueueScheduler(operationQueue: operationQueue) + let mainScheduler = MainScheduler.sharedInstance + + weak var weakSelf = self + + let API = DefaultWikipediaAPI($: ( + URLSession: NSURLSession.sharedSession(), + callbackScheduler: mainScheduler, + backgroundScheduler: backgroundScheduler + )) + let imageService = DefaultImageService($: ( + URLSession: NSURLSession.sharedSession(), + imageDecodeScheduler: backgroundScheduler, + callbackScheduler: MainScheduler.sharedInstance + )) + + let resultsTableView = self.searchDisplayController!.searchResultsTableView + let searchBar = self.searchDisplayController!.searchBar + + resultsTableView.registerNib(UINib(nibName: "WikipediaSearchCell", bundle: nil), forCellReuseIdentifier: "WikipediaSearchCell") + + resultsTableView.rowHeight = 194 + + let viewModel = SearchViewModel( + $: ( + API: API, + imageService: imageService, + mainScheduler: mainScheduler, + backgroundWorkScheduler: backgroundScheduler, + wireframe: DefaultWireframe() + ), + searchText: searchBar.rx_observableSearchText(), + selectedResult: resultsTableView.rx_observableElementTap() + ) + + // map table view rows + // { + viewModel.rows >- map { rows in + replaceErrorWith(rows, []) + } >- resultsTableView.rx_subscribeRowsToCellWithIdentifier("WikipediaSearchCell") { (_, _, viewModel, cell: WikipediaSearchCell) in + + cell.viewModel = viewModel + } >- disposeBag.addDisposable + // } + + // dismiss keyboard on scroll + // { + resultsTableView.rx_observableContentOffset() >- subscribeNext { _ in + if searchBar.isFirstResponder() { + _ = searchBar.resignFirstResponder() + } + } >- disposeBag.addDisposable + + disposeBag.addDisposable(viewModel) + + self.viewModel = viewModel + // } + } + + deinit { +#if DEBUG + println("View controller disposed with \(resourceCount) resournces") +#endif + } +} diff --git a/RxExample/RxExample/Wireframe.swift b/RxExample/RxExample/Wireframe.swift new file mode 100644 index 00000000..fdf4b3fe --- /dev/null +++ b/RxExample/RxExample/Wireframe.swift @@ -0,0 +1,21 @@ +// +// Wireframe.swift +// Example +// +// Created by Krunoslav Zaher on 4/3/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import UIKit + +protocol Wireframe { + func openURL(URL: NSURL) +} + + +class DefaultWireframe: Wireframe { + func openURL(URL: NSURL) { + UIApplication.sharedApplication().openURL(URL) + } +} \ No newline at end of file diff --git a/RxExample/RxExampleTests/Info.plist b/RxExample/RxExampleTests/Info.plist new file mode 100644 index 00000000..7f80ab77 --- /dev/null +++ b/RxExample/RxExampleTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + Krunoslav-Zaher.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/RxExample/RxExampleTests/RxExampleTests.swift b/RxExample/RxExampleTests/RxExampleTests.swift new file mode 100644 index 00000000..6cbd3d03 --- /dev/null +++ b/RxExample/RxExampleTests/RxExampleTests.swift @@ -0,0 +1,36 @@ +// +// RxExampleTests.swift +// RxExampleTests +// +// Created by Krunoslav Zaher on 4/4/15. +// Copyright (c) 2015 Krunoslav Zaher. All rights reserved. +// + +import UIKit +import XCTest + +class RxExampleTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + XCTAssert(true, "Pass") + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measureBlock() { + // Put the code you want to measure the time of here. + } + } + +}