From 0ddf9c8e5042857b022a212eb2b4468507dd819a Mon Sep 17 00:00:00 2001 From: Tuomas Artman Date: Wed, 7 Sep 2016 23:08:14 -0700 Subject: [PATCH 1/5] Updated to Xcode 8 and Swift 3 - Revamps API --- .travis.yml | 12 +- Signals.podspec | 2 +- Signals.xcodeproj/project.pbxproj | 112 ++----- Signals/Signal.swift | 385 ++++++++-------------- Signals/Signals.h | 4 - Signals/ios/AssociatedObject.swift | 56 +--- Signals/ios/UIControl+Signals.swift | 178 +--------- SignalsTests/SignalsTests.swift | 155 ++++----- SignalsTests/SingalQueueTests.swift | 138 +++----- SignalsTests/TestListener.swift | 18 +- SignalsTests/UIControl+SignalsTests.swift | 65 ++-- 11 files changed, 350 insertions(+), 775 deletions(-) diff --git a/.travis.yml b/.travis.yml index be0d645..5cde273 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode7.3 +osx_image: xcode8.0 env: global: - LC_CTYPE=en_US.UTF-8 @@ -9,10 +9,10 @@ env: - OSX_FRAMEWORK_SCHEME="Signals OSX" - TVOS_FRAMEWORK_SCHEME="Signals tvOS" - WATCHOS_FRAMEWORK_SCHEME="Signals watchOS" - - IOS_SDK=iphonesimulator9.2 - - OSX_SDK=macosx10.11 - - TVOS_SDK=appletvsimulator9.1 - - WATCHOS_SDK=watchsimulator2.1 + - IOS_SDK=iphonesimulator10.0 + - OSX_SDK=macosx10.12 + - TVOS_SDK=appletvsimulator10.0 + - WATCHOS_SDK=watchsimulator3.0 matrix: - DESTINATION="OS=8.1,name=iPhone 4S" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="YES" @@ -21,7 +21,7 @@ env: - DESTINATION="OS=8.4,name=iPhone 6" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" - DESTINATION="OS=9.0,name=iPhone 6 Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" - DESTINATION="OS=9.1,name=iPhone 6S" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" - - DESTINATION="OS=9.2,name=iPhone 6S Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" + - DESTINATION="OS=10.0,name=iPhone 6S Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" - DESTINATION="arch=x86_64" SCHEME="$OSX_FRAMEWORK_SCHEME" SDK="$OSX_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" - DESTINATION="OS=9.1,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" - DESTINATION="OS=2.1,name=Apple Watch - 38mm" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" SDK="$WATCHOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD_LINT="NO" diff --git a/Signals.podspec b/Signals.podspec index f6c3833..e211428 100644 --- a/Signals.podspec +++ b/Signals.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Signals' - s.version = '3.0.0' + s.version = '4.0.0' s.license = 'MIT' s.summary = 'Elegant eventing' s.homepage = 'https://github.com/artman/Signals' diff --git a/Signals.xcodeproj/project.pbxproj b/Signals.xcodeproj/project.pbxproj index 2aad4f9..07ed5b9 100644 --- a/Signals.xcodeproj/project.pbxproj +++ b/Signals.xcodeproj/project.pbxproj @@ -16,20 +16,16 @@ 720D11951A08537E003C4361 /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 720D11941A08537E003C4361 /* Signal.swift */; }; 721E12A91C1E8E1D00CD0CBA /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 720D11941A08537E003C4361 /* Signal.swift */; }; 721E12AC1C1E8E1D00CD0CBA /* Signals.h in Headers */ = {isa = PBXBuildFile; fileRef = 720D11771A085315003C4361 /* Signals.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 721E12AE1C1E8E1D00CD0CBA /* Signals.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 725D2B621B71C916007B83AB /* Signals.podspec */; }; - 723C01CB1C377DEA000A998E /* UIControl+SignalsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 723C01CA1C377DEA000A998E /* UIControl+SignalsTests.swift */; }; - 724993531C43557C006653CB /* UIControl+Signals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 724993521C43557C006653CB /* UIControl+Signals.swift */; }; - 724993551C4356C2006653CB /* AssociatedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 724993541C4356C2006653CB /* AssociatedObject.swift */; }; - 724993581C435AFF006653CB /* AssociatedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 724993541C4356C2006653CB /* AssociatedObject.swift */; }; - 724993591C435AFF006653CB /* UIControl+Signals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 724993521C43557C006653CB /* UIControl+Signals.swift */; }; - 7249935A1C435B22006653CB /* UIControl+SignalsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 723C01CA1C377DEA000A998E /* UIControl+SignalsTests.swift */; }; + 72321EFC1DB74CA300E79E9D /* AssociatedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72AD0F491D7CE74500ACFF1A /* AssociatedObject.swift */; }; + 72321EFD1DB74CA400E79E9D /* AssociatedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72AD0F491D7CE74500ACFF1A /* AssociatedObject.swift */; }; + 72321EFF1DB74CBF00E79E9D /* UIControl+Signals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72AD0F4A1D7CE74500ACFF1A /* UIControl+Signals.swift */; }; + 72321F001DB74CC000E79E9D /* UIControl+Signals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72AD0F4A1D7CE74500ACFF1A /* UIControl+Signals.swift */; }; 725B76C01C26558F001D04CC /* Signals.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 725B76B61C26558F001D04CC /* Signals.framework */; }; 725B76CD1C26572E001D04CC /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 720D11941A08537E003C4361 /* Signal.swift */; }; 725B76CE1C265738001D04CC /* TestListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 720D11901A085370003C4361 /* TestListener.swift */; }; 725B76CF1C265738001D04CC /* SignalEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 720D118E1A085370003C4361 /* SignalEmitter.swift */; }; 725B76D01C265738001D04CC /* SignalsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 720D11841A085315003C4361 /* SignalsTests.swift */; }; 725B76D11C265738001D04CC /* SingalQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 720D118F1A085370003C4361 /* SingalQueueTests.swift */; }; - 725D2B631B71C916007B83AB /* Signals.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 725D2B621B71C916007B83AB /* Signals.podspec */; }; 72665B401C212BDB0067DF26 /* Signals.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72665B361C212BDB0067DF26 /* Signals.framework */; }; 72665B4D1C212BEC0067DF26 /* Signals.h in Headers */ = {isa = PBXBuildFile; fileRef = 720D11771A085315003C4361 /* Signals.h */; settings = {ATTRIBUTES = (Public, ); }; }; 72665B4E1C212BEC0067DF26 /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 720D11941A08537E003C4361 /* Signal.swift */; }; @@ -37,6 +33,9 @@ 72665B501C212BF60067DF26 /* SignalEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 720D118E1A085370003C4361 /* SignalEmitter.swift */; }; 72665B511C212BF60067DF26 /* SignalsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 720D11841A085315003C4361 /* SignalsTests.swift */; }; 72665B521C212BF60067DF26 /* SingalQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 720D118F1A085370003C4361 /* SingalQueueTests.swift */; }; + 7299334E1DB349E3001A431D /* Signals.h in Headers */ = {isa = PBXBuildFile; fileRef = 720D11771A085315003C4361 /* Signals.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 72C57B6C1D7CE7A300CF2EE0 /* UIControl+SignalsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72AD0F531D7CE77200ACFF1A /* UIControl+SignalsTests.swift */; }; + 72C57B6D1D7CE7A400CF2EE0 /* UIControl+SignalsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72AD0F531D7CE77200ACFF1A /* UIControl+SignalsTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -75,14 +74,14 @@ 720D11901A085370003C4361 /* TestListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestListener.swift; sourceTree = ""; }; 720D11941A08537E003C4361 /* Signal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Signal.swift; sourceTree = ""; }; 721E12B21C1E8E1D00CD0CBA /* Signals.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Signals.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 723C01CA1C377DEA000A998E /* UIControl+SignalsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIControl+SignalsTests.swift"; sourceTree = ""; }; - 724993521C43557C006653CB /* UIControl+Signals.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIControl+Signals.swift"; path = "ios/UIControl+Signals.swift"; sourceTree = ""; }; - 724993541C4356C2006653CB /* AssociatedObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AssociatedObject.swift; path = ios/AssociatedObject.swift; sourceTree = ""; }; 725B76B61C26558F001D04CC /* Signals.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Signals.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 725B76BF1C26558F001D04CC /* SignalsTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SignalsTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 725D2B621B71C916007B83AB /* Signals.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Signals.podspec; sourceTree = ""; }; 72665B361C212BDB0067DF26 /* Signals.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Signals.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 72665B3F1C212BDB0067DF26 /* SignalsTests OSX.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SignalsTests OSX.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 72AD0F491D7CE74500ACFF1A /* AssociatedObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AssociatedObject.swift; path = ios/AssociatedObject.swift; sourceTree = ""; }; + 72AD0F4A1D7CE74500ACFF1A /* UIControl+Signals.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIControl+Signals.swift"; path = "ios/UIControl+Signals.swift"; sourceTree = ""; }; + 72AD0F531D7CE77200ACFF1A /* UIControl+SignalsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIControl+SignalsTests.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -168,9 +167,9 @@ 720D11741A085314003C4361 /* Signals */ = { isa = PBXGroup; children = ( - 724993511C435569006653CB /* iOS */, - 720D11941A08537E003C4361 /* Signal.swift */, 720D11771A085315003C4361 /* Signals.h */, + 720D11941A08537E003C4361 /* Signal.swift */, + 724993511C435569006653CB /* iOS */, 720D11751A085315003C4361 /* Supporting Files */, ); path = Signals; @@ -187,10 +186,10 @@ 720D11811A085315003C4361 /* SignalsTests */ = { isa = PBXGroup; children = ( - 720D11961A0855E0003C4361 /* Helpers */, 720D11841A085315003C4361 /* SignalsTests.swift */, 720D118F1A085370003C4361 /* SingalQueueTests.swift */, - 723C01CA1C377DEA000A998E /* UIControl+SignalsTests.swift */, + 72AD0F531D7CE77200ACFF1A /* UIControl+SignalsTests.swift */, + 720D11961A0855E0003C4361 /* Helpers */, 720D11821A085315003C4361 /* Supporting Files */, ); path = SignalsTests; @@ -216,8 +215,8 @@ 724993511C435569006653CB /* iOS */ = { isa = PBXGroup; children = ( - 724993541C4356C2006653CB /* AssociatedObject.swift */, - 724993521C43557C006653CB /* UIControl+Signals.swift */, + 72AD0F491D7CE74500ACFF1A /* AssociatedObject.swift */, + 72AD0F4A1D7CE74500ACFF1A /* UIControl+Signals.swift */, ); name = iOS; sourceTree = ""; @@ -245,6 +244,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 7299334E1DB349E3001A431D /* Signals.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -446,7 +446,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 725D2B631B71C916007B83AB /* Signals.podspec in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -461,7 +460,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 721E12AE1C1E8E1D00CD0CBA /* Signals.podspec in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -500,9 +498,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 72321EFC1DB74CA300E79E9D /* AssociatedObject.swift in Sources */, + 72321EFF1DB74CBF00E79E9D /* UIControl+Signals.swift in Sources */, 720D11951A08537E003C4361 /* Signal.swift in Sources */, - 724993551C4356C2006653CB /* AssociatedObject.swift in Sources */, - 724993531C43557C006653CB /* UIControl+Signals.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -510,7 +508,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 723C01CB1C377DEA000A998E /* UIControl+SignalsTests.swift in Sources */, + 72C57B6C1D7CE7A300CF2EE0 /* UIControl+SignalsTests.swift in Sources */, 720D11921A085370003C4361 /* SingalQueueTests.swift in Sources */, 720D11911A085370003C4361 /* SignalEmitter.swift in Sources */, 720D11851A085315003C4361 /* SignalsTests.swift in Sources */, @@ -530,8 +528,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 724993581C435AFF006653CB /* AssociatedObject.swift in Sources */, - 724993591C435AFF006653CB /* UIControl+Signals.swift in Sources */, + 72321EFD1DB74CA400E79E9D /* AssociatedObject.swift in Sources */, + 72321F001DB74CC000E79E9D /* UIControl+Signals.swift in Sources */, 725B76CD1C26572E001D04CC /* Signal.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -540,7 +538,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7249935A1C435B22006653CB /* UIControl+SignalsTests.swift in Sources */, + 72C57B6D1D7CE7A400CF2EE0 /* UIControl+SignalsTests.swift in Sources */, 725B76D01C265738001D04CC /* SignalsTests.swift in Sources */, 725B76CF1C265738001D04CC /* SignalEmitter.swift in Sources */, 725B76CE1C265738001D04CC /* TestListener.swift in Sources */, @@ -633,6 +631,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -676,6 +675,8 @@ MACOSX_DEPLOYMENT_TARGET = 10.9; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -686,14 +687,12 @@ 720D11891A085315003C4361 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_IDENTITY = "iPhone Developer"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Signals/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; OTHER_LDFLAGS = ( "-Xlinker", @@ -702,21 +701,18 @@ PRODUCT_BUNDLE_IDENTIFIER = "artman.fi.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = Signals; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; }; name = Debug; }; 720D118A1A085315003C4361 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_IDENTITY = "iPhone Developer"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Signals/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; OTHER_LDFLAGS = ( "-Xlinker", @@ -725,8 +721,6 @@ PRODUCT_BUNDLE_IDENTIFIER = "artman.fi.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = Signals; SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; }; name = Release; }; @@ -741,7 +735,6 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "artman.fi.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -752,8 +745,6 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "artman.fi.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; }; name = Release; }; @@ -766,7 +757,6 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Signals/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; OTHER_LDFLAGS = ( "-Xlinker", @@ -776,7 +766,6 @@ PRODUCT_NAME = Signals; SDKROOT = watchos; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -789,7 +778,6 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Signals/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; OTHER_LDFLAGS = ( "-Xlinker", @@ -799,20 +787,16 @@ PRODUCT_NAME = Signals; SDKROOT = watchos; SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; }; name = Release; }; 725B76C81C26558F001D04CC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = Signals/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -820,22 +804,17 @@ PRODUCT_NAME = Signals; SDKROOT = appletvos; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.0; }; name = Debug; }; 725B76C91C26558F001D04CC /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = Signals/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -843,128 +822,89 @@ PRODUCT_NAME = Signals; SDKROOT = appletvos; SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.0; }; name = Release; }; 725B76CB1C26558F001D04CC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - DEBUG_INFORMATION_FORMAT = dwarf; - GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = SignalsTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "artman.fi.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_VERSION = 3.0; - TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; }; 725B76CC1C26558F001D04CC /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = SignalsTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "artman.fi.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; - TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; }; 72665B481C212BDB0067DF26 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; - DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = Signals/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; PRODUCT_BUNDLE_IDENTIFIER = artman.fi.Signals; PRODUCT_NAME = Signals; SDKROOT = macosx; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; }; name = Debug; }; 72665B491C212BDB0067DF26 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = Signals/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; PRODUCT_BUNDLE_IDENTIFIER = artman.fi.Signals; PRODUCT_NAME = Signals; SDKROOT = macosx; SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; }; name = Release; }; 72665B4B1C212BDB0067DF26 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = SignalsTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; PRODUCT_BUNDLE_IDENTIFIER = "artman.fi.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_VERSION = 3.0; }; name = Debug; }; 72665B4C1C212BDB0067DF26 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = SignalsTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; PRODUCT_BUNDLE_IDENTIFIER = "artman.fi.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/Signals/Signal.swift b/Signals/Signal.swift index 3749a9f..5eeca17 100644 --- a/Signals/Signal.swift +++ b/Signals/Signal.swift @@ -8,18 +8,16 @@ import Foundation -/// Create instances of Signal and assign them to public constants on your class for each event type that can -/// be observed by listeners. +/// Create instances of `Signal` and assign them to public constants on your class for each event type that your +/// class fires. final public class Signal { + public typealias SignalCallback = (T) -> Void - /// The number of times the signal has fired. + /// The number of times the `Signal` has fired. public private(set) var fireCount: Int = 0 - /// The last data that the signal was fired with. - public private(set) var lastDataFired: T? = nil - - /// Whether or not the Signal should retain a reference to the last data it was fired with. Defaults to false. + /// Whether or not the `Signal` should retain a reference to the last data it was fired with. Defaults to false. public var retainLastData: Bool = false { didSet { if !retainLastData { @@ -28,122 +26,85 @@ final public class Signal { } } - /// All the listeners listening to the Signal. - public var listeners:[AnyObject] { + /// The last data that the `Signal` was fired with. In order for the `Signal` to retain the last fired data, its + /// `retainLastFired`-property needs to be set to true + public private(set) var lastDataFired: T? = nil + + /// All the observers of to the `Signal`. + public var observers:[AnyObject] { get { return signalListeners.filter { - return $0.listener != nil + return $0.observer != nil }.map { (signal) -> AnyObject in - return signal.listener! + return signal.observer! } } } + private var signalListeners = [SignalSubscription]() + /// Initializer. /// /// - parameter retainLastData: Whether or not the Signal should retain a reference to the last data it was fired /// with. Defaults to false. public init(retainLastData: Bool = false) { - fireCount = 0 self.retainLastData = retainLastData } - private var signalListeners = [SignalListener]() - - private func dumpCancelledListeners() { - var removeListeners = false - for signalListener in signalListeners { - if signalListener.listener == nil { - removeListeners = true - } - } - if removeListeners { - signalListeners = signalListeners.filter { - return $0.listener != nil - } - } - } - - /// Attaches a listener to the signal. + /// Subscribes an observer to the `Signal`. /// - /// - parameter on: The listener object. Sould the listener be deallocated, its associated callback is - /// automatically removed. - /// - parameter callback: The closure to invoke whenever the signal fires. - #if swift(>=3.0) + /// - parameter on: The observer that subscribes to the `Signal`. Should the observer be deallocated, the + /// subscription is automatically cancelled. + /// - parameter callback: The closure to invoke whenever the `Signal` fires. + /// - returns: A `SignalSubscription` that can be used to cancel or filter the subscription. @discardableResult - public func listen(on listener: AnyObject, callback: @escaping SignalCallback) -> SignalListener { - dumpCancelledListeners() - let signalListener = SignalListener(listener: listener, callback: callback); - signalListeners.append(signalListener) - return signalListener - } - #else - public func listen(on listener: AnyObject, callback: SignalCallback) -> SignalListener { - dumpCancelledListeners() - let signalListener = SignalListener(listener: listener, callback: callback); + public func subscribe(on observer: AnyObject, callback: @escaping SignalCallback) -> SignalSubscription { + flushCancelledListeners() + let signalListener = SignalSubscription(observer: observer, callback: callback); signalListeners.append(signalListener) return signalListener } - #endif - /// Attaches a listener to the signal that is removed after the signal has fired once. + /// Subscribes an observer to the `Signal`. The subscription is automatically canceled after the `Signal` has + /// fired once. /// - /// - parameter on: The listener object. Sould the listener be deallocated, its associated callback is - /// automatically removed. + /// - parameter on: The observer that subscribes to the `Signal`. Should the observer be deallocated, the + /// subscription is automatically cancelled. /// - parameter callback: The closure to invoke when the signal fires for the first time. - #if swift(>=3.0) @discardableResult - public func listenOnce(on listener: AnyObject, callback: @escaping SignalCallback) -> SignalListener { - let signalListener = self.listen(on: listener, callback: callback) + public func subscribeOnce(on observer: AnyObject, callback: @escaping SignalCallback) -> SignalSubscription { + let signalListener = self.subscribe(on: observer, callback: callback) signalListener.once = true return signalListener } - #else - public func listenOnce(on listener: AnyObject, callback: SignalCallback) -> SignalListener { - let signalListener = self.listen(on: listener, callback: callback) - signalListener.once = true - return signalListener - } - #endif - /// Attaches a listener to the signal and invokes the callback immediately with the last data fired by the signal - /// if it has fired at least once and if the `retainLastData` property has been set to true. + /// Subscribes an observer to the `Signal` and invokes its callback immediately with the last data fired by the + /// `Signal` if it has fired at least once and if the `retainLastData` property has been set to true. /// - /// - parameter on: The listener object. Sould the listener be deallocated, its associated callback is - /// automatically removed. - /// - parameter callback: The closure to invoke whenever the signal fires. - #if swift(>=3.0) + /// - parameter on: The observer that subscribes to the `Signal`. Should the observer be deallocated, the + /// subscription is automatically cancelled. + /// - parameter callback: The closure to invoke whenever the `Signal` fires. @discardableResult - public func listenPast(on listener: AnyObject, callback: @escaping SignalCallback) -> SignalListener { - let signalListener = self.listen(on: listener, callback: callback) - if let lastDataFired = lastDataFired { - signalListener.callback(lastDataFired) - } - return signalListener - } - #else - public func listenPast(on listener: AnyObject, callback: (T) -> Void) -> SignalListener { - let signalListener = self.listen(on: listener, callback: callback) + public func subscribePast(on observer: AnyObject, callback: @escaping SignalCallback) -> SignalSubscription { + let signalListener = self.subscribe(on: observer, callback: callback) if let lastDataFired = lastDataFired { signalListener.callback(lastDataFired) } return signalListener } - #endif - /// Attaches a listener to the signal and invokes the callback immediately with the last data fired by the signal - /// if it has fired at least once and if the `retainLastData` property has been set to true. If it has not been - /// fired yet, it will continue listening until it fires for the first time. + /// Subscribes an observer to the `Signal` and invokes its callback immediately with the last data fired by the + /// `Signal` if it has fired at least once and if the `retainLastData` property has been set to true. If it has + /// not been fired yet, it will continue listening until it fires for the first time. /// - /// - parameter listener: The listener object. Sould the listener be deallocated, its associated callback is - /// automatically removed. + /// - parameter on: The observer that subscribes to the `Signal`. Should the observer be deallocated, the + /// subscription is automatically cancelled. /// - parameter callback: The closure to invoke whenever the signal fires. - #if swift(>=3.0) @discardableResult - public func listenPastOnce(on listener: AnyObject, callback: @escaping SignalCallback) -> SignalListener { - let signalListener = self.listen(on: listener, callback: callback) + public func subscribePastOnce(on observer: AnyObject, callback: @escaping SignalCallback) -> SignalSubscription { + let signalListener = self.subscribe(on: observer, callback: callback) if let lastDataFired = lastDataFired { signalListener.callback(lastDataFired) signalListener.cancel() @@ -152,27 +113,14 @@ final public class Signal { } return signalListener } - #else - public func listenPastOnce(on listener: AnyObject, callback: SignalCallback) -> SignalListener { - let signalListener = self.listen(on: listener, callback: callback) - if let lastDataFired = lastDataFired { - signalListener.callback(lastDataFired) - signalListener.cancel() - } else { - signalListener.once = true - } - return signalListener - } - #endif - /// Fires the singal. + /// Fires the `Singal`. /// - /// - parameter data: The data to fire the signal with. - #if swift(>=3.0) + /// - parameter data: The data to fire the `Signal` with. public func fire(_ data: T) { fireCount += 1 lastDataFired = retainLastData ? data : nil - dumpCancelledListeners() + flushCancelledListeners() for signalListener in signalListeners { if signalListener.filter == nil || signalListener.filter!(data) == true { @@ -180,94 +128,128 @@ final public class Signal { } } } - #else - public func fire(data: T) { - fireCount += 1 - lastDataFired = retainLastData ? data : nil - dumpCancelledListeners() - - for signalListener in signalListeners { - if signalListener.filter == nil || signalListener.filter!(data) == true { - _ = signalListener.dispatch(data) - } - } - } - #endif - public func dettach(from listener: AnyObject) { + /// Cancels all subscriptions for an observer. + /// + /// - parameter for: The observer whose subscriptions to cancel + public func cancelSubscription(for observer: AnyObject) { signalListeners = signalListeners.filter { - if let definiteListener:AnyObject = $0.listener { - return definiteListener !== listener + if let definiteListener:AnyObject = $0.observer { + return definiteListener !== observer } return false } } - /// Removes all listeners from the Signal. - public func dettachAllListeners() { - #if swift(>=3.0) + /// Cancels all subscriptions for the `Signal`. + public func cancelAllSubscriptions() { signalListeners.removeAll(keepingCapacity: false) - #else - signalListeners.removeAll(keepCapacity: false) - #endif } - /// Clears the last fired data from the Signal and resets the fire count. + /// Clears the last fired data from the `Signal` and resets the fire count. public func clearLastData() { lastDataFired = nil } + + // MARK: - Private Interface + + private func flushCancelledListeners() { + var removeListeners = false + for signalListener in signalListeners { + if signalListener.observer == nil { + removeListeners = true + } + } + if removeListeners { + signalListeners = signalListeners.filter { + return $0.observer != nil + } + } + } + } -/// A SignalLister represenents an instance and its association with a Signal. -public class SignalListener { +/// A SignalLister represenents an instance and its association with a `Signal`. +final public class SignalSubscription { public typealias SignalCallback = (T) -> Void public typealias SignalFilter = (T) -> Bool - // The listener - weak public var listener: AnyObject? + // The observer. + weak public var observer: AnyObject? - /// Whether the listener should be removed once it observes the Signal firing once. Defaults to false. + /// Whether the observer should be removed once it observes the `Signal` firing once. Defaults to false. public var once = false - #if swift(>=3.0) - private var delay: TimeInterval? - #else - private var delay: NSTimeInterval? - #endif - var queuedData: T? - var filter: (SignalFilter)? - var callback: SignalCallback + fileprivate var queuedData: T? + fileprivate var filter: (SignalFilter)? + fileprivate var callback: SignalCallback + fileprivate var dispatchQueue: DispatchQueue? + private var sampleInterval: TimeInterval? - #if swift(>=3.0) - private var dispatchQueue: DispatchQueue? - #else - private var dispatchQueue: dispatch_queue_t? + fileprivate init(observer: AnyObject, callback: @escaping SignalCallback) { + self.observer = observer + self.callback = callback + } #endif - #if swift(>=3.0) - init (listener: AnyObject, callback: @escaping SignalCallback) { - self.listener = listener - self.callback = callback + /// Assigns a filter to the `SignalSubscription`. This lets you define conditions under which a observer should actually + /// receive the firing of a `Singal`. The closure that is passed an argument can decide whether the firing of a + /// `Signal` should actually be dispatched to its observer depending on the data fired. + /// + /// If the closeure returns true, the observer is informed of the fire. The default implementation always + /// returns `true`. + /// + /// - parameter predicate: A closure that can decide whether the `Signal` fire should be dispatched to its observer. + /// - returns: Returns self so you can chain calls. + @discardableResult + public func filter(_ predicate: @escaping SignalFilter) -> SignalSubscription { + self.filter = predicate + return self } - #else - init (listener: AnyObject, callback: SignalCallback) { - self.listener = listener - self.callback = callback + + + /// Tells the observer to sample received `Signal` data and only dispatch the latest data once the time interval + /// has elapsed. This is useful if the subscriber wants to throttle the amount of data it receives from the + /// `Singla`. + /// + /// - parameter sampleInterval: The number of seconds to delay dispatch. + /// - returns: Returns self so you can chain calls. + @discardableResult + public func sample(every sampleInterval: TimeInterval) -> SignalSubscription { + self.sampleInterval = sampleInterval + return self + } + + /// Assigns a dispatch queue to the `SignalSubscription`. The queue is used for scheduling the observer calls. If not + /// nil, the callback is fired asynchronously on the specified queue. Otherwise, the block is run synchronously + /// on the posting thread, which is its default behaviour. + /// + /// - parameter queue: A queue for performing the observer's calls. + /// - returns: Returns self so you can chain calls. + @discardableResult + public func dispatch(onQueue queue: DispatchQueue) -> SignalSubscription { + self.dispatchQueue = queue + return self + } + + /// Cancels the observer. This will cancelSubscription the listening object from the `Signal`. + public func cancel() { + self.observer = nil } - #endif - func dispatch(data: T) -> Bool { - guard listener != nil else { + // MARK: - Private Interface + + fileprivate func dispatch(data: T) -> Bool { + guard observer != nil else { return false } if once { - listener = nil + observer = nil } - if delay != nil { + if let sampleInterval = sampleInterval { if queuedData != nil { - // Already queueing queuedData = data } else { // Set up queue @@ -276,110 +258,33 @@ public class SignalListener { if let definiteSelf = self { let data = definiteSelf.queuedData! definiteSelf.queuedData = nil - if definiteSelf.listener != nil { + if definiteSelf.observer != nil { definiteSelf.callback(data) } } } - #if swift(>=3.0) - let dispatchQueue = self.dispatchQueue ?? DispatchQueue.main - dispatchQueue.asyncAfter(deadline: DispatchTime.now() + Double(Int64(delay! * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: block) - #else - #if swift(>=2.3) - let dispatchQueue = self.dispatchQueue ?? dispatch_get_main_queue() - #else - let dispatchQueue = self.dispatchQueue ?? dispatch_get_main_queue()! - #endif - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay! * Double(NSEC_PER_SEC))), dispatchQueue, block) - - #endif + + let dispatchQueue = self.dispatchQueue ?? DispatchQueue.main + let deadline = DispatchTime.now() + DispatchTimeInterval.milliseconds(Int(sampleInterval * 1000)) + dispatchQueue.asyncAfter(deadline: deadline, execute: block) } } else { if let queue = self.dispatchQueue { - #if swift(>=3.0) - queue.async { - self.callback(data) - } - #else - dispatch_async(queue) { - self.callback(data) - } - #endif + queue.async { + self.callback(data) + } } else { callback(data) } } - return listener != nil - } - - /// Assigns a filter to the SignalListener. This lets you define conditions under which a listener should actually - /// receive the firing of a Singal. The closure that is passed an argument can decide whether the firing of a Signal - /// should actually be dispatched to its listener depending on the data fired. - /// - /// If the closeure returns true, the listener is informed of the fire. The default implementation always - /// returns true. - /// - /// - parameter filter: A closure that can decide whether the Signal fire should be dispatched to its listener. - /// - returns: Returns self so you can chain calls. - - #if swift(>=3.0) - @discardableResult - public func filter(where predicate: @escaping SignalFilter) -> SignalListener { - self.filter = predicate - return self - } - #else - public func filter(where predicate: SignalFilter) -> SignalListener { - self.filter = predicate - return self - } - #endif - - /// Tells the listener to queue up all signal fires until the elapsed time has passed and only once dispatch the - /// last received data. A delay of 0 will wait until the next runloop to dispatch the signal fire to the listener. - /// - parameter delay: The number of seconds to delay dispatch - /// - returns: Returns self so you can chain calls. - #if swift(>=3.0) - @discardableResult - public func queue(andDelayBy delay: TimeInterval) -> SignalListener { - self.delay = delay - return self - } - #else - public func queue(andDelayBy delay: NSTimeInterval) -> SignalListener { - self.delay = delay - return self - } - #endif - - /// Assigns a dispatch queue to the SignalListener. The queue is used for scheduling the listener calls. If not nil, - /// the callback is fired asynchronously on the specified queue. Otherwise, the block is run synchronously on the - /// posting thread (default behaviour). - /// - /// - parameter queue: A queue for performing the listener's calls. - /// - returns: Returns self so you can chain calls. - #if swift(>=3.0) - @discardableResult - public func dispatch(onQueue queue: DispatchQueue) -> SignalListener { - self.dispatchQueue = queue - return self - } - #else - public func dispatch(onQueue queue: dispatch_queue_t) -> SignalListener { - self.dispatchQueue = queue - return self - } - #endif - - /// Cancels the listener. This will detach the listening object from the Signal. - public func cancel() { - self.listener = nil + return observer != nil } } -infix operator => { associativity left precedence 0 } +infix operator => +/// Helper operator to fire signal data. public func => (signal: Signal, data: T) -> Void { signal.fire(data) } diff --git a/Signals/Signals.h b/Signals/Signals.h index 8f3b5b2..4242cb2 100644 --- a/Signals/Signals.h +++ b/Signals/Signals.h @@ -13,7 +13,3 @@ FOUNDATION_EXPORT double SignalsVersionNumber; //! Project version string for Signals. FOUNDATION_EXPORT const unsigned char SignalsVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/Signals/ios/AssociatedObject.swift b/Signals/ios/AssociatedObject.swift index a2f85f7..861863d 100644 --- a/Signals/ios/AssociatedObject.swift +++ b/Signals/ios/AssociatedObject.swift @@ -8,48 +8,22 @@ import ObjectiveC -#if swift(>=3.0) - func setAssociatedObject(_ object: AnyObject, value: T, associativeKey: UnsafePointer, policy: objc_AssociationPolicy) { - if let valueAsAnyObject = value as? AnyObject { - objc_setAssociatedObject(object, associativeKey, valueAsAnyObject, policy) - } - } +func setAssociatedObject(_ object: AnyObject, value: T, associativeKey: UnsafeRawPointer, policy: objc_AssociationPolicy) { + objc_setAssociatedObject(object, associativeKey, value, policy) +} - func getAssociatedObject(_ object: AnyObject, associativeKey: UnsafePointer) -> T? { - if let valueAsType = objc_getAssociatedObject(object, associativeKey) as? T { - return valueAsType - } else { - return nil - } +func getAssociatedObject(_ object: AnyObject, associativeKey: UnsafeRawPointer) -> T? { + if let valueAsType = objc_getAssociatedObject(object, associativeKey) as? T { + return valueAsType + } else { + return nil } +} - func getOrCreateAssociatedObject(_ object: AnyObject, associativeKey: UnsafePointer, defaultValue:T, policy: objc_AssociationPolicy) -> T { - if let valueAsType: T = getAssociatedObject(object, associativeKey: associativeKey) { - return valueAsType - } - setAssociatedObject(object, value: defaultValue, associativeKey: associativeKey, policy: policy) - return defaultValue; - } -#else - func setAssociatedObject(object: AnyObject, value: T, associativeKey: UnsafePointer, policy: objc_AssociationPolicy) { - if let valueAsAnyObject = value as? AnyObject { - objc_setAssociatedObject(object, associativeKey, valueAsAnyObject, policy) - } - } - - func getAssociatedObject(object: AnyObject, associativeKey: UnsafePointer) -> T? { - if let valueAsType = objc_getAssociatedObject(object, associativeKey) as? T { - return valueAsType - } else { - return nil - } - } - - func getOrCreateAssociatedObject(object: AnyObject, associativeKey: UnsafePointer, defaultValue:T, policy: objc_AssociationPolicy) -> T { - if let valueAsType: T = getAssociatedObject(object, associativeKey: associativeKey) { - return valueAsType - } - setAssociatedObject(object, value: defaultValue, associativeKey: associativeKey, policy: policy) - return defaultValue; +func getOrCreateAssociatedObject(_ object: AnyObject, associativeKey: UnsafeRawPointer, defaultValue:T, policy: objc_AssociationPolicy) -> T { + if let valueAsType: T = getAssociatedObject(object, associativeKey: associativeKey) { + return valueAsType } -#endif \ No newline at end of file + setAssociatedObject(object, value: defaultValue, associativeKey: associativeKey, policy: policy) + return defaultValue; +} diff --git a/Signals/ios/UIControl+Signals.swift b/Signals/ios/UIControl+Signals.swift index 0ee0e70..eaf06d9 100644 --- a/Signals/ios/UIControl+Signals.swift +++ b/Signals/ios/UIControl+Signals.swift @@ -10,7 +10,7 @@ import UIKit /// Extends UIControl with signals for all ui control events. -#if swift(>=3.0) + public extension UIControl { private struct AssociatedKeys { static var SignalDictionaryKey = "signals_signalKey" @@ -185,182 +185,6 @@ public extension UIControl { handleUIControlEvent(.editingDidEndOnExit) } } -#else -public extension UIControl { - private struct AssociatedKeys { - static var SignalDictionaryKey = "signals_signalKey" - } - - static let eventToKey: [UIControlEvents: NSString] = [ - .TouchDown: "TouchDown", - .TouchDownRepeat: "TouchDownRepeat", - .TouchDragInside: "TouchDragInside", - .TouchDragOutside: "TouchDragOutside", - .TouchDragEnter: "TouchDragEnter", - .TouchDragExit: "TouchDragExit", - .TouchUpInside: "TouchUpInside", - .TouchUpOutside: "TouchUpOutside", - .TouchCancel: "TouchCancel", - .ValueChanged: "ValueChanged", - .EditingDidBegin: "EditingDidBegin", - .EditingChanged: "EditingChanged", - .EditingDidEnd: "EditingDidEnd", - .EditingDidEndOnExit: "EditingDidEndOnExit"] - - // MARK - Public interface - - /// A signal that fires for each touch down control event. - public var onTouchDown: Signal<()> { - return getOrCreateSignalForUIControlEvent(.TouchDown); - } - - /// A signal that fires for each touch down repeat control event. - public var onTouchDownRepeat: Signal<()> { - return getOrCreateSignalForUIControlEvent(.TouchDownRepeat); - } - - /// A signal that fires for each touch drag inside control event. - public var onTouchDragInside: Signal<()> { - return getOrCreateSignalForUIControlEvent(.TouchDragInside); - } - - /// A signal that fires for each touch drag outside control event. - public var onTouchDragOutside: Signal<()> { - return getOrCreateSignalForUIControlEvent(.TouchDragOutside); - } - - /// A signal that fires for each touch drag enter control event. - public var onTouchDragEnter: Signal<()> { - return getOrCreateSignalForUIControlEvent(.TouchDragEnter); - } - - /// A signal that fires for each touch drag exit control event. - public var onTouchDragExit: Signal<()> { - return getOrCreateSignalForUIControlEvent(.TouchDragExit); - } - - /// A signal that fires for each touch up inside control event. - public var onTouchUpInside: Signal<()> { - return getOrCreateSignalForUIControlEvent(.TouchUpInside); - } - - /// A signal that fires for each touch up outside control event. - public var onTouchUpOutside: Signal<()> { - return getOrCreateSignalForUIControlEvent(.TouchUpOutside); - } - - /// A signal that fires for each touch cancel control event. - public var onTouchCancel: Signal<()> { - return getOrCreateSignalForUIControlEvent(.TouchCancel); - } - - /// A signal that fires for each value changed control event. - public var onValueChanged: Signal<()> { - return getOrCreateSignalForUIControlEvent(.ValueChanged); - } - - /// A signal that fires for each editing did begin control event. - public var onEditingDidBegin: Signal<()> { - return getOrCreateSignalForUIControlEvent(.EditingDidBegin); - } - - /// A signal that fires for each editing changed control event. - public var onEditingChanged: Signal<()> { - return getOrCreateSignalForUIControlEvent(.EditingChanged); - } - - /// A signal that fires for each editing did end control event. - public var onEditingDidEnd: Signal<()> { - return getOrCreateSignalForUIControlEvent(.EditingDidEnd); - } - - /// A signal that fires for each editing did end on exit control event. - public var onEditingDidEndOnExit: Signal<()> { - return getOrCreateSignalForUIControlEvent(.EditingDidEndOnExit); - } - - // MARK: - Internal interface - - private func getOrCreateSignalForUIControlEvent(event: UIControlEvents) -> Signal<()> { - guard let key = UIControl.eventToKey[event] else { - assertionFailure("Event type is not handled") - return Signal() - } - let dictionary = getOrCreateAssociatedObject(self, associativeKey: &AssociatedKeys.SignalDictionaryKey, defaultValue: NSMutableDictionary(), policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) - - if let signal = dictionary[key] as? Signal<()> { - return signal - } else { - let signal = Signal<()>() - dictionary[key] = signal - self.addTarget(self, action: Selector("eventHandler\(key)"), forControlEvents: event) - return signal - } - } - - private func handleUIControlEvent(uiControlEvent: UIControlEvents) { - getOrCreateSignalForUIControlEvent(uiControlEvent).fire() - } - - // MARK: - Event handlers - - private dynamic func eventHandlerTouchDown() { - handleUIControlEvent(.TouchDown) - } - - private dynamic func eventHandlerTouchDownRepeat() { - handleUIControlEvent(.TouchDownRepeat) - } - - private dynamic func eventHandlerTouchDragInside() { - handleUIControlEvent(.TouchDragInside) - } - - private dynamic func eventHandlerTouchDragOutside() { - handleUIControlEvent(.TouchDragOutside) - } - - private dynamic func eventHandlerTouchDragEnter() { - handleUIControlEvent(.TouchDragEnter) - } - - private dynamic func eventHandlerTouchDragExit() { - handleUIControlEvent(.TouchDragExit) - } - - private dynamic func eventHandlerTouchUpInside() { - handleUIControlEvent(.TouchUpInside) - } - - private dynamic func eventHandlerTouchUpOutside() { - handleUIControlEvent(.TouchUpOutside) - } - - private dynamic func eventHandlerTouchCancel() { - handleUIControlEvent(.TouchCancel) - } - - private dynamic func eventHandlerValueChanged() { - handleUIControlEvent(.ValueChanged) - } - - private dynamic func eventHandlerEditingDidBegin() { - handleUIControlEvent(.EditingDidBegin) - } - - private dynamic func eventHandlerEditingChanged() { - handleUIControlEvent(.EditingChanged) - } - - private dynamic func eventHandlerEditingDidEnd() { - handleUIControlEvent(.EditingDidEnd) - } - - private dynamic func eventHandlerEditingDidEndOnExit() { - handleUIControlEvent(.EditingDidEndOnExit) - } -} -#endif extension UIControlEvents: Hashable { public var hashValue: Int { diff --git a/SignalsTests/SignalsTests.swift b/SignalsTests/SignalsTests.swift index 80f4766..b04708e 100644 --- a/SignalsTests/SignalsTests.swift +++ b/SignalsTests/SignalsTests.swift @@ -10,15 +10,6 @@ import Foundation import XCTest @testable import Signals -#if swift(>=3.0) -#else - extension XCTestCase { - func measure(block: () -> Void){ - measureBlock(block) - } - } -#endif - class SignalsTests: XCTestCase { var emitter:SignalEmitter = SignalEmitter(); @@ -36,10 +27,10 @@ class SignalsTests: XCTestCase { var intSignalResult = 0 var stringSignalResult = "" - emitter.onInt.listen(on: self, callback: { (argument) in + emitter.onInt.subscribe(on: self, callback: { (argument) in intSignalResult = argument; }) - emitter.onString.listen(on: self, callback: { (argument) in + emitter.onString.subscribe(on: self, callback: { (argument) in stringSignalResult = argument; }) @@ -53,7 +44,7 @@ class SignalsTests: XCTestCase { func testNoArgumentFiring() { var signalCount = 0 - emitter.onNoParams.listen(on: self, callback: { () -> Void in + emitter.onNoParams.subscribe(on: self, callback: { () -> Void in signalCount += 1; }) @@ -66,7 +57,7 @@ class SignalsTests: XCTestCase { var intSignalResult = 0 var stringSignalResult = "" - emitter.onIntAndString.listen(on: self, callback: { (argument1, argument2) -> Void in + emitter.onIntAndString.subscribe(on: self, callback: { (argument1, argument2) -> Void in intSignalResult = argument1 stringSignalResult = argument2 }) @@ -81,7 +72,7 @@ class SignalsTests: XCTestCase { var dispatchCount = 0 var lastArgument = 0 - emitter.onInt.listen(on: self, callback: { (argument) in + emitter.onInt.subscribe(on: self, callback: { (argument) in dispatchCount += 1 lastArgument = argument }) @@ -97,11 +88,11 @@ class SignalsTests: XCTestCase { var dispatchCount = 0 var lastArgument = 0 - emitter.onInt.listen(on: self, callback: { (argument) in + emitter.onInt.subscribe(on: self, callback: { (argument) in dispatchCount += 1 lastArgument = argument }) - emitter.onInt.listen(on: self, callback: { (argument) in + emitter.onInt.subscribe(on: self, callback: { (argument) in dispatchCount += 1 lastArgument = argument + 1 }) @@ -119,71 +110,71 @@ class SignalsTests: XCTestCase { TestListener() ] - for listener in testListeners { - listener.listen(to: emitter) + for observer in testListeners { + observer.subscribe(to: emitter) } emitter.onInt.fire(1) emitter.onInt.fire(2) - for listener in testListeners { - XCTAssertEqual(listener.dispatchCount, 2, "Dispatched two times") - XCTAssertEqual(listener.lastArgument, 2, "Last argument catched with value 2") + for observer in testListeners { + XCTAssertEqual(observer.dispatchCount, 2, "Dispatched two times") + XCTAssertEqual(observer.lastArgument, 2, "Last argument catched with value 2") } } func testListeningOnce() { - let listener1 = TestListener() - let listener2 = TestListener() - let listener3 = TestListener() + let observer1 = TestListener() + let observer2 = TestListener() + let observer3 = TestListener() - listener1.listen(to: emitter) - listener2.listenOnce(to: emitter) - listener3.listenPast(to: emitter) + observer1.subscribe(to: emitter) + observer2.subscribeOnce(to: emitter) + observer3.subscribePast(to: emitter) emitter.onInt.fire(1) emitter.onInt.fire(2) - XCTAssertEqual(listener1.dispatchCount, 2, "Dispatched two times") - XCTAssertEqual(listener2.dispatchCount, 1, "Dispatched one time") - XCTAssertEqual(listener3.dispatchCount, 2, "Dispatched two times") + XCTAssertEqual(observer1.dispatchCount, 2, "Dispatched two times") + XCTAssertEqual(observer2.dispatchCount, 1, "Dispatched one time") + XCTAssertEqual(observer3.dispatchCount, 2, "Dispatched two times") } func testListeningPastOnceAlreadyFired() { - let listener = TestListener() + let observer = TestListener() emitter.onInt.fire(1) emitter.onInt.fire(2) - listener.listenPastOnce(to: emitter) + observer.subscribePastOnce(to: emitter) emitter.onInt.fire(3) emitter.onInt.fire(4) - XCTAssertEqual(listener.dispatchCount, 1, "Dispatched once") - XCTAssertEqual(listener.lastArgument, 2, "Remembered the most recent data") + XCTAssertEqual(observer.dispatchCount, 1, "Dispatched once") + XCTAssertEqual(observer.lastArgument, 2, "Remembered the most recent data") } func testListeningPastOnceNotFiredYet() { - let listener = TestListener() + let observer = TestListener() - listener.listenPastOnce(to: emitter) + observer.subscribePastOnce(to: emitter) emitter.onInt.fire(1) emitter.onInt.fire(2) - XCTAssertEqual(listener.dispatchCount, 1, "Dispatched once") - XCTAssertEqual(listener.lastArgument, 1, "Remembered only the relevant data") + XCTAssertEqual(observer.dispatchCount, 1, "Dispatched once") + XCTAssertEqual(observer.lastArgument, 1, "Remembered only the relevant data") } func testRemovingListeners() { var dispatchCount: Int = 0 - emitter.onInt.listen(on: self, callback: { (argument) in + emitter.onInt.subscribe(on: self, callback: { (argument) in dispatchCount += 1 }) - emitter.onInt.listen(on: self, callback: { (argument) in + emitter.onInt.subscribe(on: self, callback: { (argument) in dispatchCount += 1 }) - emitter.onInt.dettach(from: self) + emitter.onInt.cancelSubscription(for: self) emitter.onInt.fire(1) XCTAssertEqual(dispatchCount, 0, "Shouldn't have catched signal fire") @@ -192,27 +183,27 @@ class SignalsTests: XCTestCase { func testRemovingAllListeners() { var dispatchCount: Int = 0 - emitter.onInt.listen(on: self, callback: { (argument) in + emitter.onInt.subscribe(on: self, callback: { (argument) in dispatchCount += 1 }) - emitter.onInt.listen(on: self, callback: { (argument) in + emitter.onInt.subscribe(on: self, callback: { (argument) in dispatchCount += 1 }) - emitter.onInt.dettachAllListeners() + emitter.onInt.cancelAllSubscriptions() emitter.onInt.fire(1) XCTAssertEqual(dispatchCount, 0, "Shouldn't have catched signal fire") } func testAutoRemoveWeakListeners() { - var listener: TestListener? = TestListener() - listener!.listen(to: emitter) - listener = nil + var observer: TestListener? = TestListener() + observer!.subscribe(to: emitter) + observer = nil emitter.onInt.fire(1) - XCTAssertEqual(emitter.onInt.listeners.count, 0, "Weak listener should have been collected") + XCTAssertEqual(emitter.onInt.observers.count, 0, "Weak observer should have been collected") } func testPostListening() { @@ -222,7 +213,7 @@ class SignalsTests: XCTestCase { emitter.onIntAndString => (intArgument:1, stringArgument:"test") - emitter.onIntAndString.listenPast(on: self, callback: { (argument1, argument2) -> Void in + emitter.onIntAndString.subscribePast(on: self, callback: { (argument1, argument2) -> Void in intSignalResult = argument1 stringSignalResult = argument2 dispatchCount += 1 @@ -241,7 +232,7 @@ class SignalsTests: XCTestCase { var stringSignalResult = "" var dispatchCount = 0 - emitter.onIntAndString.listen(on: self, callback: { (argument1, argument2) -> Void in + emitter.onIntAndString.subscribe(on: self, callback: { (argument1, argument2) -> Void in intSignalResult = argument1 stringSignalResult = argument2 dispatchCount += 1 @@ -264,7 +255,7 @@ class SignalsTests: XCTestCase { var stringSignalResult = "" var dispatchCount = 0 - emitter.onIntAndString.listenOnce(on: self, callback: { (argument1, argument2) -> Void in + emitter.onIntAndString.subscribeOnce(on: self, callback: { (argument1, argument2) -> Void in intSignalResult = argument1 stringSignalResult = argument2 dispatchCount += 1 @@ -283,12 +274,12 @@ class SignalsTests: XCTestCase { func testCancellingListeners() { var dispatchCount = 0 - let listener = emitter.onIntAndString.listen(on: self, callback: { (argument1, argument2) -> Void in + let observer = emitter.onIntAndString.subscribe(on: self, callback: { (argument1, argument2) -> Void in dispatchCount += 1 }) emitter.onIntAndString => (intArgument:1, stringArgument:"test") - listener.cancel() + observer.cancel() emitter.onIntAndString => (intArgument:1, stringArgument:"test") XCTAssertEqual(dispatchCount, 1, "Filtered fires") @@ -299,7 +290,7 @@ class SignalsTests: XCTestCase { emitter.onNoParams.fire() - emitter.onNoParams.listenPast(on: self, callback: { () -> Void in + emitter.onNoParams.subscribePast(on: self, callback: { () -> Void in dispatchCount += 1 }) @@ -309,8 +300,8 @@ class SignalsTests: XCTestCase { func testRemoveOwnListenerWhileFiring() { var dispatchCount = 0 - emitter.onIntAndString.listenOnce(on: self) { (intArgument, stringArgument) -> Void in - self.emitter.onIntAndString.dettach(from: self) + emitter.onIntAndString.subscribeOnce(on: self) { (intArgument, stringArgument) -> Void in + self.emitter.onIntAndString.cancelSubscription(for: self) dispatchCount += 1 } emitter.onIntAndString => (intArgument:1, stringArgument:"test") @@ -321,22 +312,22 @@ class SignalsTests: XCTestCase { func testRemovePreviousListenersWhileFiring() { var dispatchCount = 0 - let listener1 = NSObject() - let listener2 = NSObject() - let listener3 = NSObject() + let observer1 = NSObject() + let observer2 = NSObject() + let observer3 = NSObject() - emitter.onIntAndString.listen(on: listener1) { (intArgument, stringArgument) -> Void in + emitter.onIntAndString.subscribe(on: observer1) { (intArgument, stringArgument) -> Void in dispatchCount += 1 } - emitter.onIntAndString.listen(on: listener2) { (intArgument, stringArgument) -> Void in + emitter.onIntAndString.subscribe(on: observer2) { (intArgument, stringArgument) -> Void in dispatchCount += 1 - self.emitter.onIntAndString.dettach(from: listener1) + self.emitter.onIntAndString.cancelSubscription(for: observer1) } - emitter.onIntAndString.listen(on: listener3) { (intArgument, stringArgument) -> Void in + emitter.onIntAndString.subscribe(on: observer3) { (intArgument, stringArgument) -> Void in dispatchCount += 1 - self.emitter.onIntAndString.dettach(from: listener2) + self.emitter.onIntAndString.cancelSubscription(for: observer2) } - self.emitter.onIntAndString.dettach(from: listener2) + self.emitter.onIntAndString.cancelSubscription(for: observer2) emitter.onIntAndString => (intArgument:1, stringArgument:"test") emitter.onIntAndString => (intArgument:1, stringArgument:"test") @@ -347,21 +338,21 @@ class SignalsTests: XCTestCase { func testRemoveUpcomingListenersWhileFiring() { var dispatchCount = 0 - let listener1 = NSObject() - let listener2 = NSObject() - let listener3 = NSObject() + let observer1 = NSObject() + let observer2 = NSObject() + let observer3 = NSObject() - emitter.onIntAndString.listen(on: listener1) { (intArgument, stringArgument) -> Void in + emitter.onIntAndString.subscribe(on: observer1) { (intArgument, stringArgument) -> Void in dispatchCount += 1 - self.emitter.onIntAndString.dettach(from: listener2) + self.emitter.onIntAndString.cancelSubscription(for: observer2) } - emitter.onIntAndString.listen(on: listener2) { (intArgument, stringArgument) -> Void in + emitter.onIntAndString.subscribe(on: observer2) { (intArgument, stringArgument) -> Void in dispatchCount += 1 } - emitter.onIntAndString.listen(on: listener3) { (intArgument, stringArgument) -> Void in + emitter.onIntAndString.subscribe(on: observer3) { (intArgument, stringArgument) -> Void in dispatchCount += 1 } - self.emitter.onIntAndString.dettach(from: listener2) + self.emitter.onIntAndString.cancelSubscription(for: observer2) emitter.onIntAndString => (intArgument:1, stringArgument:"test") emitter.onIntAndString => (intArgument:1, stringArgument:"test") @@ -372,21 +363,21 @@ class SignalsTests: XCTestCase { func testRemoveAllListenersWhileFiring() { var dispatchCount = 0 - let listener1 = NSObject() - let listener2 = NSObject() - let listener3 = NSObject() + let observer1 = NSObject() + let observer2 = NSObject() + let observer3 = NSObject() - emitter.onIntAndString.listen(on: listener1) { (intArgument, stringArgument) -> Void in + emitter.onIntAndString.subscribe(on: observer1) { (intArgument, stringArgument) -> Void in dispatchCount += 1 - self.emitter.onIntAndString.dettachAllListeners() + self.emitter.onIntAndString.cancelAllSubscriptions() } - emitter.onIntAndString.listen(on: listener2) { (intArgument, stringArgument) -> Void in + emitter.onIntAndString.subscribe(on: observer2) { (intArgument, stringArgument) -> Void in dispatchCount += 1 } - emitter.onIntAndString.listen(on: listener3) { (intArgument, stringArgument) -> Void in + emitter.onIntAndString.subscribe(on: observer3) { (intArgument, stringArgument) -> Void in dispatchCount += 1 } - self.emitter.onIntAndString.dettach(from: listener2) + self.emitter.onIntAndString.cancelSubscription(for: observer2) emitter.onIntAndString => (intArgument:1, stringArgument:"test") emitter.onIntAndString => (intArgument:1, stringArgument:"test") @@ -410,7 +401,7 @@ class SignalsTests: XCTestCase { self.measure() { var dispatchCount = 0 for _ in 0..<10 { - self.emitter.onIntAndString.listen(on: self) { (argument1, argument2) -> Void in + self.emitter.onIntAndString.subscribe(on: self) { (argument1, argument2) -> Void in dispatchCount += 1 } } diff --git a/SignalsTests/SingalQueueTests.swift b/SignalsTests/SingalQueueTests.swift index df9ffba..ff86be4 100644 --- a/SignalsTests/SingalQueueTests.swift +++ b/SignalsTests/SingalQueueTests.swift @@ -9,19 +9,6 @@ import Foundation import XCTest -#if swift(>=3.0) -#else - extension XCTestCase { - @nonobjc func expectation(description descripton: String) -> XCTestExpectation { - return self.expectationWithDescription(descripton) - } - - @nonobjc func waitForExpectations(timeout timeout: NSTimeInterval, handler: XCWaitCompletionHandler?) { - waitForExpectationsWithTimeout(timeout, handler: handler) - } - } -#endif - class SignalQueueTests: XCTestCase { var emitter:SignalEmitter = SignalEmitter(); @@ -38,194 +25,165 @@ class SignalQueueTests: XCTestCase { func testBasicFiring() { let expectation = self.expectation(description: "queuedDispatch") - emitter.onInt.listen(on: self, callback: { (argument) in + emitter.onInt.subscribe(on: self, callback: { (argument) in XCTAssertEqual(argument, 1, "Last data catched") expectation.fulfill() - }).queue(andDelayBy: 0.1) + }).sample(every: 0.1) emitter.onInt.fire(1); - waitForExpectations(timeout: 1.0, handler: nil) + waitForExpectations(timeout: 10.0, handler: nil) } func testDispatchQueueing() { let expectation = self.expectation(description: "queuedDispatch") - emitter.onInt.listen(on: self, callback: { (argument) in + emitter.onInt.subscribe(on: self, callback: { (argument) in XCTAssertEqual(argument, 3, "Last data catched") expectation.fulfill() - }).queue(andDelayBy: 0.1) + }).sample(every: 0.1) emitter.onInt.fire(1); emitter.onInt.fire(2); emitter.onInt.fire(3); - waitForExpectations(timeout: 1.0, handler: nil) + waitForExpectations(timeout: 10.0, handler: nil) } func testNoQueueTimeFiring() { let expectation = self.expectation(description: "queuedDispatch") - emitter.onInt.listen(on: self, callback: { (argument) in + emitter.onInt.subscribe(on: self, callback: { (argument) in XCTAssertEqual(argument, 3, "Last data catched") expectation.fulfill() - }).queue(andDelayBy: 0.0) + }).sample(every: 0.0) emitter.onInt.fire(1); emitter.onInt.fire(2); emitter.onInt.fire(3); - waitForExpectations(timeout: 1.0, handler: nil) + waitForExpectations(timeout: 10.0, handler: nil) } func testConditionalListening() { let expectation = self.expectation(description: "queuedDispatch") - emitter.onIntAndString.listen(on: self, callback: { (argument1, argument2) -> Void in + emitter.onIntAndString.subscribe(on: self, callback: { (argument1, argument2) -> Void in XCTAssertEqual(argument1, 2, "argument1 catched") XCTAssertEqual(argument2, "test2", "argument2 catched") expectation.fulfill() - }).queue(andDelayBy: 0.01).filter { $0 == 2 && $1 == "test2" } + }).sample(every: 0.01).filter { $0 == 2 && $1 == "test2" } emitter.onIntAndString.fire((intArgument:1, stringArgument:"test")) emitter.onIntAndString.fire((intArgument:1, stringArgument:"test2")) emitter.onIntAndString.fire((intArgument:2, stringArgument:"test2")) emitter.onIntAndString.fire((intArgument:1, stringArgument:"test3")) - waitForExpectations(timeout: 1.0, handler: nil) + waitForExpectations(timeout: 10.0, handler: nil) } func testCancellingListeners() { let expectation = self.expectation(description: "queuedDispatch") - let listener = emitter.onIntAndString.listen(on: self, callback: { (argument1, argument2) -> Void in + let observer = emitter.onIntAndString.subscribe(on: self, callback: { (argument1, argument2) -> Void in XCTFail("Listener should have been canceled") - }).queue(andDelayBy: 0.01) + }).sample(every: 0.01) emitter.onIntAndString.fire((intArgument:1, stringArgument:"test")) emitter.onIntAndString.fire((intArgument:1, stringArgument:"test")) - listener.cancel() + observer.cancel() let block = { - // Cancelled listener didn't dispatch + // Cancelled observer didn't dispatch expectation.fulfill() } - #if swift(>=3.0) - DispatchQueue.main.asyncAfter( deadline: DispatchTime.now() + Double(Int64(0.05 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: block) - #else - dispatch_after( dispatch_time(DISPATCH_TIME_NOW, Int64(0.05 * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), block) - #endif + DispatchQueue.main.asyncAfter( deadline: DispatchTime.now() + Double(Int64(0.05 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: block) - waitForExpectations(timeout: 1.0, handler: nil) + waitForExpectations(timeout: 10.0, handler: nil) } func testListeningNoData() { let expectation = self.expectation(description: "queuedDispatch") var dispatchCount = 0 - emitter.onNoParams.listen(on: self, callback: { () -> Void in + emitter.onNoParams.subscribe(on: self, callback: { () -> Void in dispatchCount += 1 XCTAssertEqual(dispatchCount, 1, "Dispatched only once") expectation.fulfill() - }).queue(andDelayBy: 0.01) + }).sample(every: 0.01) emitter.onNoParams.fire() emitter.onNoParams.fire() emitter.onNoParams.fire() - waitForExpectations(timeout: 1.0, handler: nil) + waitForExpectations(timeout: 10.0, handler: nil) } func testListenerProperty() { - var listener1: NSObject? = NSObject() - var listener2: NSObject? = NSObject() + var observer1: NSObject? = NSObject() + var observer2: NSObject? = NSObject() - emitter.onInt.listen(on: listener1!) { _ = $0 } - emitter.onInt.listen(on: listener2!) { _ = $0 } + emitter.onInt.subscribe(on: observer1!) { _ = $0 } + emitter.onInt.subscribe(on: observer2!) { _ = $0 } - XCTAssertEqual(emitter.onInt.listeners.count, 2, "Should have two listener") + XCTAssertEqual(emitter.onInt.observers.count, 2, "Should have two observer") - listener1 = nil - XCTAssertEqual(emitter.onInt.listeners.count, 1, "Should have one listener") + observer1 = nil + XCTAssertEqual(emitter.onInt.observers.count, 1, "Should have one observer") - listener2 = nil - XCTAssertEqual(emitter.onInt.listeners.count, 0, "Should have zero listener") + observer2 = nil + XCTAssertEqual(emitter.onInt.observers.count, 0, "Should have zero observer") } func testListeningOnDispatchQueue() { let firstQueueLabel = "com.signals.queue.first"; let secondQueueLabel = "com.signals.queue.second"; - #if swift(>=3.0) - let firstQueue = DispatchQueue(label: firstQueueLabel) - let secondQueue = DispatchQueue(label: secondQueueLabel, attributes: DispatchQueue.Attributes.concurrent) - #else - let firstQueue = dispatch_queue_create(firstQueueLabel, DISPATCH_QUEUE_SERIAL) - let secondQueue = dispatch_queue_create(secondQueueLabel, DISPATCH_QUEUE_CONCURRENT) - #endif + let firstQueue = DispatchQueue(label: firstQueueLabel) + let secondQueue = DispatchQueue(label: secondQueueLabel, attributes: DispatchQueue.Attributes.concurrent) + let firstListener = NSObject() let secondListener = NSObject() let firstExpectation = expectation(description: "firstDispatchOnQueue") - emitter.onInt.listen(on: firstListener, callback: { (argument) in - #if swift(>=3.0) - let currentQueueLabel = String(validatingUTF8: __dispatch_queue_get_label(nil)) - #else - let currentQueueLabel = String(UTF8String: dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)) - #endif + emitter.onInt.subscribe(on: firstListener, callback: { (argument) in + let currentQueueLabel = String(validatingUTF8: __dispatch_queue_get_label(nil)) XCTAssertTrue(firstQueueLabel == currentQueueLabel) firstExpectation.fulfill() }).dispatch(onQueue: firstQueue) let secondExpectation = expectation(description: "secondDispatchOnQueue") - emitter.onInt.listen(on: secondListener, callback: { (argument) in - #if swift(>=3.0) - let currentQueueLabel = String(validatingUTF8: __dispatch_queue_get_label(nil)) - #else - let currentQueueLabel = String(UTF8String: dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)) - #endif + emitter.onInt.subscribe(on: secondListener, callback: { (argument) in + let currentQueueLabel = String(validatingUTF8: __dispatch_queue_get_label(nil)) XCTAssertTrue(secondQueueLabel == currentQueueLabel) secondExpectation.fulfill() }).dispatch(onQueue: secondQueue) emitter.onInt.fire(10) - waitForExpectations(timeout: 1.0, handler: nil) + waitForExpectations(timeout: 10.0, handler: nil) } func testUsesCurrentQueueByDefault() { let queueLabel = "com.signals.queue"; - #if swift(>=3.0) - let queue = DispatchQueue(label: queueLabel, attributes: DispatchQueue.Attributes.concurrent) - #else - let queue = dispatch_queue_create(queueLabel, DISPATCH_QUEUE_CONCURRENT) - #endif + let queue = DispatchQueue(label: queueLabel, attributes: DispatchQueue.Attributes.concurrent) - let listener = NSObject() + let observer = NSObject() let expectation = self.expectation(description: "receivedCallbackOnQueue") - emitter.onInt.listen(on: listener, callback: { (argument) in - #if swift(>=3.0) - let currentQueueLabel = String(validatingUTF8: __dispatch_queue_get_label(nil)) - #else - let currentQueueLabel = String(UTF8String: dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)) - #endif + emitter.onInt.subscribe(on: observer, callback: { (argument) in + let currentQueueLabel = String(validatingUTF8: __dispatch_queue_get_label(nil)) + XCTAssertTrue(queueLabel == currentQueueLabel) expectation.fulfill() }) - #if swift(>=3.0) - queue.async { - self.emitter.onInt.fire(10) - } - #else - dispatch_async(queue) { - self.emitter.onInt.fire(10) - } - #endif - - waitForExpectations(timeout: 1.0, handler: nil) + queue.async { + self.emitter.onInt.fire(10) + } + + waitForExpectations(timeout: 10.0, handler: nil) } } diff --git a/SignalsTests/TestListener.swift b/SignalsTests/TestListener.swift index ab1c4b1..4778488 100644 --- a/SignalsTests/TestListener.swift +++ b/SignalsTests/TestListener.swift @@ -1,5 +1,5 @@ // -// SignalListener.swift +// Observer.swift // Signals // // Created by Tuomas Artman on 16.8.2014. @@ -13,32 +13,32 @@ class TestListener { var dispatchCount: Int = 0; var lastArgument: Int = 0; - func listen(to emitter: SignalEmitter) { - emitter.onInt.listen(on: self, callback: { + func subscribe(to emitter: SignalEmitter) { + emitter.onInt.subscribe(on: self, callback: { [unowned self] (argument) in self.dispatchCount += 1 self.lastArgument = argument; }) } - func listenOnce(to emitter: SignalEmitter) { - emitter.onInt.listenOnce(on: self, callback: { + func subscribeOnce(to emitter: SignalEmitter) { + emitter.onInt.subscribeOnce(on: self, callback: { [unowned self] (argument) in self.dispatchCount += 1 self.lastArgument = argument; }) } - func listenPastOnce(to emitter: SignalEmitter) { - emitter.onInt.listenPastOnce(on: self, callback: { + func subscribePastOnce(to emitter: SignalEmitter) { + emitter.onInt.subscribePastOnce(on: self, callback: { [unowned self] (argument) in self.dispatchCount += 1 self.lastArgument = argument }) } - func listenPast(to emitter: SignalEmitter) { - emitter.onInt.listenPast(on: self, callback: { + func subscribePast(to emitter: SignalEmitter) { + emitter.onInt.subscribePast(on: self, callback: { [unowned self] (argument) in self.dispatchCount += 1 self.lastArgument = argument; diff --git a/SignalsTests/UIControl+SignalsTests.swift b/SignalsTests/UIControl+SignalsTests.swift index 89f3960..c5dc393 100644 --- a/SignalsTests/UIControl+SignalsTests.swift +++ b/SignalsTests/UIControl+SignalsTests.swift @@ -13,7 +13,7 @@ import XCTest import Signals class UIControl_SignalsTests: XCTestCase { - func testActionObservation() { + func test_actionObservation() { let button = UIButton() var onTouchDownCount = 0 @@ -31,74 +31,61 @@ class UIControl_SignalsTests: XCTestCase { var onEditingDidEndCount = 0 var onEditingDidEndOnExitCount = 0 - button.onTouchDown.listen(on: self) { + button.onTouchDown.subscribe(on: self) { onTouchDownCount += 1 } - button.onTouchDownRepeat.listen(on: self) { + button.onTouchDownRepeat.subscribe(on: self) { onTouchDownRepeatCount += 1 } - button.onTouchDragInside.listen(on: self) { + button.onTouchDragInside.subscribe(on: self) { onTouchDragInsideCount += 1 } - button.onTouchDragOutside.listen(on: self) { + button.onTouchDragOutside.subscribe(on: self) { onTouchDragOutsideCount += 1 } - button.onTouchDragEnter.listen(on: self) { + button.onTouchDragEnter.subscribe(on: self) { onTouchDragEnterCount += 1 } - button.onTouchDragExit.listen(on: self) { + button.onTouchDragExit.subscribe(on: self) { onTouchDragExitCount += 1 } - button.onTouchUpInside.listen(on: self) { + button.onTouchUpInside.subscribe(on: self) { onTouchUpInsideCount += 1 } - button.onTouchUpOutside.listen(on: self) { + button.onTouchUpOutside.subscribe(on: self) { onTouchUpOutsideCount += 1 } - button.onTouchCancel.listen(on: self) { + button.onTouchCancel.subscribe(on: self) { onTouchCancelCount += 1 } - button.onValueChanged.listen(on: self) { + button.onValueChanged.subscribe(on: self) { onValueChangedCount += 1 } - button.onEditingDidBegin.listen(on: self) { + button.onEditingDidBegin.subscribe(on: self) { onEditingDidBeginCount += 1 } - button.onEditingChanged.listen(on: self) { + button.onEditingChanged.subscribe(on: self) { onEditingChangedCount += 1 } - button.onEditingDidEnd.listen(on: self) { + button.onEditingDidEnd.subscribe(on: self) { onEditingDidEndCount += 1 } - button.onEditingDidEndOnExit.listen(on: self) { + button.onEditingDidEndOnExit.subscribe(on: self) { onEditingDidEndOnExitCount += 1 } - #if swift(>=3.0) - let events: [UIControlEvents] = [.touchDown, .touchDownRepeat, .touchDragInside, .touchDragOutside, .touchDragEnter, - .touchDragExit, .touchUpInside, .touchUpOutside, .touchCancel, .valueChanged, .editingDidBegin, .editingChanged, - .editingDidEnd, .editingDidEndOnExit]; - - for event in events { - let actions = button.actions(forTarget: button, forControlEvent: event); - for action in actions! { - button.perform(Selector(action)) - } - } - #else - let events: [UIControlEvents] = [.TouchDown, .TouchDownRepeat, .TouchDragInside, .TouchDragOutside, .TouchDragEnter, - .TouchDragExit, .TouchUpInside, .TouchUpOutside, .TouchCancel, .ValueChanged, .EditingDidBegin, .EditingChanged, - .EditingDidEnd, .EditingDidEndOnExit]; - - for event in events { - let actions = button.actionsForTarget(button, forControlEvent: event); - for action in actions! { - button.performSelector(Selector(action)) - } - } - #endif - + let events: [UIControlEvents] = [.touchDown, .touchDownRepeat, .touchDragInside, .touchDragOutside, + .touchDragEnter, .touchDragExit, .touchUpInside, .touchUpOutside, + .touchCancel, .valueChanged, .editingDidBegin, .editingChanged, + .editingDidEnd, .editingDidEndOnExit]; + for event in events { + let actions = button.actions(forTarget: button, forControlEvent: event); + for action in actions! { + button.perform(Selector(action)) + } + } + XCTAssertEqual(onTouchDownCount, 1, "Should have triggered once") XCTAssertEqual(onTouchDownRepeatCount, 1, "Should have triggered once") XCTAssertEqual(onTouchDragInsideCount, 1, "Should have triggered once") From c2c97dd2e6a69954c36e1863f1dcdbfd3bb246f0 Mon Sep 17 00:00:00 2001 From: Tuomas Artman Date: Wed, 19 Oct 2016 00:00:53 -0700 Subject: [PATCH 2/5] Updating travis --- .travis.yml | 2 +- Signals/Signal.swift | 20 ++++++++------------ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5cde273..a05d06f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode8.0 +osx_image: xcode8 env: global: - LC_CTYPE=en_US.UTF-8 diff --git a/Signals/Signal.swift b/Signals/Signal.swift index 5eeca17..49acccf 100644 --- a/Signals/Signal.swift +++ b/Signals/Signal.swift @@ -35,9 +35,9 @@ final public class Signal { get { return signalListeners.filter { return $0.observer != nil - }.map { - (signal) -> AnyObject in - return signal.observer! + }.map { + (signal) -> AnyObject in + return signal.observer! } } } @@ -94,7 +94,7 @@ final public class Signal { } return signalListener } - + /// Subscribes an observer to the `Signal` and invokes its callback immediately with the last data fired by the /// `Signal` if it has fired at least once and if the `retainLastData` property has been set to true. If it has /// not been fired yet, it will continue listening until it fires for the first time. @@ -113,7 +113,7 @@ final public class Signal { } return signalListener } - + /// Fires the `Singal`. /// /// - parameter data: The data to fire the `Signal` with. @@ -124,7 +124,7 @@ final public class Signal { for signalListener in signalListeners { if signalListener.filter == nil || signalListener.filter!(data) == true { - _ = signalListener.dispatch(data: data) + _ = signalListener.dispatch(data: data) } } } @@ -143,7 +143,7 @@ final public class Signal { /// Cancels all subscriptions for the `Signal`. public func cancelAllSubscriptions() { - signalListeners.removeAll(keepingCapacity: false) + signalListeners.removeAll(keepingCapacity: false) } /// Clears the last fired data from the `Signal` and resets the fire count. @@ -166,7 +166,6 @@ final public class Signal { } } } - } /// A SignalLister represenents an instance and its association with a `Signal`. @@ -190,7 +189,6 @@ final public class SignalSubscription { self.observer = observer self.callback = callback } - #endif /// Assigns a filter to the `SignalSubscription`. This lets you define conditions under which a observer should actually /// receive the firing of a `Singal`. The closure that is passed an argument can decide whether the firing of a @@ -219,7 +217,7 @@ final public class SignalSubscription { self.sampleInterval = sampleInterval return self } - + /// Assigns a dispatch queue to the `SignalSubscription`. The queue is used for scheduling the observer calls. If not /// nil, the callback is fired asynchronously on the specified queue. Otherwise, the block is run synchronously /// on the posting thread, which is its default behaviour. @@ -252,7 +250,6 @@ final public class SignalSubscription { if queuedData != nil { queuedData = data } else { - // Set up queue queuedData = data let block = { [weak self] () -> Void in if let definiteSelf = self { @@ -263,7 +260,6 @@ final public class SignalSubscription { } } } - let dispatchQueue = self.dispatchQueue ?? DispatchQueue.main let deadline = DispatchTime.now() + DispatchTimeInterval.milliseconds(Int(sampleInterval * 1000)) dispatchQueue.asyncAfter(deadline: deadline, execute: block) From 0f30a15740872d9bc3afd879449bd5cafcb73f88 Mon Sep 17 00:00:00 2001 From: Tuomas Artman Date: Wed, 19 Oct 2016 22:33:24 -0700 Subject: [PATCH 3/5] Updated travis.config --- .travis.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index a05d06f..7e9db0a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,16 +15,16 @@ env: - WATCHOS_SDK=watchsimulator3.0 matrix: - - DESTINATION="OS=8.1,name=iPhone 4S" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="YES" - - DESTINATION="OS=8.2,name=iPhone 5" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" - - DESTINATION="OS=8.3,name=iPhone 5S" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" - - DESTINATION="OS=8.4,name=iPhone 6" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" - - DESTINATION="OS=9.0,name=iPhone 6 Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" - - DESTINATION="OS=9.1,name=iPhone 6S" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" + - DESTINATION="OS=9.0,name=iPhone 4S" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="YES" + - DESTINATION="OS=9.1,name=iPhone 5" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" + - DESTINATION="OS=9.2,name=iPhone 5S" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" + - DESTINATION="OS=9.3,name=iPhone 6" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" + - DESTINATION="OS=10.0,name=iPhone 6 Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" + - DESTINATION="OS=10.0,name=iPhone 6S" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" - DESTINATION="OS=10.0,name=iPhone 6S Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" - DESTINATION="arch=x86_64" SCHEME="$OSX_FRAMEWORK_SCHEME" SDK="$OSX_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" - - DESTINATION="OS=9.1,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" - - DESTINATION="OS=2.1,name=Apple Watch - 38mm" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" SDK="$WATCHOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD_LINT="NO" + - DESTINATION="OS=10.0,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" + - DESTINATION="OS=3.0,name=Apple Watch - 38mm" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" SDK="$WATCHOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD_LINT="NO" before_install: - gem install slather -N From 992a7e5ada3055e77d0b6d7cb90f1ff9ae808174 Mon Sep 17 00:00:00 2001 From: Tuomas Artman Date: Wed, 19 Oct 2016 23:13:37 -0700 Subject: [PATCH 4/5] Updated documentation, change UIControl signal types to void --- .travis.yml | 2 +- CHANGELOG.md | 97 ----------------------------- README.md | 52 ++++++++-------- Signals.xcodeproj/project.pbxproj | 2 - Signals/ios/UIControl+Signals.swift | 77 +++++++++++------------ 5 files changed, 63 insertions(+), 167 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/.travis.yml b/.travis.yml index 7e9db0a..caa1d45 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,7 @@ env: - DESTINATION="OS=10.0,name=iPhone 6S" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" - DESTINATION="OS=10.0,name=iPhone 6S Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO" - DESTINATION="arch=x86_64" SCHEME="$OSX_FRAMEWORK_SCHEME" SDK="$OSX_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" - - DESTINATION="OS=10.0,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" + - DESTINATION="OS=10.0,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" - DESTINATION="OS=3.0,name=Apple Watch - 38mm" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" SDK="$WATCHOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD_LINT="NO" before_install: diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 99ee10d..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,97 +0,0 @@ -# Change Log - -## [3.0.0](https://github.com/artman/signals/tree/3.0.0) (2016-04-21) -[Full Changelog](https://github.com/artman/signals/compare/2.3.0...3.0.0) - -**Implemented enhancements:** - -- Retaining old data fired on Signals should be opt-in [\#28](https://github.com/artman/Signals/pull/28) ([netizen01](https://github.com/netizen01)) - -**Fixed bugs:** - -- Makes previousData and fireCount read only. Update podspec to 3.0.0 [\#32](https://github.com/artman/Signals/pull/32) ([artman](https://github.com/artman)) - -**Closed issues:** - -- Previous data and fire count should be read only [\#31](https://github.com/artman/Signals/issues/31) -- Retaining old data fired on Signals should be opt-in [\#30](https://github.com/artman/Signals/issues/30) - -**Merged pull requests:** - -- Fixing Swift 3 Warnings [\#29](https://github.com/artman/Signals/pull/29) ([hibento](https://github.com/hibento)) - -## [2.3.0](https://github.com/artman/signals/tree/2.3.0) (2016-01-31) -[Full Changelog](https://github.com/artman/signals/compare/2.2.0...2.3.0) - -**Implemented enhancements:** - -- Extend UIControl to support signals for all UIControlEvents [\#27](https://github.com/artman/Signals/issues/27) -- Support OSX, watchOS and tvOS platforms [\#25](https://github.com/artman/Signals/issues/25) -- Document changes across releases [\#24](https://github.com/artman/Signals/issues/24) -- Added changelog [\#26](https://github.com/artman/Signals/pull/26) ([artman](https://github.com/artman)) - -## [2.2.0](https://github.com/artman/signals/tree/2.2.0) (2015-12-21) -[Full Changelog](https://github.com/artman/signals/compare/2.1.1...2.2.0) - -**Closed issues:** - -- Wrong version in podspec [\#17](https://github.com/artman/Signals/issues/17) - -**Merged pull requests:** - -- Support for all Apple platforms [\#23](https://github.com/artman/Signals/pull/23) ([artman](https://github.com/artman)) -- Set the dispatch queue explicitly [\#20](https://github.com/artman/Signals/pull/20) ([sgl0v](https://github.com/sgl0v)) -- Changed Deployment Target to iOS 8.0 [\#19](https://github.com/artman/Signals/pull/19) ([hibento](https://github.com/hibento)) - -## [2.1.1](https://github.com/artman/signals/tree/2.1.1) (2015-10-20) -[Full Changelog](https://github.com/artman/signals/compare/2.1.0...2.1.1) - -**Implemented enhancements:** - -- Modernized syntax, added missing unit tests [\#13](https://github.com/artman/Signals/pull/13) ([artman](https://github.com/artman)) - -**Closed issues:** - -- `carthage update` fails due to ENABLE\_TESTABILTY not being set [\#15](https://github.com/artman/Signals/issues/15) -- Would it be possible to add a numListeners property \[please\]? [\#14](https://github.com/artman/Signals/issues/14) - -**Merged pull requests:** - -- Set ENABLE\_TESTABILITY to YES for release builds [\#16](https://github.com/artman/Signals/pull/16) ([raj-room](https://github.com/raj-room)) - -## [2.1.0](https://github.com/artman/signals/tree/2.1.0) (2015-10-10) -[Full Changelog](https://github.com/artman/signals/compare/2.0.0...2.1.0) - -**Implemented enhancements:** - -- ListenPastOnce? [\#11](https://github.com/artman/Signals/issues/11) -- Swift 2.0 Compatibility [\#9](https://github.com/artman/Signals/issues/9) -- Add listenPastOnce [\#12](https://github.com/artman/Signals/pull/12) ([ReneB](https://github.com/ReneB)) - -**Closed issues:** - -- Deployment target iOS 8.0, should be 7.0? [\#10](https://github.com/artman/Signals/issues/10) - -## [2.0.0](https://github.com/artman/signals/tree/2.0.0) (2015-09-24) -[Full Changelog](https://github.com/artman/signals/compare/1.0.0...2.0.0) - -**Closed issues:** - -- Missing Podspec in repo [\#6](https://github.com/artman/Signals/issues/6) - -**Merged pull requests:** - -- Add watchOS 2.0 as a pod target [\#8](https://github.com/artman/Signals/pull/8) ([netizen01](https://github.com/netizen01)) -- Updated readme to reflect Carthage support [\#7](https://github.com/artman/Signals/pull/7) ([csjones](https://github.com/csjones)) -- Update README.md [\#5](https://github.com/artman/Signals/pull/5) ([LunaCodeGirl](https://github.com/LunaCodeGirl)) - -## [1.0.0](https://github.com/artman/signals/tree/1.0.0) (2015-02-17) -**Merged pull requests:** - -- Add more safe removal of weak listeners [\#4](https://github.com/artman/Signals/pull/4) ([robskillington](https://github.com/robskillington)) -- Update README.md [\#2](https://github.com/artman/Signals/pull/2) ([robskillington](https://github.com/robskillington)) -- Setting methods and class required from other packages as public so as their accessible for outside packages [\#1](https://github.com/artman/Signals/pull/1) ([robskillington](https://github.com/robskillington)) - - - -\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file diff --git a/README.md b/README.md index 3b513f9..62eb32e 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Signals is a library for creating and observing events. It replaces delegates, a ## Requirements - iOS 7.0 / watchOS 2.0 / Mac OS X 10.9 -- Xcode 7.0 (compatible with Swift 2.0) +- Swift 3.0 ## Installation @@ -33,7 +33,7 @@ To integrate Signals into your project add the following to your `Podfile`: platform :ios, '8.0' use_frameworks! -pod 'Signals', '~> 3.0' +pod 'Signals', '~> 4.0' ``` #### Carthage @@ -41,7 +41,7 @@ pod 'Signals', '~> 3.0' To integrate Signals into your project using Carthage add the following to your `Cartfile`: ```ruby -github "artman/Signals" ~> 3.0 +github "artman/Signals" ~> 4.0 ``` ## Quick start @@ -70,23 +70,23 @@ Subscribe to these signals from elsewhere in your application ```swift let networkLoader = NetworkLoader("http://artman.fi") -networkLoader.onProgress.listen(self) { (progress) in - println("Loading progress: \(progress*100)%") +networkLoader.onProgress.subscribe(on: self) { (progress) in + print("Loading progress: \(progress*100)%") } -networkLoader.onData.listen(self) { (data, error) in +networkLoader.onData.subscribe(on: self) { (data, error) in // Do something with the data } ``` -Adding listeners to signals is a attach-and-forget operation. If your listener is deallocated, the Signal removes the listener from it's list of listeners. If the Signal emitter is deallocated, so is the closure that was supposed to fire on the listener, so you don't need to explicitly manage the removal of listeners. +Adding subscriptions to Signals is an attach-and-forget operation. If the subscribing object is deallocated, the `Signal` cancels the subscription, so you don't need to explicitly manage the cancellation of your subsciptions. -Singals aren't restricted to one listener, so multiple objects can listen on the same Signal. +Singals aren't restricted to one subscriber. Multiple objects can subscribe to the same Signal. You can also subscribe to events after they have occurred: ```swift -networkLoader.onProgress.listenPast(self) { (progress) in +networkLoader.onProgress.subscribePast(on: self) { (progress) in // This will immediately fire with last progress that was reported // by the onProgress signal println("Loading progress: \(progress*100)%") @@ -95,31 +95,31 @@ networkLoader.onProgress.listenPast(self) { (progress) in ### Advanced topics -Signal listeners can apply filters: +Signal subscriptions can apply filters: ```swift -networkLoader.onProgress.listen(self) { (progress) in +networkLoader.onProgress.subscribe(on: self) { (progress) in // This fires when progress is done }.filter { $0 == 1.0 } ``` -You can queue up listener dispatches for a set amount of time and fire them only once: +You can sample up subscriptions to throttle how often you're subscription is exectuded, regardless how often the `Signal` fires: ```swift -networkLoader.onProgress.listen(self) { (progress) in +networkLoader.onProgress.subscribe(on: self) { (progress) in // Executed once per second while progress changes -}.queueAndDelayBy(1.0) +}.sample(every: 1.0) ``` -A signal dispatches listener calls synchronously on the posting thread by default. To define the thread explicitly, you should use the `dispatchOnQueue` method. In this way you will receive listener calls asynchronously on the specified queue: +By default, a subscription executes synchronously on the thread that fires the `Signal`. To change the default behaviour, you can use the `dispatchOnQueue` method to define the dispatch queue: ```swift -networkLoader.onProgress.listen(self) { (progress) in +networkLoader.onProgress.subscribe(on: self) { (progress) in // This fires on the main queue -}.dispatchOnQueue(dispatch_get_main_queue()) +}.dispatchOnQueue(DispatchQueue.main) ``` -If you don't like the double quotes that you have to use since Swift 2.0 when you fire signals that take tuples, you can use a special operator to fire the data: +If you don't like the double quotes when you fire signals that take tuples, you can use the custom `=>` operator to fire the data: ```swift // If you don't like the double quotes when firing signals that have tuples @@ -134,16 +134,16 @@ self.onProgress => 1.0 ## Replacing actions -Signals extends all classes that extend from UIControl (not available on OS X) and lets you use signals to listen to control events for increased code locality. +Signals extends all classes that extend from UIControl (not available on OS X) and lets you use Signals to listen to control events for increased code locality. ```swift let button = UIButton() -button.onTouchUpInside.listen(self) { +button.onTouchUpInside.observe(on: self) { // Handle the touch } let slider = UISlider() -slider.onValueChanged.listen(self) { +slider.onValueChanged.observe(on: self) { // Handle value change } ``` @@ -163,14 +163,14 @@ Would you rather implement a callback using a delegate: Or do the same thing with Signals: -- Create a signal for the class that wants to provide an event -- Subscribe to the signal as a listener from any instance you want +- Create a Signal for the class that wants to provide an event +- Subscribe to the Signal -Signals can have multiple listeners and they therefore don't provide a way for the observer to return data to the signal invoker. The delegate pattern should still be used for data requests (e.g. UITableViewDataSource). +## Replace NotificationCenter -## Replace NSNotificationCenter +When your team of engineers grows, NotificationCenter quickly becomes an anti-pattern. Global notifications with implicit data and no compiler safety easily make your code error-prone and hard to maintain and refactor. -To replace global notifications via the NSNotificationCenter with Signals, just create a Singleton with a number of public signals that anybody can subscribe to or fire. You'll gain type safety, refactorability and attach-and-forget observation. +Replacing NotificationCenter with Signals will give you strong type safety enforced by the compiler that will help you maintain your code no matter how fast you move. ## Communication diff --git a/Signals.xcodeproj/project.pbxproj b/Signals.xcodeproj/project.pbxproj index 07ed5b9..8f3bdc7 100644 --- a/Signals.xcodeproj/project.pbxproj +++ b/Signals.xcodeproj/project.pbxproj @@ -605,7 +605,6 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; 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; @@ -657,7 +656,6 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; 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; diff --git a/Signals/ios/UIControl+Signals.swift b/Signals/ios/UIControl+Signals.swift index eaf06d9..1764436 100644 --- a/Signals/ios/UIControl+Signals.swift +++ b/Signals/ios/UIControl+Signals.swift @@ -10,103 +10,100 @@ import UIKit /// Extends UIControl with signals for all ui control events. - public extension UIControl { - private struct AssociatedKeys { - static var SignalDictionaryKey = "signals_signalKey" - } - - static let eventToKey: [UIControlEvents: NSString] = [ - .touchDown: "TouchDown", - .touchDownRepeat: "TouchDownRepeat", - .touchDragInside: "TouchDragInside", - .touchDragOutside: "TouchDragOutside", - .touchDragEnter: "TouchDragEnter", - .touchDragExit: "TouchDragExit", - .touchUpInside: "TouchUpInside", - .touchUpOutside: "TouchUpOutside", - .touchCancel: "TouchCancel", - .valueChanged: "ValueChanged", - .editingDidBegin: "EditingDidBegin", - .editingChanged: "EditingChanged", - .editingDidEnd: "EditingDidEnd", - .editingDidEndOnExit: "EditingDidEndOnExit"] - - // MARK - Public interface - /// A signal that fires for each touch down control event. - public var onTouchDown: Signal<()> { + public var onTouchDown: Signal<(Void)> { return getOrCreateSignalForUIControlEvent(.touchDown); } /// A signal that fires for each touch down repeat control event. - public var onTouchDownRepeat: Signal<()> { + public var onTouchDownRepeat: Signal<(Void)> { return getOrCreateSignalForUIControlEvent(.touchDownRepeat); } /// A signal that fires for each touch drag inside control event. - public var onTouchDragInside: Signal<()> { + public var onTouchDragInside: Signal<(Void)> { return getOrCreateSignalForUIControlEvent(.touchDragInside); } /// A signal that fires for each touch drag outside control event. - public var onTouchDragOutside: Signal<()> { + public var onTouchDragOutside: Signal<(Void)> { return getOrCreateSignalForUIControlEvent(.touchDragOutside); } /// A signal that fires for each touch drag enter control event. - public var onTouchDragEnter: Signal<()> { + public var onTouchDragEnter: Signal<(Void)> { return getOrCreateSignalForUIControlEvent(.touchDragEnter); } /// A signal that fires for each touch drag exit control event. - public var onTouchDragExit: Signal<()> { + public var onTouchDragExit: Signal<(Void)> { return getOrCreateSignalForUIControlEvent(.touchDragExit); } /// A signal that fires for each touch up inside control event. - public var onTouchUpInside: Signal<()> { + public var onTouchUpInside: Signal<(Void)> { return getOrCreateSignalForUIControlEvent(.touchUpInside); } /// A signal that fires for each touch up outside control event. - public var onTouchUpOutside: Signal<()> { + public var onTouchUpOutside: Signal<(Void)> { return getOrCreateSignalForUIControlEvent(.touchUpOutside); } /// A signal that fires for each touch cancel control event. - public var onTouchCancel: Signal<()> { + public var onTouchCancel: Signal<(Void)> { return getOrCreateSignalForUIControlEvent(.touchCancel); } /// A signal that fires for each value changed control event. - public var onValueChanged: Signal<()> { + public var onValueChanged: Signal<(Void)> { return getOrCreateSignalForUIControlEvent(.valueChanged); } /// A signal that fires for each editing did begin control event. - public var onEditingDidBegin: Signal<()> { + public var onEditingDidBegin: Signal<(Void)> { return getOrCreateSignalForUIControlEvent(.editingDidBegin); } /// A signal that fires for each editing changed control event. - public var onEditingChanged: Signal<()> { + public var onEditingChanged: Signal<(Void)> { return getOrCreateSignalForUIControlEvent(.editingChanged); } /// A signal that fires for each editing did end control event. - public var onEditingDidEnd: Signal<()> { + public var onEditingDidEnd: Signal<(Void)> { return getOrCreateSignalForUIControlEvent(.editingDidEnd); } /// A signal that fires for each editing did end on exit control event. - public var onEditingDidEndOnExit: Signal<()> { + public var onEditingDidEndOnExit: Signal<(Void)> { return getOrCreateSignalForUIControlEvent(.editingDidEndOnExit); } - // MARK: - Internal interface + // MARK: - Private interface - private func getOrCreateSignalForUIControlEvent(_ event: UIControlEvents) -> Signal<()> { + private struct AssociatedKeys { + static var SignalDictionaryKey = "signals_signalKey" + } + + private static let eventToKey: [UIControlEvents: NSString] = [ + .touchDown: "TouchDown", + .touchDownRepeat: "TouchDownRepeat", + .touchDragInside: "TouchDragInside", + .touchDragOutside: "TouchDragOutside", + .touchDragEnter: "TouchDragEnter", + .touchDragExit: "TouchDragExit", + .touchUpInside: "TouchUpInside", + .touchUpOutside: "TouchUpOutside", + .touchCancel: "TouchCancel", + .valueChanged: "ValueChanged", + .editingDidBegin: "EditingDidBegin", + .editingChanged: "EditingChanged", + .editingDidEnd: "EditingDidEnd", + .editingDidEndOnExit: "EditingDidEndOnExit"] + + private func getOrCreateSignalForUIControlEvent(_ event: UIControlEvents) -> Signal<(Void)> { guard let key = UIControl.eventToKey[event] else { assertionFailure("Event type is not handled") return Signal() @@ -127,8 +124,6 @@ public extension UIControl { getOrCreateSignalForUIControlEvent(uiControlEvent).fire() } - // MARK: - Event handlers - private dynamic func eventHandlerTouchDown() { handleUIControlEvent(.touchDown) } From b8dd6b0dd17d2108c9bbcbe3ecab54a18e54fe72 Mon Sep 17 00:00:00 2001 From: Tuomas Artman Date: Wed, 19 Oct 2016 23:26:47 -0700 Subject: [PATCH 5/5] Updated travis.config with cocoapods installation --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index caa1d45..f61015b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ env: before_install: - gem install slather -N + - gem install cocoapods --pre --no-rdoc --no-ri --no-document --quiet script: - set -o pipefail