Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deallocate Trigger Signal #99

Merged
merged 4 commits into from
Apr 15, 2016
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Rex.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -188,6 +192,7 @@
8295FD8B1B873748007C9000 /* UIBarButtonItemTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIBarButtonItemTests.swift; sourceTree = "<group>"; };
9DA915A31CA6301C003723B9 /* UIDatePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIDatePicker.swift; sourceTree = "<group>"; };
9DA915A51CA63046003723B9 /* UIDatePickerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIDatePickerTests.swift; sourceTree = "<group>"; };
C72CF3E41CBF188A00E19897 /* RACSignal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RACSignal.swift; sourceTree = "<group>"; };
C7932E811C4B3EDB00086F3C /* UITextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITextField.swift; sourceTree = "<group>"; };
C7932E851C4B420A00086F3C /* UITextFieldTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITextFieldTests.swift; sourceTree = "<group>"; };
C7DCE2B21CB3C872001217D8 /* UITextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITextView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -319,6 +324,7 @@
D8A454051BD26A1A00C9E790 /* Property.swift */,
D8003EBC1AFED01000D7D3C5 /* Signal.swift */,
D8003EB81AFEC7A900D7D3C5 /* SignalProducer.swift */,
C72CF3E41CBF188A00E19897 /* RACSignal.swift */,
4238D5941B4D593E008534C0 /* AppKit */,
D8F097391B17F2BF002E15BA /* Foundation */,
D86FFBD31B34B0E2001A89B3 /* UIKit */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand All @@ -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 */,
Expand Down
29 changes: 28 additions & 1 deletion Source/Foundation/NSObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,33 @@ 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.
@warn_unused_result(message="Did you forget to call `observe` on the signal?")
public final func willDeallocSignal() -> Signal<(), NoError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could/should be a property. It's idempotent.

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<Self.Value, Self.Error> {
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<Self.Value, Self.Error> {
return self.takeUntil(object.willDeallocSignal())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem like this buys us much. I feel like it reads nicer as takeUntil(object.willDeallocSignal).

}
}
38 changes: 38 additions & 0 deletions Source/RACSignal.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// 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.
@warn_unused_result(message="Did you forget to call `observe` on the signal?")
public func toSignalAssumingHot() -> Signal<AnyObject?, NSError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since Signals are hot, it seems like this could just be called toSignal.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept the original comments from Nacho with a slight modification, but yes I agree with your suggestion.

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)
@warn_unused_result(message="Did you forget to call `observe` on the signal?")
public final func toTriggerSignal() -> Signal<(), NoError> {
return self
.toSignalAssumingHot()
.map { _ in () }
.ignoreError()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this actually ignore errors? Notice that my operators don't actually ignore unexpected errors, they assert they won't happen.

Copy link
Member Author

@RuiAAPeres RuiAAPeres Apr 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When an error event is sent, it will complete (by default replacement == .Completed):

case .Failed:
        observer.action(replacement)

Your operator is slightly more aggressive.

}
}
16 changes: 15 additions & 1 deletion Tests/Foundation/NSObjectTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import ReactiveCocoa
import XCTest

final class NSObjectTests: XCTestCase {

func testProducerForKeyPath() {
let object = Object()
var value: String = ""
Expand All @@ -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 {
Expand Down