From f64ea2a587f03ac898198710347f65c78788fb26 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Sat, 3 Dec 2016 12:12:40 -0800 Subject: [PATCH 1/5] Initial commit of IndefiniteObservable. Reviewers: appsforartists, O2 Material Motion, O4 Material Motion Apple platform reviewers! Reviewed By: appsforartists, O2 Material Motion Subscribers: appsforartists Tags: #material_motion Differential Revision: http://codereview.cc/D2096 --- .arcconfig | 4 +- .jazzy.yaml | 10 +- .travis.yml | 2 +- AUTHORS | 2 +- ...le.podspec => IndefiniteObservable.podspec | 15 +- Podfile | 11 +- Podfile.lock | 24 ++ README.md | 225 ++++++++++- examples/DelegateObservableExample.swift | 86 +++++ examples/OperatorExample.swift | 83 +++++ .../Catalog/Catalog.xcodeproj/project.pbxproj | 350 +++++++++++++++++- .../apps/Catalog/Catalog/AppDelegate.swift | 2 +- .../Catalog/Catalog/TableOfContents.swift | 21 +- examples/apps/Catalog/OSXTests/Info.plist | 22 ++ src/IndefiniteObservable.swift | 146 ++++++++ tests/unit/MemoryLeakTests.swift | 146 ++++++++ tests/unit/ObservableTests.swift | 257 +++++++++++++ tests/unit/SimpleOperators.swift | 40 ++ 18 files changed, 1386 insertions(+), 60 deletions(-) rename MaterialMotionObservable.podspec => IndefiniteObservable.podspec (63%) create mode 100644 Podfile.lock create mode 100644 examples/DelegateObservableExample.swift create mode 100644 examples/OperatorExample.swift create mode 100644 examples/apps/Catalog/OSXTests/Info.plist create mode 100644 src/IndefiniteObservable.swift create mode 100644 tests/unit/MemoryLeakTests.swift create mode 100644 tests/unit/ObservableTests.swift create mode 100644 tests/unit/SimpleOperators.swift diff --git a/.arcconfig b/.arcconfig index 62d66ca..1373ddc 100644 --- a/.arcconfig +++ b/.arcconfig @@ -13,13 +13,13 @@ "arc.feature.start.default": "origin/develop", "unit.xcode": { "build": { - "workspace": "MaterialMotionObservable.xcworkspace", + "workspace": "IndefiniteObservable.xcworkspace", "scheme": "UnitTests", "configuration": "Debug", "destination": "platform=iOS Simulator,name=iPhone 6s" }, "coverage": { - "product": "MaterialMotionObservable.framework/MaterialMotionObservable" + "product": "IndefiniteObservable.framework/IndefiniteObservable" }, "pre-build": "pod install" } diff --git a/.jazzy.yaml b/.jazzy.yaml index 47f32f2..b4411b5 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -1,10 +1,10 @@ -module: MaterialMotionObservable +module: IndefiniteObservable module_version: 1.0.0 sdk: iphonesimulator xcodebuild_arguments: - -workspace - - MaterialMotionObservable.xcworkspace + - IndefiniteObservable.xcworkspace - -scheme - - MaterialMotionObservable -github_url: https://github.com/material-motion/observable-swift -github_file_prefix: https://github.com/material-motion/observable-swift/tree/v1.0.0 + - IndefiniteObservable +github_url: https://github.com/material-motion/indefinite-observable-swift +github_file_prefix: https://github.com/material-motion/indefinite-observable-swift/tree/v1.0.0 diff --git a/.travis.yml b/.travis.yml index fc7c30e..76d306a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,6 @@ before_install: script: - set -o pipefail - arcanist/bin/arc unit --everything --trace - - xcodebuild build -workspace MaterialMotionObservable.xcworkspace -scheme Catalog -sdk "iphonesimulator10.1" -destination "name=iPhone 6s,OS=10.1" ONLY_ACTIVE_ARCH=YES | xcpretty -c; + - xcodebuild build -workspace IndefiniteObservable.xcworkspace -scheme Catalog -sdk "iphonesimulator10.1" -destination "name=iPhone 6s,OS=10.1" ONLY_ACTIVE_ARCH=YES | xcpretty -c; after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/AUTHORS b/AUTHORS index 8a8a5c6..17d514d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,4 +1,4 @@ -# This is the list of Swift Observable authors for copyright purposes. +# This is the list of IndefiniteObservable.swift authors for copyright purposes. # # This does not necessarily list everyone who has contributed code, since in # some cases, their employer may be the copyright holder. To see the full list diff --git a/MaterialMotionObservable.podspec b/IndefiniteObservable.podspec similarity index 63% rename from MaterialMotionObservable.podspec rename to IndefiniteObservable.podspec index 1afb50b..8f96ec7 100644 --- a/MaterialMotionObservable.podspec +++ b/IndefiniteObservable.podspec @@ -1,12 +1,13 @@ Pod::Spec.new do |s| - s.name = "MaterialMotionObservable" - s.summary = "Swift Observable" + s.name = "IndefiniteObservable" + s.summary = "IndefiniteObservable.swift" s.version = "1.0.0" s.authors = "The Material Motion Authors" s.license = "Apache 2.0" - s.homepage = "https://github.com/material-motion/observable-swift" - s.source = { :git => "https://github.com/material-motion/observable-swift.git", :tag => "v" + s.version.to_s } - s.platform = :ios, "8.0" + s.homepage = "https://github.com/material-motion/indefinite-observable-swift" + s.source = { :git => "https://github.com/material-motion/indefinite-observable-swift.git", :tag => "v" + s.version.to_s } + s.ios.deployment_target = '8.0' + s.osx.deployment_target = '10.9' s.requires_arc = true s.default_subspec = "lib" @@ -18,11 +19,11 @@ Pod::Spec.new do |s| ss.source_files = "examples/*.{swift}", "examples/supplemental/*.{swift}" ss.exclude_files = "examples/TableOfContents.swift" ss.resources = "examples/supplemental/*.{xcassets}" - ss.dependency "MaterialMotionObservable/lib" + ss.dependency "IndefiniteObservable/lib" end s.subspec "tests" do |ss| ss.source_files = "tests/src/*.{swift}", "tests/src/private/*.{swift}" - ss.dependency "MaterialMotionObservable/lib" + ss.dependency "IndefiniteObservable/lib" end end diff --git a/Podfile b/Podfile index 1840dd5..fffab64 100644 --- a/Podfile +++ b/Podfile @@ -1,15 +1,18 @@ -workspace 'MaterialMotionObservable.xcworkspace' +workspace 'IndefiniteObservable.xcworkspace' use_frameworks! target "Catalog" do pod 'CatalogByConvention' - pod 'MaterialMotionObservable/examples', :path => './' + pod 'IndefiniteObservable/examples', :path => './' project 'examples/apps/Catalog/Catalog.xcodeproj' end -target "UnitTests" do +abstract_target 'Tests' do project 'examples/apps/Catalog/Catalog.xcodeproj' - pod 'MaterialMotionObservable/tests', :path => './' + pod 'IndefiniteObservable/tests', :path => './' + + target "UnitTests" + target "OSXTests" end post_install do |installer| diff --git a/Podfile.lock b/Podfile.lock new file mode 100644 index 0000000..f844b81 --- /dev/null +++ b/Podfile.lock @@ -0,0 +1,24 @@ +PODS: + - CatalogByConvention (2.0.0) + - IndefiniteObservable/examples (1.0.0): + - IndefiniteObservable/lib + - IndefiniteObservable/lib (1.0.0) + - IndefiniteObservable/tests (1.0.0): + - IndefiniteObservable/lib + +DEPENDENCIES: + - CatalogByConvention + - IndefiniteObservable/examples (from `./`) + - IndefiniteObservable/tests (from `./`) + +EXTERNAL SOURCES: + IndefiniteObservable: + :path: "./" + +SPEC CHECKSUMS: + CatalogByConvention: be55c2263132e4f9f59299ac8a528ee8715b3275 + IndefiniteObservable: 3efc90f559fea4010f8be8a9ab4f33ffa6718a44 + +PODFILE CHECKSUM: d381ba7e64de99c14e9c80a75f594f468c07327d + +COCOAPODS: 1.1.1 diff --git a/README.md b/README.md index 9886cac..08a0bdf 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,19 @@ -# Swift Observable +# IndefiniteObservable.swift -[![Build Status](https://travis-ci.org/material-motion/observable-swift.svg?branch=develop)](https://travis-ci.org/material-motion/observable-swift) -[![codecov](https://codecov.io/gh/material-motion/observable-swift/branch/develop/graph/badge.svg)](https://codecov.io/gh/material-motion/observable-swift) -[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/MaterialMotionObservable.svg)](https://cocoapods.org/pods/MaterialMotionObservable) -[![Platform](https://img.shields.io/cocoapods/p/MaterialMotionObservable.svg)](http://cocoadocs.org/docsets/MaterialMotionObservable) -[![Docs](https://img.shields.io/cocoapods/metrics/doc-percent/MaterialMotionObservable.svg)](http://cocoadocs.org/docsets/MaterialMotionObservable) +[![Build Status](https://travis-ci.org/material-motion/indefinite-observable-swift.svg?branch=develop)](https://travis-ci.org/material-motion/indefinite-observable-swift) +[![codecov](https://codecov.io/gh/material-motion/indefinite-observable-swift/branch/develop/graph/badge.svg)](https://codecov.io/gh/material-motion/indefinite-observable-swift) +[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/IndefiniteObservable.svg)](https://cocoapods.org/pods/IndefiniteObservable) +[![Platform](https://img.shields.io/cocoapods/p/IndefiniteObservable.svg)](http://cocoadocs.org/docsets/IndefiniteObservable) +[![Docs](https://img.shields.io/cocoapods/metrics/doc-percent/IndefiniteObservable.svg)](http://cocoadocs.org/docsets/IndefiniteObservable) + +`IndefiniteObservable` is a minimal implementation of [Observable](http://reactivex.io/rxjs/manual/overview.html) +with no concept of completion or failure. + +## Supported languages + +- Swift 3 + +This library does not support Objective-C due to its heavy use of generics. ## Installation @@ -17,9 +26,9 @@ > > gem install cocoapods -Add `MaterialMotionObservable` to your `Podfile`: +Add `IndefiniteObservable` to your `Podfile`: - pod 'MaterialMotionObservable' + pod 'IndefiniteObservable' Then run the following command: @@ -29,7 +38,7 @@ Then run the following command: Import the framework: - @import MaterialMotionObservable; + @import IndefiniteObservable; You will now have access to all of the APIs. @@ -38,25 +47,203 @@ You will now have access to all of the APIs. Check out a local copy of the repo to access the Catalog application by running the following commands: - git clone https://github.com/material-motion/observable-swift.git + git clone https://github.com/material-motion/indefinite-observable-swift.git cd observable-swift pod install - open MaterialMotionObservable.xcworkspace + open IndefiniteObservable.xcworkspace -## Guides +# Guides -1. [Architecture](#architecture) -2. [How to ...](#how-to-...) +1. [How to create a synchronous stream](#how-to-create-a-synchronous-stream) +2. [How to create an asynchronous stream using blocks](#how-to-create-an-asynchronous-stream-using-blocks) +3. [How to subscribe to a stream](#how-to-subscribe-to-a-stream) +4. [How to unsubscribe from a stream](#how-to-unsubscribe-from-a-stream) +5. [How to create an synchronous stream using objects](#how-to-create-an-synchronous-stream-using-objects) -### Architecture +## How to create a synchronous stream -### How to ... +```swift +let observable = IndefiniteObservable<<#ValueType#>> { observer in + observer.next(<#value#>) + return noUnsubscription +} +``` -## Contributing +## How to create an asynchronous stream using blocks + +If you have an API that provides a block-based mechanism for registering observers then you can +create an asynchronous stream in place like so: + +```swift +let observable = IndefiniteObservable<<#ValueType#>> { observer in + let someToken = registerSomeCallback { callbackValue in + observer.next(callbackValue) + } + + return { + unregisterCallback(someToken) + } +} +``` + +## How to subscribe to a stream + +Streams are kept in memory by their subscriptions. + +```swift +let subscription = observable.subscribe { value in + print(value) +} +``` + +## How to unsubscribe from a stream + +Unsubscribe from a stream to allow the stream to be released. The stream can be deallocated once all +of its subscriptions have unsubscribed. + +```swift +subscription.unsubscribe() +``` + +## How to create an synchronous stream using objects + +Many iOS/macOS APIs use delegation for event handling. To connect delegates with a stream you will +need to create a `Producer` class. A `Producer` listens for events with an event delegate like +`didTap` and forwards those events to an IndefiniteObservable's observer. + +### Final result + +```swift +class DragProducer: Subscription { + typealias Value = (state: UIGestureRecognizerState, location: CGPoint) + + init(subscribedTo gesture: UIPanGestureRecognizer, observer: AnyObserver) { + self.gesture = gesture + self.observer = observer + + gesture.addTarget(self, action: #selector(didPan)) + + // Populate the observer with the current gesture state. + observer.next(currentValue(for: gesture)) + } + + @objc func didPan(_ gesture: UIPanGestureRecognizer) { + observer.next(currentValue(for: gesture)) + } + + func currentValue(for gesture: UIPanGestureRecognizer) -> Value { + return (gesture.state, gesture.location(in: gesture.view!)) + } + + func unsubscribe() { + gesture?.removeTarget(self, action: #selector(didPan)) + gesture = nil + } + + var gesture: (UIPanGestureRecognizer)? + let observer: AnyObserver +} + +let pan = UIPanGestureRecognizer() +view.addGestureRecognizer(pan) -We welcome contributions! +let dragStream = IndefiniteObservable { observer in + return DragProducer(subscribedTo: pan, observer: observer).subscription +} +let subscription = dragStream.subscribe { + dump($0.state) + dump($0.location) +} +``` + +### Step 1: Define the Producer type + +A Producer should be a type of Subscription. + +```swift +class <#Name#>Producer: Subscription { +} +``` + +### Step 2: Define the Value type + +```swift +class DragProducer: Subscription { + typealias Value = (state: UIGestureRecognizerState, location: CGPoint) +} +``` + +### Step 3: Implement the initializer + +Your initializer must accept and store an `AnyObserver` instance. + +```swift + init(subscribedTo gesture: UIPanGestureRecognizer, observer: AnyObserver) { + self.gesture = gesture + self.observer = observer + } + + var gesture: (UIPanGestureRecognizer)? + let observer: AnyObserver +``` + +### Step 4: Connect to the event source and send values to the observer + +```swift + init(subscribedTo gesture: UIPanGestureRecognizer, observer: AnyObserver) { + ... + + gesture.addTarget(self, action: #selector(didPan)) + } + + @objc func didPan(_ gesture: UIPanGestureRecognizer) { + observer.next(currentValue(for: gesture)) + } + + func currentValue(for gesture: UIPanGestureRecognizer) -> Value { + return (gesture.state, gesture.location(in: gesture.view!)) + } +``` + +### Step 5: Implement unsubscribe + +You are responsible for disconnecting from and releasing any resources here. + +```swift + func unsubscribe() { + gesture?.removeTarget(self, action: #selector(didPan)) + gesture = nil + } +``` + +### Step 6: (Optional) Provide the initial state + +It often is helpful to provide the observer with the current state on registration. + +```swift + init(subscribedTo gesture: UIPanGestureRecognizer, observer: AnyObserver) { + ... + + // Populate the observer with the current gesture state. + observer.next(currentValue(for: gesture)) + } +``` + +### Step 7: Observe the producer + +```swift +let dragStream = IndefiniteObservable { observer in + return DragProducer(subscribedTo: pan, observer: observer).subscription +} +let subscription = dragStream.subscribe { + dump($0) +} +``` + +## Contributing -Check out our [upcoming milestones](https://github.com/material-motion/observable-swift/milestones). +This library is meant to be a minimal implementation that never grows. As such, we only encourage +contributions in the form of documentation, tests, and examples. Learn more about [our team](https://material-motion.github.io/material-motion/team/), [our community](https://material-motion.github.io/material-motion/team/community/), and diff --git a/examples/DelegateObservableExample.swift b/examples/DelegateObservableExample.swift new file mode 100644 index 0000000..06f229a --- /dev/null +++ b/examples/DelegateObservableExample.swift @@ -0,0 +1,86 @@ +/* + Copyright 2016-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit +import IndefiniteObservable + +// This example demonstrates how to observe state changes from a delegate. We use +// UIPanGestureRecognizer in this example, but the Producer pattern can be used for any delegated +// type. + +class DragProducer: Subscription { + typealias Value = (state: UIGestureRecognizerState, location: CGPoint) + + init(subscribedTo gesture: UIPanGestureRecognizer, observer: AnyObserver) { + self.gesture = gesture + self.observer = observer + + gesture.addTarget(self, action: #selector(didPan)) + + // Populate the observer with the current gesture state. + observer.next(currentValue(for: gesture)) + } + + @objc func didPan(_ gesture: UIPanGestureRecognizer) { + observer.next(currentValue(for: gesture)) + } + + func currentValue(for gesture: UIPanGestureRecognizer) -> Value { + return (gesture.state, gesture.location(in: gesture.view!)) + } + + func unsubscribe() { + gesture?.removeTarget(self, action: #selector(didPan)) + gesture = nil + } + + var gesture: (UIPanGestureRecognizer)? + let observer: AnyObserver +} + +public class DelegateObservableExampleViewController: UIViewController { + + var initialPosition: CGPoint = .zero + var subscriptions: [Subscription] = [] + override public func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + + let targetView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) + targetView.center = .init(x: view.frame.midX, y: view.frame.midY) + targetView.backgroundColor = .red + view.addSubview(targetView) + + let pan = UIPanGestureRecognizer() + view.addGestureRecognizer(pan) + + let dragStream = IndefiniteObservable { observer in + return DragProducer(subscribedTo: pan, observer: observer).unsubscribe + } + + // Must hold a reference to the subscription, otherwise the stream will be deallocated when the + // subscription goes out of scope. + subscriptions.append(dragStream.subscribe { + if $0.state == .began || $0.state == .changed { + targetView.layer.position = $0.location + } + }) + + subscriptions.append(dragStream.subscribe { + print($0.state.rawValue) + }) + } +} diff --git a/examples/OperatorExample.swift b/examples/OperatorExample.swift new file mode 100644 index 0000000..51e0891 --- /dev/null +++ b/examples/OperatorExample.swift @@ -0,0 +1,83 @@ +/* + Copyright 2016-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit +import IndefiniteObservable + +// This example demonstrates how to create custom operators that can be chained to an +// IndefiniteObservable. + +extension IndefiniteObservable { + + // Map from one value type to another. + public func map(_ transform: @escaping (T) -> U) -> IndefiniteObservable { + return IndefiniteObservable { observer in + return self.subscribe { + observer.next(transform($0)) + }.unsubscribe + } + } + + // Only emit values downstream for which passesTest returns true + public func filter(_ passesTest: @escaping (T) -> Bool) -> IndefiniteObservable { + return IndefiniteObservable { observer in + return self.subscribe { + if passesTest($0) { + observer.next($0) + } + }.unsubscribe + } + } +} + +public class OperatorExampleViewController: UIViewController { + + var initialPosition: CGPoint = .zero + var subscriptions: [Subscription] = [] + override public func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + + let targetView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) + targetView.center = .init(x: view.frame.midX, y: view.frame.midY) + targetView.backgroundColor = .red + view.addSubview(targetView) + + let pan = UIPanGestureRecognizer() + view.addGestureRecognizer(pan) + + let dragStream = IndefiniteObservable { observer in + return DragProducer(subscribedTo: pan, observer: observer).unsubscribe + } + + // Note that we avoid keep a strong reference to self in the stream's operators. + // A strong reference would create a retain cycle: + // + // subscription -> stream -> operator -> self -> subscriptions + // \------------------------------------/ + // + let midX = self.view.bounds.midX + + subscriptions.append(dragStream + .filter { $0.state == .began || $0.state == .changed } + .map { $0.location } + .map { .init(x: midX, y: $0.y) } + .subscribe { + targetView.layer.position = $0 + } + ) + } +} diff --git a/examples/apps/Catalog/Catalog.xcodeproj/project.pbxproj b/examples/apps/Catalog/Catalog.xcodeproj/project.pbxproj index b57fa2f..0e0d4af 100644 --- a/examples/apps/Catalog/Catalog.xcodeproj/project.pbxproj +++ b/examples/apps/Catalog/Catalog.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 2A0E88DD8D7CD00441B864B7 /* Pods_Catalog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6961ADFDA1A71199463A2EEC /* Pods_Catalog.framework */; }; 664A5B771DD10D320082B5DF /* TableOfContents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664A5B761DD10D320082B5DF /* TableOfContents.swift */; }; 666FAA841D384A6B000363DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 666FAA831D384A6B000363DA /* AppDelegate.swift */; }; 666FAA8B1D384A6B000363DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 666FAA8A1D384A6B000363DA /* Assets.xcassets */; }; @@ -14,6 +15,14 @@ 667A3F5C1DEE276000CB3A99 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 667A3F5B1DEE276000CB3A99 /* AppDelegate.swift */; }; 667A3F631DEE276000CB3A99 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 667A3F621DEE276000CB3A99 /* Assets.xcassets */; }; 667A3F661DEE276000CB3A99 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 667A3F641DEE276000CB3A99 /* LaunchScreen.storyboard */; }; + 6691DF911DF1735F00F2CF8B /* MemoryLeakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6691DF8F1DF1735F00F2CF8B /* MemoryLeakTests.swift */; }; + 6691DF921DF1735F00F2CF8B /* ObservableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6691DF901DF1735F00F2CF8B /* ObservableTests.swift */; }; + 6691DF941DF1745300F2CF8B /* SimpleOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6691DF931DF1745300F2CF8B /* SimpleOperators.swift */; }; + 66FCB4821DF2675600E3D2F1 /* MemoryLeakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6691DF8F1DF1735F00F2CF8B /* MemoryLeakTests.swift */; }; + 66FCB4831DF2675600E3D2F1 /* SimpleOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6691DF931DF1745300F2CF8B /* SimpleOperators.swift */; }; + 66FCB4841DF2675600E3D2F1 /* ObservableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6691DF901DF1735F00F2CF8B /* ObservableTests.swift */; }; + 8B95F21D1FAB12C447423986 /* Pods_Tests_UnitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E77A541AC619F1A6DF56643A /* Pods_Tests_UnitTests.framework */; }; + 8BBF531E342C62F1D77ACF64 /* Pods_Tests_OSXTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1BEF6C8376388E4773A8F8C1 /* Pods_Tests_OSXTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -34,6 +43,9 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 1BEF6C8376388E4773A8F8C1 /* Pods_Tests_OSXTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Tests_OSXTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 396A31018358EC92DBCBA8E9 /* Pods-Tests-UnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-UnitTests.release.xcconfig"; path = "../../../Pods/Target Support Files/Pods-Tests-UnitTests/Pods-Tests-UnitTests.release.xcconfig"; sourceTree = ""; }; + 4418D94811370DAB3BACF8BA /* Pods-Catalog.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Catalog.release.xcconfig"; path = "../../../Pods/Target Support Files/Pods-Catalog/Pods-Catalog.release.xcconfig"; sourceTree = ""; }; 664A5B761DD10D320082B5DF /* TableOfContents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TableOfContents.swift; path = Catalog/TableOfContents.swift; sourceTree = ""; }; 666FAA801D384A6B000363DA /* Catalog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Catalog.app; sourceTree = BUILT_PRODUCTS_DIR; }; 666FAA831D384A6B000363DA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Catalog/AppDelegate.swift; sourceTree = ""; }; @@ -47,9 +59,28 @@ 667A3F621DEE276000CB3A99 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 667A3F651DEE276000CB3A99 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 667A3F671DEE276000CB3A99 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 6691DF8F1DF1735F00F2CF8B /* MemoryLeakTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryLeakTests.swift; sourceTree = ""; }; + 6691DF901DF1735F00F2CF8B /* ObservableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableTests.swift; sourceTree = ""; }; + 6691DF931DF1745300F2CF8B /* SimpleOperators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleOperators.swift; sourceTree = ""; }; + 66FCB47A1DF2673800E3D2F1 /* OSXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OSXTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 66FCB47E1DF2673800E3D2F1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 6961ADFDA1A71199463A2EEC /* Pods_Catalog.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Catalog.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 73F2FDB408767F29680BE4EB /* Pods-Catalog.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Catalog.debug.xcconfig"; path = "../../../Pods/Target Support Files/Pods-Catalog/Pods-Catalog.debug.xcconfig"; sourceTree = ""; }; + 8AA59027A9B127C73A45E638 /* Pods-Tests-UnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-UnitTests.debug.xcconfig"; path = "../../../Pods/Target Support Files/Pods-Tests-UnitTests/Pods-Tests-UnitTests.debug.xcconfig"; sourceTree = ""; }; + A0E80974FF54D3B737ED920A /* Pods-Tests-OSXTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-OSXTests.debug.xcconfig"; path = "../../../Pods/Target Support Files/Pods-Tests-OSXTests/Pods-Tests-OSXTests.debug.xcconfig"; sourceTree = ""; }; + DD10C9D2D5ADCB22F5B6DB5B /* Pods-Tests-OSXTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-OSXTests.release.xcconfig"; path = "../../../Pods/Target Support Files/Pods-Tests-OSXTests/Pods-Tests-OSXTests.release.xcconfig"; sourceTree = ""; }; + E77A541AC619F1A6DF56643A /* Pods_Tests_UnitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Tests_UnitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 4B4451C4A14828CECD769414 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8B95F21D1FAB12C447423986 /* Pods_Tests_UnitTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 667A3F561DEE275F00CB3A99 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -57,6 +88,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 66FCB4771DF2673800E3D2F1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8BBF531E342C62F1D77ACF64 /* Pods_Tests_OSXTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B7F3E37CA8F612F26C4BA6CE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2A0E88DD8D7CD00441B864B7 /* Pods_Catalog.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -64,11 +111,12 @@ isa = PBXGroup; children = ( 664A5B761DD10D320082B5DF /* TableOfContents.swift */, - 666FAAA31D384B13000363DA /* examples */, 666FAAA61D384B77000363DA /* app */, 666FAA971D384A6B000363DA /* tests */, 666FAA821D384A6B000363DA /* resources */, 666FAA811D384A6B000363DA /* Products */, + EF63B4F810D6734509169867 /* Pods */, + CD4C223B004D44A470664E95 /* Frameworks */, ); sourceTree = ""; }; @@ -78,6 +126,7 @@ 666FAA801D384A6B000363DA /* Catalog.app */, 666FAA941D384A6B000363DA /* UnitTests.xctest */, 667A3F591DEE275F00CB3A99 /* TestHarness.app */, + 66FCB47A1DF2673800E3D2F1 /* OSXTests.xctest */, ); name = Products; sourceTree = ""; @@ -85,6 +134,7 @@ 666FAA821D384A6B000363DA /* resources */ = { isa = PBXGroup; children = ( + 66FCB47B1DF2673800E3D2F1 /* OSXTests */, 667A3F5A1DEE276000CB3A99 /* TestHarness */, 666FAAA51D384B5B000363DA /* catalog */, 666FAAA41D384B52000363DA /* tests */, @@ -96,19 +146,12 @@ 666FAA971D384A6B000363DA /* tests */ = { isa = PBXGroup; children = ( + 6691DF8E1DF1735F00F2CF8B /* unit */, ); name = tests; path = ../../../tests/unit; sourceTree = ""; }; - 666FAAA31D384B13000363DA /* examples */ = { - isa = PBXGroup; - children = ( - ); - name = examples; - path = ../..; - sourceTree = ""; - }; 666FAAA41D384B52000363DA /* tests */ = { isa = PBXGroup; children = ( @@ -147,6 +190,48 @@ path = ../TestHarness; sourceTree = ""; }; + 6691DF8E1DF1735F00F2CF8B /* unit */ = { + isa = PBXGroup; + children = ( + 6691DF8F1DF1735F00F2CF8B /* MemoryLeakTests.swift */, + 6691DF901DF1735F00F2CF8B /* ObservableTests.swift */, + 6691DF931DF1745300F2CF8B /* SimpleOperators.swift */, + ); + name = unit; + sourceTree = ""; + }; + 66FCB47B1DF2673800E3D2F1 /* OSXTests */ = { + isa = PBXGroup; + children = ( + 66FCB47E1DF2673800E3D2F1 /* Info.plist */, + ); + name = OSXTests; + path = ../OSXTests; + sourceTree = ""; + }; + CD4C223B004D44A470664E95 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 6961ADFDA1A71199463A2EEC /* Pods_Catalog.framework */, + 1BEF6C8376388E4773A8F8C1 /* Pods_Tests_OSXTests.framework */, + E77A541AC619F1A6DF56643A /* Pods_Tests_UnitTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + EF63B4F810D6734509169867 /* Pods */ = { + isa = PBXGroup; + children = ( + 73F2FDB408767F29680BE4EB /* Pods-Catalog.debug.xcconfig */, + 4418D94811370DAB3BACF8BA /* Pods-Catalog.release.xcconfig */, + A0E80974FF54D3B737ED920A /* Pods-Tests-OSXTests.debug.xcconfig */, + DD10C9D2D5ADCB22F5B6DB5B /* Pods-Tests-OSXTests.release.xcconfig */, + 8AA59027A9B127C73A45E638 /* Pods-Tests-UnitTests.debug.xcconfig */, + 396A31018358EC92DBCBA8E9 /* Pods-Tests-UnitTests.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -154,8 +239,12 @@ isa = PBXNativeTarget; buildConfigurationList = 666FAA9D1D384A6B000363DA /* Build configuration list for PBXNativeTarget "Catalog" */; buildPhases = ( + C422812492166EECC504B121 /* [CP] Check Pods Manifest.lock */, 666FAA7C1D384A6B000363DA /* Sources */, 666FAA7E1D384A6B000363DA /* Resources */, + B7F3E37CA8F612F26C4BA6CE /* Frameworks */, + 523B46BDF9B37C8EF3589DC2 /* [CP] Embed Pods Frameworks */, + E1232B415265535486F5B675 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -170,8 +259,12 @@ isa = PBXNativeTarget; buildConfigurationList = 666FAAA01D384A6B000363DA /* Build configuration list for PBXNativeTarget "UnitTests" */; buildPhases = ( + AD785605919240626175A552 /* [CP] Check Pods Manifest.lock */, 666FAA901D384A6B000363DA /* Sources */, 666FAA921D384A6B000363DA /* Resources */, + 4B4451C4A14828CECD769414 /* Frameworks */, + 0FE04FCB6F2248E66DC3A0F1 /* [CP] Embed Pods Frameworks */, + 80D2B9EF5137CC2B9C533C34 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -201,6 +294,26 @@ productReference = 667A3F591DEE275F00CB3A99 /* TestHarness.app */; productType = "com.apple.product-type.application"; }; + 66FCB4791DF2673800E3D2F1 /* OSXTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 66FCB4811DF2673800E3D2F1 /* Build configuration list for PBXNativeTarget "OSXTests" */; + buildPhases = ( + D4A83BA44BF49522B2594F38 /* [CP] Check Pods Manifest.lock */, + 66FCB4761DF2673800E3D2F1 /* Sources */, + 66FCB4771DF2673800E3D2F1 /* Frameworks */, + 66FCB4781DF2673800E3D2F1 /* Resources */, + E848787DC418C7BB44030A64 /* [CP] Embed Pods Frameworks */, + F19FB7F8EA4A80839B14C1FB /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = OSXTests; + productName = OSXTests; + productReference = 66FCB47A1DF2673800E3D2F1 /* OSXTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -224,6 +337,10 @@ CreatedOnToolsVersion = 8.1; ProvisioningStyle = Automatic; }; + 66FCB4791DF2673800E3D2F1 = { + CreatedOnToolsVersion = 8.1; + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = 666FAA7B1D384A6B000363DA /* Build configuration list for PBXProject "Catalog" */; @@ -242,6 +359,7 @@ 666FAA7F1D384A6B000363DA /* Catalog */, 667A3F581DEE275F00CB3A99 /* TestHarness */, 666FAA931D384A6B000363DA /* UnitTests */, + 66FCB4791DF2673800E3D2F1 /* OSXTests */, ); }; /* End PBXProject section */ @@ -272,8 +390,153 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 66FCB4781DF2673800E3D2F1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 0FE04FCB6F2248E66DC3A0F1 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/../../../Pods/Target Support Files/Pods-Tests-UnitTests/Pods-Tests-UnitTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 523B46BDF9B37C8EF3589DC2 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/../../../Pods/Target Support Files/Pods-Catalog/Pods-Catalog-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 80D2B9EF5137CC2B9C533C34 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/../../../Pods/Target Support Files/Pods-Tests-UnitTests/Pods-Tests-UnitTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + AD785605919240626175A552 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + C422812492166EECC504B121 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + D4A83BA44BF49522B2594F38 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + E1232B415265535486F5B675 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/../../../Pods/Target Support Files/Pods-Catalog/Pods-Catalog-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + E848787DC418C7BB44030A64 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/../../../Pods/Target Support Files/Pods-Tests-OSXTests/Pods-Tests-OSXTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + F19FB7F8EA4A80839B14C1FB /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/../../../Pods/Target Support Files/Pods-Tests-OSXTests/Pods-Tests-OSXTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 666FAA7C1D384A6B000363DA /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -288,6 +551,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 6691DF921DF1735F00F2CF8B /* ObservableTests.swift in Sources */, + 6691DF941DF1745300F2CF8B /* SimpleOperators.swift in Sources */, + 6691DF911DF1735F00F2CF8B /* MemoryLeakTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -299,6 +565,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 66FCB4761DF2673800E3D2F1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 66FCB4821DF2675600E3D2F1 /* MemoryLeakTests.swift in Sources */, + 66FCB4841DF2675600E3D2F1 /* ObservableTests.swift in Sources */, + 66FCB4831DF2675600E3D2F1 /* SimpleOperators.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -372,6 +648,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.9; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -412,6 +689,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.9; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -421,6 +699,7 @@ }; 666FAA9E1D384A6B000363DA /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 73F2FDB408767F29680BE4EB /* Pods-Catalog.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Catalog/Info.plist; @@ -433,6 +712,7 @@ }; 666FAA9F1D384A6B000363DA /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 4418D94811370DAB3BACF8BA /* Pods-Catalog.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Catalog/Info.plist; @@ -446,8 +726,8 @@ }; 666FAAA11D384A6B000363DA /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 8AA59027A9B127C73A45E638 /* Pods-Tests-UnitTests.debug.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = UnitTests/Info.plist; @@ -462,8 +742,8 @@ }; 666FAAA21D384A6B000363DA /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 396A31018358EC92DBCBA8E9 /* Pods-Tests-UnitTests.release.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = UnitTests/Info.plist; @@ -508,6 +788,44 @@ }; name = Release; }; + 66FCB47F1DF2673800E3D2F1 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A0E80974FF54D3B737ED920A /* Pods-Tests-OSXTests.debug.xcconfig */; + buildSettings = { + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = OSXTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.google.OSXTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 66FCB4801DF2673800E3D2F1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DD10C9D2D5ADCB22F5B6DB5B /* Pods-Tests-OSXTests.release.xcconfig */; + buildSettings = { + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = OSXTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.google.OSXTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -545,6 +863,16 @@ 667A3F6A1DEE276000CB3A99 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 66FCB4811DF2673800E3D2F1 /* Build configuration list for PBXNativeTarget "OSXTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 66FCB47F1DF2673800E3D2F1 /* Debug */, + 66FCB4801DF2673800E3D2F1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/examples/apps/Catalog/Catalog/AppDelegate.swift b/examples/apps/Catalog/Catalog/AppDelegate.swift index 5b06a79..8c57eec 100644 --- a/examples/apps/Catalog/Catalog/AppDelegate.swift +++ b/examples/apps/Catalog/Catalog/AppDelegate.swift @@ -27,7 +27,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { self.window = window let rootViewController = CBCNodeListViewController(node: CBCCreateNavigationTree()) - rootViewController.title = "Swift Observable" + rootViewController.title = "IndefiniteObservable.swift" window.rootViewController = UINavigationController(rootViewController: rootViewController) window.makeKeyAndVisible() diff --git a/examples/apps/Catalog/Catalog/TableOfContents.swift b/examples/apps/Catalog/Catalog/TableOfContents.swift index 4c08961..43a486c 100644 --- a/examples/apps/Catalog/Catalog/TableOfContents.swift +++ b/examples/apps/Catalog/Catalog/TableOfContents.swift @@ -15,13 +15,16 @@ */ // MARK: Catalog by convention +import IndefiniteObservable -// Example entry in the table of contents: -// Extend a UIViewController instance and implement catalogBreadcrumbs(), returning the list of -// breadcrumbs required to navigate to an instance of this view controller. -// -//extension ExampleViewController { -// class func catalogBreadcrumbs() -> [String] { -// return ["Example"] -// } -//} +extension DelegateObservableExampleViewController { + class func catalogBreadcrumbs() -> [String] { + return ["How to observe a delegate"] + } +} + +extension OperatorExampleViewController { + class func catalogBreadcrumbs() -> [String] { + return ["How to create an operator"] + } +} diff --git a/examples/apps/Catalog/OSXTests/Info.plist b/examples/apps/Catalog/OSXTests/Info.plist new file mode 100644 index 0000000..6c6c23c --- /dev/null +++ b/examples/apps/Catalog/OSXTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/src/IndefiniteObservable.swift b/src/IndefiniteObservable.swift new file mode 100644 index 0000000..f67d7aa --- /dev/null +++ b/src/IndefiniteObservable.swift @@ -0,0 +1,146 @@ +/* + Copyright 2016-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +/** + An IndefiniteObservable represents a sequence of values that may be observed. + + IndefiniteObservable is meant for use with streams of values that have no concept of completion. + + This is an implementation of a subset of the Observable interface defined at http://reactivex.io/ + + Simple synchronous stream: + + let observable = IndefiniteObservable { observer in + observer.next(5) + return noUnsubscription + } + + let subscription = observable.subscribe { value in + print(value) + } + + subscription.unsubscribe() + + Example of an asynchronous stream: + + let observable = IndefiniteObservable { observer in + let someToken = registerSomeCallback { callbackValue in + observer.next(callbackValue) + } + + return SimpleSubscription { + unregisterCallback(someToken) + } + } + */ +open class IndefiniteObservable { + public typealias Subscriber = (AnyObserver) -> (() -> Void)? + + /** A subscriber is only invoked when subscribe is invoked. */ + public init(_ subscriber: @escaping Subscriber) { + self.subscriber = subscriber + } + + /** + Subscribes to the IndefiniteObservable. + + The returned subscription will hold a strong reference to the IndefiniteObservable chain. The + reference can be released by calling unsubscribe on the returned subscription. The Subscription + is type-erased, making it possible to keep a collection of Subscription objects for as long as + you need the associated streams alive. + + - Parameter next: A block that will be executed when new values are sent from upstream. + - Returns: A subscription. + */ + public func subscribe(next: @escaping (T) -> Void) -> Subscription { + let observer = AnyObserver(next) + + // This line creates our "downstream" data flow. + let subscription = subscriber(AnyObserver { observer.next($0) }) + + // We store a strong reference to self in the subscription in order to keep the stream alive. + // When the subscription goes away, so does the stream. + return UpstreamSubscription(observable: self) { + subscription?() + } + } + + private let subscriber: Subscriber +} + +/** An Observer receives data from an IndefiniteObservable. */ +public protocol Observer { + associatedtype Value + func next(_ value: Value) -> Void +} + +/** A Subscription is returned by IndefiniteObservable.subscribe. */ +public protocol Subscription { + func unsubscribe() +} + +/** + A no-op subscription that can be returned by subscribers when there is no need for teardown. + + Does nothing when unsubscribe is invoked. + + Example: + + let observable = IndefiniteObservable { observer in + observer.next(5) + + return noUnsubscription + } + */ +public let noUnsubscription: (() -> Void)? = nil + +// MARK: Type erasing + +/** A type-erased observer. */ +public final class AnyObserver: Observer { + public typealias Value = T + + init(_ next: @escaping (Value) -> Void) { + _next = next + } + + public func next(_ value: Value) { + _next(value) + } + + private let _next: (Value) -> Void +} + +// MARK: Private + +// Internal class for ensuring that an active subscription keeps its stream alive. +// Streams don't hold strong references down the chain, so our subscriptions hold strong references +// "up" the chain to the IndefiniteObservable type. +private final class UpstreamSubscription: Subscription { + init(observable: Any, _ unsubscribe: @escaping () -> Void) { + _observable = observable + _unsubscribe = unsubscribe + } + + func unsubscribe() { + _unsubscribe?() + _unsubscribe = nil + _observable = nil + } + + private var _unsubscribe: (() -> Void)? + private var _observable: Any? +} diff --git a/tests/unit/MemoryLeakTests.swift b/tests/unit/MemoryLeakTests.swift new file mode 100644 index 0000000..bc9ff89 --- /dev/null +++ b/tests/unit/MemoryLeakTests.swift @@ -0,0 +1,146 @@ +/* + Copyright 2016-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import XCTest +import CoreGraphics +import IndefiniteObservable + +class MemoryLeakTests: XCTestCase { + func testObservableIsDeallocated() { + var observable: IndefiniteObservable? = IndefiniteObservable { observer in + observer.next(5) + return noUnsubscription + } + weak var weakObservable = observable + + autoreleasepool { + // Remove our only strong reference. + observable = nil + } + + // If this fails it means there's a retain cycle. Place a breakpoint here and use the Debug + // Memory Graph tool to debug. + XCTAssertNil(weakObservable) + } + + func testDownstreamObservableKeepsUpstreamAlive() { + var observable: IndefiniteObservable? = IndefiniteObservable { observer in + observer.next(5) + return noUnsubscription + } + weak var weakObservable = observable + + let downstream = observable!.map { $0 } + + autoreleasepool { + observable = nil + } + + let _ = downstream // Silence warnings. + + // The downstream ref should keep our observable in scope. + XCTAssertNotNil(weakObservable) + } + + func testSubscribedObservableIsDeallocated() { + var observable: IndefiniteObservable? = IndefiniteObservable { observer in + observer.next(5) + return noUnsubscription + } + weak var weakObservable = observable + + autoreleasepool { + let _ = observable!.subscribe(next: { + let _ = $0 + }) + // Remove our only strong reference. + observable = nil + } + + // If this fails it means there's a retain cycle. Place a breakpoint here and use the Debug + // Memory Graph tool to debug. + XCTAssertNil(weakObservable) + } + + func testSubscribedObservableWithOperatorIsDeallocated() { + var observable: IndefiniteObservable? = IndefiniteObservable { observer in + observer.next(5) + return noUnsubscription + } + weak var weakObservable = observable + + autoreleasepool { + let _ = observable!.map { value in + return value * value + }.subscribe(next: { + let _ = $0 + }) + // Remove our only strong reference. + observable = nil + } + + // If this fails it means there's a retain cycle. Place a breakpoint here and use the Debug + // Memory Graph tool to debug. + XCTAssertNil(weakObservable) + } + + func testUnsubscribedObservableWithOperatorIsDeallocated() { + weak var weakObservable: IndefiniteObservable? + autoreleasepool { + let observable: IndefiniteObservable? = IndefiniteObservable { observer in + observer.next(5) + return noUnsubscription + } + weakObservable = observable + + let subscription = observable!.map { value in + return value * value + }.subscribe(next: { + let _ = $0 + }) + // Remove our only strong reference. + subscription.unsubscribe() + } + + // If this fails it means there's a retain cycle. Place a breakpoint here and use the Debug + // Memory Graph tool to debug. + XCTAssertNil(weakObservable) + } + + func testSubscriptionKeepsObservableInMemory() { + weak var weakObservable: IndefiniteObservable? + var subscription: Subscription? + + autoreleasepool { + let value = 10 + let observable = IndefiniteObservable { observer in + observer.next(value) + return noUnsubscription + } + weakObservable = observable + + subscription = observable.subscribe { _ in } + } + + XCTAssertNotNil(weakObservable) + + subscription?.unsubscribe() + + // If this fails it means there's a retain cycle. Place a breakpoint here and use the Debug + // Memory Graph tool to debug. + XCTAssertNil(weakObservable) + } +} diff --git a/tests/unit/ObservableTests.swift b/tests/unit/ObservableTests.swift new file mode 100644 index 0000000..8de9fe5 --- /dev/null +++ b/tests/unit/ObservableTests.swift @@ -0,0 +1,257 @@ +/* + Copyright 2016-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import XCTest +import CoreGraphics +@testable import IndefiniteObservable + +class ObservableTests: XCTestCase { + + func testSubscription() { + let value = 10 + + let observable = IndefiniteObservable { observer in + observer.next(value) + return noUnsubscription + } + + let wasReceived = expectation(description: "Value was received") + let _ = observable.subscribe { + if $0 == value { + wasReceived.fulfill() + } + } + + waitForExpectations(timeout: 0) + } + + func testTwoSubsequentSubscriptions() { + let value = 10 + + let observable = IndefiniteObservable { observer in + observer.next(value) + return noUnsubscription + } + + let wasReceived = expectation(description: "Value was received") + let _ = observable.subscribe { + if $0 == value { + wasReceived.fulfill() + } + } + + let wasReceived2 = expectation(description: "Value was received") + let _ = observable.subscribe { + if $0 == value { + wasReceived2.fulfill() + } + } + + waitForExpectations(timeout: 0) + } + + func testTwoParalellSubscriptions() { + let value = 10 + + let observable = IndefiniteObservable { observer in + observer.next(value) + return noUnsubscription + } + + let wasReceived = expectation(description: "Value was received") + let subscription1 = observable.subscribe { + if $0 == value { + wasReceived.fulfill() + } + } + + let wasReceived2 = expectation(description: "Value was received") + let subscription2 = observable.subscribe { + if $0 == value { + wasReceived2.fulfill() + } + } + + waitForExpectations(timeout: 0) + + subscription1.unsubscribe() + subscription2.unsubscribe() + } + + func testMappingValues() { + let value = 10 + let observable = IndefiniteObservable { observer in + observer.next(value) + return noUnsubscription + } + + let wasReceived = expectation(description: "Value was received") + let _ = observable.map { $0 * $0 }.subscribe { + if $0 == value * value { + wasReceived.fulfill() + } + } + + waitForExpectations(timeout: 0) + } + + func testMappingTypes() { + let value = CGPoint(x: 0, y: 10) + let observable = IndefiniteObservable { observer in + observer.next(value) + return noUnsubscription + } + + let wasReceived = expectation(description: "Value was received") + let _ = observable.map { $0.y }.subscribe { + if $0 == value.y { + wasReceived.fulfill() + } + } + + waitForExpectations(timeout: 0) + } + + func testFilteringValues() { + let value = CGPoint(x: 0, y: 10) + let observable = IndefiniteObservable<(Bool, CGPoint)> { observer in + observer.next((false, value)) + observer.next((true, value)) + return noUnsubscription + } + + var filteredValues: [CGPoint] = [] + let _ = observable.filter { (state, _) in state == true }.map { $0.1 }.subscribe { + filteredValues.append($0) + } + + XCTAssertEqual(filteredValues, [value]) + } + + class DeferredGenerator { + func addObserver(_ observer: AnyObserver) { + observers.append(observer) + } + + func removeObserver(_ observer: AnyObserver) { + if let index = observers.index(where: { $0 === observer }) { + observers.remove(at: index) + } + } + + func emit(_ value: Int) { + for observer in observers { + observer.next(value) + } + } + var observers: [AnyObserver] = [] + } + + func testGeneratedValuesAreReceived() { + let generator = DeferredGenerator() + + let observable = IndefiniteObservable { observer in + generator.addObserver(observer) + return { + generator.removeObserver(observer) + } + } + + var valuesObserved: [Int] = [] + let subscription1 = observable.subscribe { + valuesObserved.append($0) + } + + let subscription2 = observable.subscribe { + valuesObserved.append($0 * 2) + } + + generator.emit(5) + generator.emit(10) + generator.emit(2) + + XCTAssertEqual(valuesObserved, [5, 10, 10, 20, 2, 4]) + + subscription1.unsubscribe() + subscription2.unsubscribe() + } + + func testGeneratedValuesAreNotReceivedAfterUnsubscription() { + let generator = DeferredGenerator() + + let observable = IndefiniteObservable { observer in + generator.addObserver(observer) + return { + generator.removeObserver(observer) + } + } + + var valuesObserved: [Int] = [] + let subscription1 = observable.subscribe { + valuesObserved.append($0) + } + + let subscription2 = observable.subscribe { + valuesObserved.append($0 * 2) + } + + generator.emit(5) + generator.emit(10) + subscription2.unsubscribe() + generator.emit(2) + + XCTAssertEqual(valuesObserved, [5, 10, 10, 20, 2]) + + subscription1.unsubscribe() + } + + func testGeneratedValuesAreNotReceivedAfterUnsubscriptionOrder2() { + weak var weakObservable: IndefiniteObservable? + autoreleasepool { + let generator = DeferredGenerator() + + let observable = IndefiniteObservable { observer in + generator.addObserver(observer) + return { + generator.removeObserver(observer) + } + } + weakObservable = observable + + var valuesObserved: [Int] = [] + let subscription1 = observable.subscribe { + valuesObserved.append($0) + } + + let subscription2 = observable.map { $0 * 2 }.subscribe { + valuesObserved.append($0) + } + + generator.emit(5) + generator.emit(10) + subscription1.unsubscribe() + generator.emit(2) + + XCTAssertEqual(valuesObserved, [5, 10, 10, 20, 4]) + + subscription2.unsubscribe() + } + + // If this fails it means there's a retain cycle. Place a breakpoint here and use the Debug + // Memory Graph tool to debug. + XCTAssertNil(weakObservable) + } +} diff --git a/tests/unit/SimpleOperators.swift b/tests/unit/SimpleOperators.swift new file mode 100644 index 0000000..20ffc59 --- /dev/null +++ b/tests/unit/SimpleOperators.swift @@ -0,0 +1,40 @@ +/* + Copyright 2016-present The Material Motion Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import IndefiniteObservable + +// Simple operators used by the tests. + +extension IndefiniteObservable { + public func map(_ transform: @escaping (T) -> U) -> IndefiniteObservable { + return IndefiniteObservable { observer in + return self.subscribe { + observer.next(transform($0)) + }.unsubscribe + } + } + + public func filter(_ isIncluded: @escaping (T) -> Bool) -> IndefiniteObservable { + return IndefiniteObservable { observer in + return self.subscribe { + if isIncluded($0) { + observer.next($0) + } + }.unsubscribe + } + } +} From 4f3b48a12f1e2a57d1b251db0c6a8a46f58e5e41 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Sat, 3 Dec 2016 13:08:30 -0800 Subject: [PATCH 2/5] Fix outdated reference in header docs. --- src/IndefiniteObservable.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IndefiniteObservable.swift b/src/IndefiniteObservable.swift index f67d7aa..2a7ee35 100644 --- a/src/IndefiniteObservable.swift +++ b/src/IndefiniteObservable.swift @@ -41,7 +41,7 @@ observer.next(callbackValue) } - return SimpleSubscription { + return { unregisterCallback(someToken) } } From 2296221e05b9e126d0041af3836fced35e9d17f2 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Sat, 3 Dec 2016 13:09:34 -0800 Subject: [PATCH 3/5] Automatic changelog preparation for release. --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29..d992cd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# release-candidate + + TODO: Enumerate changes. From 9c0e236d109cc310241fc4a5350c602fab799aff Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Sat, 3 Dec 2016 13:12:02 -0800 Subject: [PATCH 4/5] Update CHANGELOG.md. --- CHANGELOG.md | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d992cd2..17ed46d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,44 @@ -# release-candidate +# 1.0.0 - TODO: Enumerate changes. +Stable release of `IndefiniteObservable`. + +## Source changes + +* [Fix outdated reference in header docs.](https://github.com/material-motion/indefinite-observable-swift/commit/4f3b48a12f1e2a57d1b251db0c6a8a46f58e5e41) (Jeff Verkoeyen) +* [Initial commit of IndefiniteObservable.](https://github.com/material-motion/indefinite-observable-swift/commit/f64ea2a587f03ac898198710347f65c78788fb26) (Jeff Verkoeyen) + +## API changes + +Auto-generated by running: + + apidiff origin/stable release-candidate swift IndefiniteObservable.xcworkspace IndefiniteObservable + +## Subscription + +*new* method: `unsubscribe()` in `Subscription` + +*new* protocol: `Subscription` + +## noUnsubscription + +*new* global var: `noUnsubscription` + +## Observer + +*new* protocol: `Observer` + +*new* method: `next(_:)` in `Observer` + +## AnyObserver + +*new* class: `AnyObserver` + +*new* method: `next(_:)` in `AnyObserver` + +## IndefiniteObservable + +*new* method: `init(_:)` in `IndefiniteObservable` + +*new* method: `subscribe(next:)` in `IndefiniteObservable` + +*new* class: `IndefiniteObservable` From df9ee040d5a20da9b2d9e8067daa23351e5ab198 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Sat, 3 Dec 2016 13:17:28 -0800 Subject: [PATCH 5/5] Clean up podspec and Podfile in reaction to pod lib lint warnings. --- IndefiniteObservable.podspec | 14 +++++++------- Podfile | 2 +- Podfile.lock | 8 +++----- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/IndefiniteObservable.podspec b/IndefiniteObservable.podspec index 8f96ec7..0e08284 100644 --- a/IndefiniteObservable.podspec +++ b/IndefiniteObservable.podspec @@ -16,14 +16,14 @@ Pod::Spec.new do |s| end s.subspec "examples" do |ss| - ss.source_files = "examples/*.{swift}", "examples/supplemental/*.{swift}" - ss.exclude_files = "examples/TableOfContents.swift" - ss.resources = "examples/supplemental/*.{xcassets}" + ss.ios.source_files = "examples/*.{swift}", "examples/supplemental/*.{swift}" + ss.ios.exclude_files = "examples/TableOfContents.swift" + #ss.resources = "examples/supplemental/*.{xcassets}" ss.dependency "IndefiniteObservable/lib" end - s.subspec "tests" do |ss| - ss.source_files = "tests/src/*.{swift}", "tests/src/private/*.{swift}" - ss.dependency "IndefiniteObservable/lib" - end + #s.subspec "tests" do |ss| + # ss.source_files = "tests/src/*.{swift}", "tests/src/private/*.{swift}" + # ss.dependency "IndefiniteObservable/lib" + #end end diff --git a/Podfile b/Podfile index fffab64..ebacad4 100644 --- a/Podfile +++ b/Podfile @@ -9,7 +9,7 @@ end abstract_target 'Tests' do project 'examples/apps/Catalog/Catalog.xcodeproj' - pod 'IndefiniteObservable/tests', :path => './' + pod 'IndefiniteObservable/lib', :path => './' target "UnitTests" target "OSXTests" diff --git a/Podfile.lock b/Podfile.lock index f844b81..047d050 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -3,13 +3,11 @@ PODS: - IndefiniteObservable/examples (1.0.0): - IndefiniteObservable/lib - IndefiniteObservable/lib (1.0.0) - - IndefiniteObservable/tests (1.0.0): - - IndefiniteObservable/lib DEPENDENCIES: - CatalogByConvention - IndefiniteObservable/examples (from `./`) - - IndefiniteObservable/tests (from `./`) + - IndefiniteObservable/lib (from `./`) EXTERNAL SOURCES: IndefiniteObservable: @@ -17,8 +15,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: CatalogByConvention: be55c2263132e4f9f59299ac8a528ee8715b3275 - IndefiniteObservable: 3efc90f559fea4010f8be8a9ab4f33ffa6718a44 + IndefiniteObservable: b90809f2fa025e37124c14681192bc441b128625 -PODFILE CHECKSUM: d381ba7e64de99c14e9c80a75f594f468c07327d +PODFILE CHECKSUM: 3e4cdba95901e07a289159f0c5a8b830ecb1a5c8 COCOAPODS: 1.1.1