Skip to content
This repository has been archived by the owner on Dec 5, 2019. It is now read-only.

Commit

Permalink
Merge pull request ReactiveCocoa#1636 from ReactiveCocoa/document-mem…
Browse files Browse the repository at this point in the history
…ory-management

Document Swift memory management
  • Loading branch information
joshaber committed Dec 22, 2014
2 parents 908bbb8 + 918f264 commit 67f98f7
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 103 deletions.
220 changes: 117 additions & 103 deletions ReactiveCocoa/Swift/ColdSignal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,115 +11,16 @@ import LlamaKit
private func doNothing<T>(value: T) {}
private func doNothing() {}

/// Represents a stream event.
///
/// Streams must conform to the grammar:
/// `Next* (Error | Completed)?`
public enum Event<T> {
/// A value provided by the stream.
case Next(Box<T>)

/// The stream terminated because of an error.
case Error(NSError)

/// The stream successfully terminated.
case Completed

/// Whether this event indicates stream termination (from success or
/// failure).
public var isTerminating: Bool {
switch self {
case let .Next:
return false

default:
return true
}
}

/// Lifts the given function over the event's value.
public func map<U>(f: T -> U) -> Event<U> {
switch self {
case let .Next(box):
return .Next(Box(f(box.unbox)))

case let .Error(error):
return .Error(error)

case let .Completed:
return .Completed
}
}

/// Case analysis on the receiver.
public func event<U>(#ifNext: T -> U, ifError: NSError -> U, ifCompleted: @autoclosure () -> U) -> U {
switch self {
case let .Next(box):
return ifNext(box.unbox)

case let .Error(err):
return ifError(err)

case let .Completed:
return ifCompleted()
}
}

/// Creates a sink that can receive events of this type, then invoke the
/// given handlers based on the kind of event received.
public static func sink(next: T -> () = doNothing, error: NSError -> () = doNothing, completed: () -> () = doNothing) -> SinkOf<Event> {
return SinkOf { event in
switch event {
case let .Next(value):
next(value.unbox)

case let .Error(err):
error(err)

case .Completed:
completed()
}
}
}
}

public func == <T: Equatable> (lhs: Event<T>, rhs: Event<T>) -> Bool {
switch (lhs, rhs) {
case let (.Next(left), .Next(right)):
return left.unbox == right.unbox

case let (.Error(left), .Error(right)):
return left == right

case (.Completed, .Completed):
return true

default:
return false
}
}

extension Event: Printable {
public var description: String {
switch self {
case let .Next(value):
return "NEXT \(value.unbox)"

case let .Error(error):
return "ERROR \(error)"

case .Completed:
return "COMPLETED"
}
}
}

/// A stream that will begin generating Events when a sink is attached, possibly
/// performing some side effects in the process. Events are pushed to the sink
/// as they are generated.
///
/// A corollary to this is that different sinks may see a different timing of
/// Events, or even a different version of events altogether.
///
/// Cold signals, once started, do not need to be retained in order to receive
/// events. See the documentation for startWithSink() or start() for more
/// information.
public struct ColdSignal<T> {
/// The type of value that will be sent to any sink which attaches to this
/// signal.
Expand Down Expand Up @@ -159,6 +60,11 @@ public struct ColdSignal<T> {
/// The disposable given to the closure will cancel the work associated with
/// event production, and prevent any further events from being sent.
///
/// Once the signal has been started, there is no need to maintain a
/// reference to it. The signal will continue to do work until it sends a
/// Completed or Error event, or the returned Disposable is explicitly
/// disposed.
///
/// Returns the disposable which was given to the closure.
public func startWithSink(sinkCreator: Disposable -> SinkOf<Element>) -> Disposable {
let disposable = CompositeDisposable()
Expand Down Expand Up @@ -204,6 +110,11 @@ public struct ColdSignal<T> {
/// ColdSignal, and invoking the given handlers for each kind of event
/// generated.
///
/// Once the signal has been started, there is no need to maintain a
/// reference to it. The signal will continue to do work until it sends a
/// Completed or Error event, or the returned Disposable is explicitly
/// disposed.
///
/// Returns a disposable that will cancel the work associated with event
/// production, and prevent any further events from being sent.
public func start(next: T -> () = doNothing, error: NSError -> () = doNothing, completed: () -> () = doNothing) -> Disposable {
Expand Down Expand Up @@ -1237,6 +1148,109 @@ extension ColdSignal: DebugPrintable {
}
}

/// Represents a stream event.
///
/// Streams must conform to the grammar:
/// `Next* (Error | Completed)?`
public enum Event<T> {
/// A value provided by the stream.
case Next(Box<T>)

/// The stream terminated because of an error.
case Error(NSError)

/// The stream successfully terminated.
case Completed

/// Whether this event indicates stream termination (from success or
/// failure).
public var isTerminating: Bool {
switch self {
case let .Next:
return false

default:
return true
}
}

/// Lifts the given function over the event's value.
public func map<U>(f: T -> U) -> Event<U> {
switch self {
case let .Next(box):
return .Next(Box(f(box.unbox)))

case let .Error(error):
return .Error(error)

case let .Completed:
return .Completed
}
}

/// Case analysis on the receiver.
public func event<U>(#ifNext: T -> U, ifError: NSError -> U, ifCompleted: @autoclosure () -> U) -> U {
switch self {
case let .Next(box):
return ifNext(box.unbox)

case let .Error(err):
return ifError(err)

case let .Completed:
return ifCompleted()
}
}

/// Creates a sink that can receive events of this type, then invoke the
/// given handlers based on the kind of event received.
public static func sink(next: T -> () = doNothing, error: NSError -> () = doNothing, completed: () -> () = doNothing) -> SinkOf<Event> {
return SinkOf { event in
switch event {
case let .Next(value):
next(value.unbox)

case let .Error(err):
error(err)

case .Completed:
completed()
}
}
}
}

public func == <T: Equatable> (lhs: Event<T>, rhs: Event<T>) -> Bool {
switch (lhs, rhs) {
case let (.Next(left), .Next(right)):
return left.unbox == right.unbox

case let (.Error(left), .Error(right)):
return left == right

case (.Completed, .Completed):
return true

default:
return false
}
}

extension Event: Printable {
public var description: String {
switch self {
case let .Next(value):
return "NEXT \(value.unbox)"

case let .Error(error):
return "ERROR \(error)"

case .Completed:
return "COMPLETED"
}
}
}

private class CombineLatestState<T> {
var latestValue: T?
var completed = false
Expand Down
48 changes: 48 additions & 0 deletions ReactiveCocoa/Swift/HotSignal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
import LlamaKit

/// A push-driven stream that sends the same values to all observers.
///
/// Generally, hot signals do not need to be retained in order to observe the
/// values they send, but there are some exceptions. See the documentation for
/// observe() and HotSignal.weak() for more information.
public final class HotSignal<T> {
private let queue = dispatch_queue_create("org.reactivecocoa.ReactiveCocoa.HotSignal", DISPATCH_QUEUE_CONCURRENT)
private var observers: Bag<SinkOf<T>>? = Bag()
Expand Down Expand Up @@ -87,6 +91,14 @@ public final class HotSignal<T> {

/// Notifies `observer` about new values from the receiver.
///
/// If this signal was created through HotSignal.weak, you must keep a
/// strong reference to the HotSignal or the returned Disposable in order to
/// keep receiving values.
///
/// Otherwise (if the signal was initialized with HotSignal.init), your
/// observer will be retained until the signal has finished generating
/// values, or until the returned Disposable is explicitly disposed.
///
/// Returns a Disposable which can be disposed of to stop notifying
/// `observer` of future changes.
public func observe<S: SinkType where S.Element == T>(observer: S) -> Disposable {
Expand Down Expand Up @@ -153,6 +165,10 @@ extension HotSignal {

/// Creates a repeating timer of the given interval, with a reasonable
/// default leeway, sending updates on the given scheduler.
///
/// The timer will automatically be destroyed when there are no more strong
/// references to the returned signal, and no Disposables returned from
/// observe() are still around.
public class func interval(interval: NSTimeInterval, onScheduler scheduler: DateScheduler) -> HotSignal<NSDate> {
// Apple's "Power Efficiency Guide for Mac Apps" recommends a leeway of
// at least 10% of the timer interval.
Expand All @@ -161,6 +177,10 @@ extension HotSignal {

/// Creates a repeating timer of the given interval, sending updates on the
/// given scheduler.
///
/// The timer will automatically be destroyed when there are no more strong
/// references to the returned signal, and no Disposables returned from
/// observe() are still around.
public class func interval(interval: NSTimeInterval, onScheduler scheduler: DateScheduler, withLeeway leeway: NSTimeInterval) -> HotSignal<NSDate> {
precondition(interval >= 0)
precondition(leeway >= 0)
Expand Down Expand Up @@ -416,6 +436,10 @@ extension HotSignal {
///
/// If `sampler` fires before a value has been observed on the receiver,
/// nothing happens.
///
/// The returned signal will automatically be destroyed when there are no
/// more strong references to it, and no Disposables returned from observe()
/// are still around.
public func sampleOn(sampler: HotSignal<()>) -> HotSignal {
let latest = Atomic<T?>(nil)
let selfDisposable = observe { latest.value = $0 }
Expand All @@ -437,6 +461,10 @@ extension HotSignal {
/// Combines the latest value of the receiver with the latest value from
/// the given signal.
///
/// The returned signal will automatically be destroyed when there are no
/// more strong references to it, and no Disposables returned from observe()
/// are still around.
///
/// The returned signal will not send a value until both inputs have sent
/// at least one value each.
public func combineLatestWith<U>(signal: HotSignal<U>) -> HotSignal<(T, U)> {
Expand Down Expand Up @@ -469,6 +497,10 @@ extension HotSignal {

/// Zips elements of two signals into pairs. The elements of any Nth pair
/// are the Nth elements of the two input signals.
///
/// The returned signal will automatically be destroyed when there are no
/// more strong references to it, and no Disposables returned from observe()
/// are still around.
public func zipWith<U>(signal: HotSignal<U>) -> HotSignal<(T, U)> {
return HotSignal<(T, U)>.weak { sink in
let queue = dispatch_queue_create("org.reactivecocoa.ReactiveCocoa.HotSignal.zipWith", DISPATCH_QUEUE_SERIAL)
Expand Down Expand Up @@ -506,6 +538,10 @@ extension HotSignal {
/// Merges a signal of signals down into a single signal, biased toward the
/// signals added earlier.
///
/// The returned signal will automatically be destroyed when there are no
/// more strong references to it, and no Disposables returned from observe()
/// are still around.
///
/// evidence - Used to prove to the typechecker that the receiver is
/// a signal of signals. Simply pass in the `identity` function.
///
Expand All @@ -530,6 +566,10 @@ extension HotSignal {
///
/// This is equivalent to map() followed by merge().
///
/// The returned signal will automatically be destroyed when there are no
/// more strong references to it, and no Disposables returned from observe()
/// are still around.
///
/// Returns a signal that will forward changes from all mapped signals as
/// they arrive.
public func mergeMap<U>(f: T -> HotSignal<U>) -> HotSignal<U> {
Expand All @@ -539,6 +579,10 @@ extension HotSignal {
/// Switches on a signal of signals, forwarding values from the
/// latest inner signal.
///
/// The returned signal will automatically be destroyed when there are no
/// more strong references to it, and no Disposables returned from observe()
/// are still around.
///
/// evidence - Used to prove to the typechecker that the receiver is
/// a signal of signals. Simply pass in the `identity` function.
///
Expand All @@ -562,6 +606,10 @@ extension HotSignal {
///
/// This is equivalent to map() followed by switchToLatest().
///
/// The returned signal will automatically be destroyed when there are no
/// more strong references to it, and no Disposables returned from observe()
/// are still around.
///
/// Returns a signal that will forward changes only from the latest mapped
/// signal to arrive.
public func switchMap<U>(f: T -> HotSignal<U>) -> HotSignal<U> {
Expand Down

0 comments on commit 67f98f7

Please sign in to comment.