From 6d398a7aec631abc5d508222b4ee69aed495767a Mon Sep 17 00:00:00 2001 From: Rui Peres Date: Thu, 14 Apr 2016 01:41:06 +0100 Subject: [PATCH 1/4] Added deallocate trigger signal --- Rex.xcodeproj/project.pbxproj | 10 ++++++++ Source/Foundation/NSObject.swift | 26 +++++++++++++++++++- Source/RACSignal.swift | 36 ++++++++++++++++++++++++++++ Tests/Foundation/NSObjectTests.swift | 16 ++++++++++++- 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 Source/RACSignal.swift diff --git a/Rex.xcodeproj/project.pbxproj b/Rex.xcodeproj/project.pbxproj index 1e963b7..a8883d4 100644 --- a/Rex.xcodeproj/project.pbxproj +++ b/Rex.xcodeproj/project.pbxproj @@ -19,6 +19,10 @@ 8295FD8D1B87374A007C9000 /* UIBarButtonItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8295FD8B1B873748007C9000 /* UIBarButtonItemTests.swift */; }; 9DA915A41CA6301C003723B9 /* UIDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA915A31CA6301C003723B9 /* UIDatePicker.swift */; }; 9DA915A61CA63046003723B9 /* UIDatePickerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA915A51CA63046003723B9 /* UIDatePickerTests.swift */; }; + C72CF3E51CBF188A00E19897 /* RACSignal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72CF3E41CBF188A00E19897 /* RACSignal.swift */; }; + C72CF3E61CBF188A00E19897 /* RACSignal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72CF3E41CBF188A00E19897 /* RACSignal.swift */; }; + C72CF3E71CBF188A00E19897 /* RACSignal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72CF3E41CBF188A00E19897 /* RACSignal.swift */; }; + C72CF3E81CBF188A00E19897 /* RACSignal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72CF3E41CBF188A00E19897 /* RACSignal.swift */; }; C7932E831C4B3F3000086F3C /* UITextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7932E811C4B3EDB00086F3C /* UITextField.swift */; }; C7932E841C4B41E100086F3C /* UITextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7932E811C4B3EDB00086F3C /* UITextField.swift */; }; C7932E871C4B42F500086F3C /* UITextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7932E851C4B420A00086F3C /* UITextFieldTests.swift */; }; @@ -188,6 +192,7 @@ 8295FD8B1B873748007C9000 /* UIBarButtonItemTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIBarButtonItemTests.swift; sourceTree = ""; }; 9DA915A31CA6301C003723B9 /* UIDatePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIDatePicker.swift; sourceTree = ""; }; 9DA915A51CA63046003723B9 /* UIDatePickerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIDatePickerTests.swift; sourceTree = ""; }; + C72CF3E41CBF188A00E19897 /* RACSignal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RACSignal.swift; sourceTree = ""; }; C7932E811C4B3EDB00086F3C /* UITextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITextField.swift; sourceTree = ""; }; C7932E851C4B420A00086F3C /* UITextFieldTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITextFieldTests.swift; sourceTree = ""; }; C7DCE2B21CB3C872001217D8 /* UITextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITextView.swift; sourceTree = ""; }; @@ -319,6 +324,7 @@ D8A454051BD26A1A00C9E790 /* Property.swift */, D8003EBC1AFED01000D7D3C5 /* Signal.swift */, D8003EB81AFEC7A900D7D3C5 /* SignalProducer.swift */, + C72CF3E41CBF188A00E19897 /* RACSignal.swift */, 4238D5941B4D593E008534C0 /* AppKit */, D8F097391B17F2BF002E15BA /* Foundation */, D86FFBD31B34B0E2001A89B3 /* UIKit */, @@ -746,6 +752,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C72CF3E51CBF188A00E19897 /* RACSignal.swift in Sources */, D8A454061BD26A1A00C9E790 /* Property.swift in Sources */, D86FFBDA1B34B3F0001A89B3 /* Action.swift in Sources */, D86FFBD11B34AD6F001A89B3 /* Association.swift in Sources */, @@ -783,6 +790,7 @@ D834572D1AFEE45B0070616A /* Signal.swift in Sources */, D8E4A6211B7BBB2100EAD8A8 /* UIBarItem.swift in Sources */, 7D2AA99B1CB6EFEB008AB5C9 /* UISwitch.swift in Sources */, + C72CF3E61CBF188A00E19897 /* RACSignal.swift in Sources */, D8A454071BD26A1A00C9E790 /* Property.swift in Sources */, D8E4A6201B7BBB1600EAD8A8 /* UIBarButtonItem.swift in Sources */, D8F097451B17F3C8002E15BA /* NSObject.swift in Sources */, @@ -825,6 +833,7 @@ D8715DA31C21107F005F4191 /* Association.swift in Sources */, D8715DA51C21107F005F4191 /* NSObject.swift in Sources */, D8715D9F1C210FF9005F4191 /* Signal.swift in Sources */, + C72CF3E81CBF188A00E19897 /* RACSignal.swift in Sources */, D8715DA41C21107F005F4191 /* NSData.swift in Sources */, D8715D9D1C210FF9005F4191 /* Action.swift in Sources */, D8715DA61C21107F005F4191 /* NSUserDefaults.swift in Sources */, @@ -845,6 +854,7 @@ D8715DC01C2112D6005F4191 /* NSObject.swift in Sources */, D8715DC91C211553005F4191 /* UIControl.swift in Sources */, D8715DBC1C2112D1005F4191 /* Signal.swift in Sources */, + C72CF3E71CBF188A00E19897 /* RACSignal.swift in Sources */, D8715DBF1C2112D6005F4191 /* NSData.swift in Sources */, D8715DCC1C211553005F4191 /* UIView.swift in Sources */, D8715DBA1C2112D1005F4191 /* Action.swift in Sources */, diff --git a/Source/Foundation/NSObject.swift b/Source/Foundation/NSObject.swift index 324122c..a8931b0 100644 --- a/Source/Foundation/NSObject.swift +++ b/Source/Foundation/NSObject.swift @@ -24,6 +24,30 @@ extension NSObject { // Errors aren't possible, but the compiler doesn't know that. assertionFailure("Unexpected error from KVO signal: \(error)") return .empty - } + } } + + /// Creates a signal that will be triggered when the object + /// is deallocated. + public final func willDeallocSignal() -> Signal<(), NoError> { + return self + .rac_willDeallocSignal() + .toTriggerSignal() + } +} + +extension SignalProducerType { + /// Forwards events from `self` until `object` is deallocated, + /// at which point the returned producer will complete. + public final func takeUntilObjectDeallocates(object: NSObject) -> SignalProducer { + return self.lift { $0.takeUntilObjectDeallocates(object) } + } } + +extension SignalType { + /// Forwards events from `self` until `object` is deallocated, + /// at which point the returned signal will complete. + public final func takeUntilObjectDeallocates(object: NSObject) -> Signal { + return self.takeUntil(object.willDeallocSignal()) + } +} \ No newline at end of file diff --git a/Source/RACSignal.swift b/Source/RACSignal.swift new file mode 100644 index 0000000..7b565d1 --- /dev/null +++ b/Source/RACSignal.swift @@ -0,0 +1,36 @@ +// +// RACSignal.swift +// Rex +// +// Created by Rui Peres on 14/04/2016. +// Copyright © 2016 Neil Pankey. All rights reserved. +// + +import Foundation +import Result +import ReactiveCocoa + +extension RACSignal { + + /// Converts `self` into a `Signal`. + /// + /// Because the operator can't know whether `self` is hot or cold, + /// for certain things, like event streams (see `UIControl.signalForControlEvents`) + /// use this method to be able to expose these inherently hot streams + /// as `Signal`s. + public func toSignalAssumingHot() -> Signal { + return Signal { observer in + return self.toSignalProducer().start(observer) + } + } + + /// Converts `self` into a `Signal`, that can be used + /// with the `takeUntil` operator, or as an "activation" signal. + /// (e.g. a button) + public final func toTriggerSignal() -> Signal<(), NoError> { + return self + .toSignalAssumingHot() + .map { _ in () } + .ignoreError() + } +} \ No newline at end of file diff --git a/Tests/Foundation/NSObjectTests.swift b/Tests/Foundation/NSObjectTests.swift index 45ec54e..8b5728d 100644 --- a/Tests/Foundation/NSObjectTests.swift +++ b/Tests/Foundation/NSObjectTests.swift @@ -11,7 +11,7 @@ import ReactiveCocoa import XCTest final class NSObjectTests: XCTestCase { - + func testProducerForKeyPath() { let object = Object() var value: String = "" @@ -22,6 +22,20 @@ final class NSObjectTests: XCTestCase { object.string = "bar" XCTAssertEqual(value, "bar") } + + func testObjectsWillBeDeallocatedSignal() { + + let expectation = self.expectationWithDescription("Expected timer to send `completed` event when object deallocates") + defer { self.waitForExpectationsWithTimeout(2, handler: nil) } + + let object = Object() + + timer(1, onScheduler: QueueScheduler(name: "test.queue")) + .takeUntilObjectDeallocates(object) + .startWithCompleted { + expectation.fulfill() + } + } } final class NSObjectDeallocTests: XCTestCase { From 0e2cdded87b7e662daed9cd01c8b7aca8c0ea9a3 Mon Sep 17 00:00:00 2001 From: Rui Peres Date: Thu, 14 Apr 2016 01:47:27 +0100 Subject: [PATCH 2/4] Adding in unused warnings --- Source/Foundation/NSObject.swift | 3 +++ Source/RACSignal.swift | 2 ++ 2 files changed, 5 insertions(+) diff --git a/Source/Foundation/NSObject.swift b/Source/Foundation/NSObject.swift index a8931b0..b55849c 100644 --- a/Source/Foundation/NSObject.swift +++ b/Source/Foundation/NSObject.swift @@ -29,6 +29,7 @@ extension NSObject { /// Creates a signal that will be triggered when the object /// is deallocated. + @warn_unused_result(message="Did you forget to call `observe` on the signal?") public final func willDeallocSignal() -> Signal<(), NoError> { return self .rac_willDeallocSignal() @@ -39,6 +40,7 @@ extension NSObject { extension SignalProducerType { /// Forwards events from `self` until `object` is deallocated, /// at which point the returned producer will complete. + @warn_unused_result(message="Did you forget to call `start` on the producer?") public final func takeUntilObjectDeallocates(object: NSObject) -> SignalProducer { return self.lift { $0.takeUntilObjectDeallocates(object) } } @@ -47,6 +49,7 @@ extension SignalProducerType { extension SignalType { /// Forwards events from `self` until `object` is deallocated, /// at which point the returned signal will complete. + @warn_unused_result(message="Did you forget to call `observe` on the signal?") public final func takeUntilObjectDeallocates(object: NSObject) -> Signal { return self.takeUntil(object.willDeallocSignal()) } diff --git a/Source/RACSignal.swift b/Source/RACSignal.swift index 7b565d1..42172e7 100644 --- a/Source/RACSignal.swift +++ b/Source/RACSignal.swift @@ -18,6 +18,7 @@ extension RACSignal { /// for certain things, like event streams (see `UIControl.signalForControlEvents`) /// use this method to be able to expose these inherently hot streams /// as `Signal`s. + @warn_unused_result(message="Did you forget to call `observe` on the signal?") public func toSignalAssumingHot() -> Signal { return Signal { observer in return self.toSignalProducer().start(observer) @@ -27,6 +28,7 @@ extension RACSignal { /// Converts `self` into a `Signal`, that can be used /// with the `takeUntil` operator, or as an "activation" signal. /// (e.g. a button) + @warn_unused_result(message="Did you forget to call `observe` on the signal?") public final func toTriggerSignal() -> Signal<(), NoError> { return self .toSignalAssumingHot() From 87cbcffd7f43f146145cc7cfe5bc9c3d452688cf Mon Sep 17 00:00:00 2001 From: Rui Peres Date: Fri, 15 Apr 2016 10:50:15 +0100 Subject: [PATCH 3/4] Code review --- Source/Foundation/NSObject.swift | 21 +-------------------- Source/RACSignal.swift | 4 ++-- Tests/Foundation/NSObjectTests.swift | 2 +- 3 files changed, 4 insertions(+), 23 deletions(-) diff --git a/Source/Foundation/NSObject.swift b/Source/Foundation/NSObject.swift index b55849c..0defe0c 100644 --- a/Source/Foundation/NSObject.swift +++ b/Source/Foundation/NSObject.swift @@ -29,28 +29,9 @@ extension NSObject { /// Creates a signal that will be triggered when the object /// is deallocated. - @warn_unused_result(message="Did you forget to call `observe` on the signal?") - public final func willDeallocSignal() -> Signal<(), NoError> { + public var willDeallocSignal: Signal<(), NoError> { return self .rac_willDeallocSignal() .toTriggerSignal() } -} - -extension SignalProducerType { - /// Forwards events from `self` until `object` is deallocated, - /// at which point the returned producer will complete. - @warn_unused_result(message="Did you forget to call `start` on the producer?") - public final func takeUntilObjectDeallocates(object: NSObject) -> SignalProducer { - return self.lift { $0.takeUntilObjectDeallocates(object) } - } -} - -extension SignalType { - /// Forwards events from `self` until `object` is deallocated, - /// at which point the returned signal will complete. - @warn_unused_result(message="Did you forget to call `observe` on the signal?") - public final func takeUntilObjectDeallocates(object: NSObject) -> Signal { - return self.takeUntil(object.willDeallocSignal()) - } } \ No newline at end of file diff --git a/Source/RACSignal.swift b/Source/RACSignal.swift index 42172e7..a1ac297 100644 --- a/Source/RACSignal.swift +++ b/Source/RACSignal.swift @@ -19,7 +19,7 @@ extension RACSignal { /// use this method to be able to expose these inherently hot streams /// as `Signal`s. @warn_unused_result(message="Did you forget to call `observe` on the signal?") - public func toSignalAssumingHot() -> Signal { + public func toSignal() -> Signal { return Signal { observer in return self.toSignalProducer().start(observer) } @@ -31,7 +31,7 @@ extension RACSignal { @warn_unused_result(message="Did you forget to call `observe` on the signal?") public final func toTriggerSignal() -> Signal<(), NoError> { return self - .toSignalAssumingHot() + .toSignal() .map { _ in () } .ignoreError() } diff --git a/Tests/Foundation/NSObjectTests.swift b/Tests/Foundation/NSObjectTests.swift index 8b5728d..40f4d63 100644 --- a/Tests/Foundation/NSObjectTests.swift +++ b/Tests/Foundation/NSObjectTests.swift @@ -31,7 +31,7 @@ final class NSObjectTests: XCTestCase { let object = Object() timer(1, onScheduler: QueueScheduler(name: "test.queue")) - .takeUntilObjectDeallocates(object) + .takeUntil(object.willDeallocSignal) .startWithCompleted { expectation.fulfill() } From c501edadbab03a42edfcea21effa0b6a554eaf36 Mon Sep 17 00:00:00 2001 From: Rui Peres Date: Fri, 15 Apr 2016 10:53:34 +0100 Subject: [PATCH 4/4] To be consistent with the nomenclature. --- Source/Foundation/NSObject.swift | 4 ++-- Source/RACSignal.swift | 6 +++--- Tests/Foundation/NSObjectTests.swift | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Foundation/NSObject.swift b/Source/Foundation/NSObject.swift index 0defe0c..dda40bb 100644 --- a/Source/Foundation/NSObject.swift +++ b/Source/Foundation/NSObject.swift @@ -29,9 +29,9 @@ extension NSObject { /// Creates a signal that will be triggered when the object /// is deallocated. - public var willDeallocSignal: Signal<(), NoError> { + public var rex_willDeallocSignal: Signal<(), NoError> { return self .rac_willDeallocSignal() - .toTriggerSignal() + .rex_toTriggerSignal() } } \ No newline at end of file diff --git a/Source/RACSignal.swift b/Source/RACSignal.swift index a1ac297..57f86b8 100644 --- a/Source/RACSignal.swift +++ b/Source/RACSignal.swift @@ -19,7 +19,7 @@ extension RACSignal { /// use this method to be able to expose these inherently hot streams /// as `Signal`s. @warn_unused_result(message="Did you forget to call `observe` on the signal?") - public func toSignal() -> Signal { + public func rex_toSignal() -> Signal { return Signal { observer in return self.toSignalProducer().start(observer) } @@ -29,9 +29,9 @@ extension RACSignal { /// with the `takeUntil` operator, or as an "activation" signal. /// (e.g. a button) @warn_unused_result(message="Did you forget to call `observe` on the signal?") - public final func toTriggerSignal() -> Signal<(), NoError> { + public final func rex_toTriggerSignal() -> Signal<(), NoError> { return self - .toSignal() + .rex_toSignal() .map { _ in () } .ignoreError() } diff --git a/Tests/Foundation/NSObjectTests.swift b/Tests/Foundation/NSObjectTests.swift index 40f4d63..9a5b2cf 100644 --- a/Tests/Foundation/NSObjectTests.swift +++ b/Tests/Foundation/NSObjectTests.swift @@ -31,7 +31,7 @@ final class NSObjectTests: XCTestCase { let object = Object() timer(1, onScheduler: QueueScheduler(name: "test.queue")) - .takeUntil(object.willDeallocSignal) + .takeUntil(object.rex_willDeallocSignal) .startWithCompleted { expectation.fulfill() }