From 3c1bc7e0dcf18582c1cc4999cccdc3500a4d29ea Mon Sep 17 00:00:00 2001 From: Srdan Rasic Date: Tue, 14 Jun 2016 12:37:52 +0200 Subject: [PATCH] Improve ProtocolProxy --- README.md | 38 ++-- ReactiveKit.podspec | 4 +- ReactiveKit.xcodeproj/project.pbxproj | 4 + ReactiveKit/Info.plist | 2 +- Sources/ProtocolProxy.swift | 255 ++++++++++++++++++-------- Tests/ProtocolProxyTests.swift | 95 ++++++++++ 6 files changed, 301 insertions(+), 97 deletions(-) create mode 100644 Tests/ProtocolProxyTests.swift diff --git a/README.md b/README.md index 65947d5..249741c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![](Assets/logo.png) -[![Platform](https://img.shields.io/cocoapods/p/ReactiveKit.svg?style=flat)](http://cocoadocs.org/docsets/ReactiveKit/2.0.0-beta4/) +[![Platform](https://img.shields.io/cocoapods/p/ReactiveKit.svg?style=flat)](http://cocoadocs.org/docsets/ReactiveKit/2.1.0) [![Build Status](https://travis-ci.org/ReactiveKit/ReactiveKit.svg?branch=master)](https://travis-ci.org/ReactiveKit/ReactiveKit) [![Join Us on Gitter](https://img.shields.io/badge/GITTER-join%20chat-blue.svg)](https://gitter.im/ReactiveKit/General) [![Twitter](https://img.shields.io/badge/twitter-@srdanrasic-red.svg?style=flat)](https://twitter.com/srdanrasic) @@ -447,7 +447,7 @@ When you want to receive events on a specific dispatch queue, just use `context` ReactiveKit provides NSObject extensions that makes it easy to convert delegate pattern into streams. -First make an extension on your type, UITableView in the following example, that provides a reactive delegate proxy: +First make an extension on your type, UITableView in this example, that provides a reactive delegate proxy: ```swift extension UITableView { @@ -462,12 +462,14 @@ You can then convert methods of that protocol into streams: ```swift extension UITableView { var selectedRow: Stream { - return rDelegate.streamFor(#selector(UITableViewDelegate.tableView(_:didSelectRowAtIndexPath:))) { (_: UITableView, indexPath: NSIndexPath) in indexPath.row } + return rDelegate.stream(#selector(UITableViewDelegate.tableView(_:didSelectRowAtIndexPath:))) { (s: PushStream, _: UITableView, indexPath: NSIndexPath) in + s.next(indexPath.row) + } } } ``` -Method `streamFor` takes two parameters: a selector to convert to a stream and a mapping closure that maps selector method arguments into a stream. +Method `stream` takes two parameters: a selector of a method to convert to a stream and a closure that is invoked on each method call. You should use that closure to push events into the given `PushStream`. Method returns that `PushStream` mapped to a `Stream`. Now you can do: @@ -479,23 +481,25 @@ tableView.selectedRow.observeNext { row in Protocol proxy takes up delegate slot of the object so if you also need to implement delegate methods manually, don't set `tableView.delegate = x`, rather set `tableView.rDelegate.forwardTo = x`. -Protocol methods that return values are usually used to query data. Such methods can be set up to be fed from a property type. For example: +Note that it is you who must ensure that the parameters in the `dispatch` closure are correctly typed. For example, if there is no specific type you can use Void stream. -```swift -let numberOfItems = Property(12) -tableView.rDataSource.feed( - numberOfItems, - to: #selector(UITableViewDataSource.tableView(_:numberOfRowsInSection:)), - map: { (value: Int, _: UITableView, _: Int) -> Int in value } -) +```swift +let _ = rDelegate.stream(#selector(UITextViewDelegate.textViewDidBeginEditing(_:))) { (s: PushStream, _: UITextView) in + s.next() +} ``` -Method `feed` takes three parameters: a property to feed from, a selector, and a mapping closure that maps from the property value and selector method arguments to the selector method return value. +If a protocol method has return value then you must handle it by returning it in the `dispatch` closure. -You should not set more that one feed property per selector. +```swift +let _ = rDelegate.stream(#selector(UITextViewDelegate.textView(_:shouldChangeTextInRange:replacementText:))) { (s: PushStream, _: UITextView, range: NSRange, text: NSString) -> Bool in + s.next(text as String) + return true +} +``` -Note that in the mapping closures of both `streamFor` and `feed` methods you must be explicit about argument and return types. Also, **you must use ObjC types as this is ObjC API**. For example, use `NSString` instead of `String`. +Note that **you must use ObjC types as this is ObjC API** in the place of proxied method argument parameters. For example, use `NSString` instead of `String`. ## Requirements @@ -523,14 +527,14 @@ Note that in the mapping closures of both `streamFor` and `feed` methods you mus ### CocoaPods ``` -pod 'ReactiveKit', '~> 2.0' +pod 'ReactiveKit', '~> 2.1' pod 'ReactiveUIKit', '~> 2.0' ``` ### Carthage ``` -github "ReactiveKit/ReactiveKit" ~> 2.0 +github "ReactiveKit/ReactiveKit" ~> 2.1 github "ReactiveKit/ReactiveUIKit" ~> 2.0 ``` diff --git a/ReactiveKit.podspec b/ReactiveKit.podspec index 2631e2b..ae197d7 100644 --- a/ReactiveKit.podspec +++ b/ReactiveKit.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |s| s.name = "ReactiveKit" - s.version = "2.0.4" + s.version = "2.1.0" s.summary = "A Swift Reactive Programming Framework" s.description = "ReactiveKit is a collection of Swift frameworks for reactive and functional reactive programming." s.homepage = "https://github.com/ReactiveKit/ReactiveKit" s.license = 'MIT' s.author = { "Srdan Rasic" => "srdan.rasic@gmail.com" } - s.source = { :git => "https://github.com/ReactiveKit/ReactiveKit.git", :tag => "v2.0.4" } + s.source = { :git => "https://github.com/ReactiveKit/ReactiveKit.git", :tag => "v2.1.0" } s.ios.deployment_target = '8.0' s.osx.deployment_target = '10.9' diff --git a/ReactiveKit.xcodeproj/project.pbxproj b/ReactiveKit.xcodeproj/project.pbxproj index a5a4080..0a10c35 100644 --- a/ReactiveKit.xcodeproj/project.pbxproj +++ b/ReactiveKit.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 16C33B851BEFBAC900A0DBE0 /* ReactiveKit.h in Headers */ = {isa = PBXBuildFile; fileRef = ECBCCDD31BEB6B9A00723476 /* ReactiveKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; EC2515771CBD4E0700175926 /* StreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2515751CBD4E0000175926 /* StreamTests.swift */; }; EC25158F1CBFB6A400175926 /* ArrayDiffTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A9A171CABDA5F0042A6AD /* ArrayDiffTests.swift */; }; + EC5367361D05C33B000D6045 /* ProtocolProxyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5367351D05C33B000D6045 /* ProtocolProxyTests.swift */; }; EC65A0B31CBFEE4D00B41FA7 /* OperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC65A0B21CBFEE4D00B41FA7 /* OperationTests.swift */; }; EC8A99E71CABD9B50042A6AD /* CollectionProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DB1CABD9B50042A6AD /* CollectionProperty.swift */; }; EC8A99E81CABD9B50042A6AD /* CollectionProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DB1CABD9B50042A6AD /* CollectionProperty.swift */; }; @@ -106,6 +107,7 @@ 16C33B161BEFB9CB00A0DBE0 /* ReactiveKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 16C33B241BEFBA0100A0DBE0 /* ReactiveKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EC2515751CBD4E0000175926 /* StreamTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StreamTests.swift; path = Tests/StreamTests.swift; sourceTree = SOURCE_ROOT; }; + EC5367351D05C33B000D6045 /* ProtocolProxyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProtocolProxyTests.swift; path = Tests/ProtocolProxyTests.swift; sourceTree = SOURCE_ROOT; }; EC65A0B21CBFEE4D00B41FA7 /* OperationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OperationTests.swift; path = Tests/OperationTests.swift; sourceTree = SOURCE_ROOT; }; EC8A99DB1CABD9B50042A6AD /* CollectionProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CollectionProperty.swift; path = Sources/CollectionProperty.swift; sourceTree = SOURCE_ROOT; }; EC8A99DC1CABD9B50042A6AD /* Disposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Disposable.swift; path = Sources/Disposable.swift; sourceTree = SOURCE_ROOT; }; @@ -226,6 +228,7 @@ EC2515751CBD4E0000175926 /* StreamTests.swift */, EC65A0B21CBFEE4D00B41FA7 /* OperationTests.swift */, 168103431D046388007D1DD4 /* CollectionPropertyTests.swift */, + EC5367351D05C33B000D6045 /* ProtocolProxyTests.swift */, 168103441D046388007D1DD4 /* Helpers.swift */, ECBCCDE11BEB6B9B00723476 /* Info.plist */, ); @@ -544,6 +547,7 @@ buildActionMask = 2147483647; files = ( 168103451D046388007D1DD4 /* CollectionPropertyTests.swift in Sources */, + EC5367361D05C33B000D6045 /* ProtocolProxyTests.swift in Sources */, EC2515771CBD4E0700175926 /* StreamTests.swift in Sources */, EC25158F1CBFB6A400175926 /* ArrayDiffTests.swift in Sources */, 168103461D046388007D1DD4 /* Helpers.swift in Sources */, diff --git a/ReactiveKit/Info.plist b/ReactiveKit/Info.plist index 241ef86..dca568e 100644 --- a/ReactiveKit/Info.plist +++ b/ReactiveKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.0.4 + 2.1.0 CFBundleSignature ???? CFBundleVersion diff --git a/Sources/ProtocolProxy.swift b/Sources/ProtocolProxy.swift index aadb84e..c4d43ca 100644 --- a/Sources/ProtocolProxy.swift +++ b/Sources/ProtocolProxy.swift @@ -41,6 +41,14 @@ public class ProtocolProxy: RKProtocolProxyBase { invoker(argumentExtractor, setReturnValue) } + private func registerInvoker(selector: Selector, block: () -> R) { + invokers[selector] = { extractor, setReturnValue in + var r = block() + if let setReturnValue = setReturnValue { setReturnValue(&r) } + } + registerDelegate() + } + private func registerInvoker(selector: Selector, block: T -> R) { invokers[selector] = { extractor, setReturnValue in let a1 = UnsafeMutablePointer.alloc(1) @@ -111,133 +119,126 @@ public class ProtocolProxy: RKProtocolProxyBase { registerDelegate() } - /// Maps the given protocol method to a stream. + /// Maps the given protocol method to a stream. Whenever the method on the target object is called, + /// the framework will call the provided `dispatch` closure that you can use to generate a stream. /// - /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! - public func streamFor(selector: Selector, map: T -> Z) -> Stream { + /// - parameter selector: Selector of the method to map. + /// - parameter dispatch: A closure that dispatches calls to the given PushStream. + public func stream(selector: Selector, dispatch: PushStream -> R) -> Stream { if let stream = handlers[selector] { - return (stream as! PushStream).toStream() + return (stream as! PushStream).toStream() } else { - let pushStream = PushStream() + let pushStream = PushStream() handlers[selector] = pushStream - registerInvoker(selector) { a1 in - pushStream.next(map(a1)) + registerInvoker(selector) { () -> R in + return dispatch(pushStream) } return pushStream.toStream() } } - /// Provides a feed for specified protocol method. + /// Maps the given protocol method to a stream. Whenever the method on the target object is called, + /// the framework will call the provided `dispatch` closure that you can use to generate a stream. /// - /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! - public func feed(property: Property, to selector: Selector, map: (A, T) -> R) { - handlers[selector] = property - registerInvoker(selector) { (a1: T) -> R in - return map(property.value, a1) - } - } - - /// Maps the given protocol method to a stream. + /// - parameter selector: Selector of the method to map. + /// - parameter dispatch: A closure that dispatches calls to the given PushStream. /// - /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! - public func streamFor(selector: Selector, map: (T, U) -> Z) -> Stream { + /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String + /// in place of generic parameter A and R! + public func stream(selector: Selector, dispatch: (PushStream, A) -> R) -> Stream { if let stream = handlers[selector] { - return (stream as! PushStream).toStream() + return (stream as! PushStream).toStream() } else { - let pushStream = PushStream() + let pushStream = PushStream() handlers[selector] = pushStream - registerInvoker(selector) { a1, a2 in - pushStream.next(map(a1, a2)) + registerInvoker(selector) { (a: A) -> R in + return dispatch(pushStream, a) } return pushStream.toStream() } } - /// Provides a feed for specified protocol method. + /// Maps the given protocol method to a stream. Whenever the method on the target object is called, + /// the framework will call the provided `dispatch` closure that you can use to generate a stream. /// - /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! - public func feed(property: Property, to selector: Selector, map: (A, T, U) -> R) { - handlers[selector] = property - registerInvoker(selector) { (a1: T, a2: U) -> R in - return map(property.value, a1, a2) - } - } - - /// Maps the given protocol method to a stream. + /// - parameter selector: Selector of the method to map. + /// - parameter dispatch: A closure that dispatches calls to the given PushStream. /// - /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! - public func streamFor(selector: Selector, map: (T, U, V) -> Z) -> Stream { + /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String + /// in place of generic parameters A, B and R! + public func stream(selector: Selector, dispatch: (PushStream, A, B) -> R) -> Stream { if let stream = handlers[selector] { - return (stream as! PushStream).toStream() + return (stream as! PushStream).toStream() } else { - let pushStream = PushStream() + let pushStream = PushStream() handlers[selector] = pushStream - registerInvoker(selector) { a1, a2, a3 in - pushStream.next(map(a1, a2, a3)) + registerInvoker(selector) { (a: A, b: B) -> R in + return dispatch(pushStream, a, b) } return pushStream.toStream() } } - /// Provides a feed for specified protocol method. + /// Maps the given protocol method to a stream. Whenever the method on the target object is called, + /// the framework will call the provided `dispatch` closure that you can use to generate a stream. /// - /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! - public func feed(property: Property, to selector: Selector, map: (A, T, U, V) -> R) { - handlers[selector] = property - registerInvoker(selector) { (a1: T, a2: U, a3: V) -> R in - return map(property.value, a1, a2, a3) - } - } - - /// Maps the given protocol method to a stream. + /// - parameter selector: Selector of the method to map. + /// - parameter dispatch: A closure that dispatches calls to the given PushStream. /// - /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! - public func streamFor(selector: Selector, map: (T, U, V, W) -> Z) -> Stream { + /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String + /// in place of generic parameters A, B, C and R! + public func stream(selector: Selector, dispatch: (PushStream, A, B, C) -> R) -> Stream { if let stream = handlers[selector] { - return (stream as! PushStream).toStream() + return (stream as! PushStream).toStream() } else { - let pushStream = PushStream() + let pushStream = PushStream() handlers[selector] = pushStream - registerInvoker(selector) { a1, a2, a3, a4 in - pushStream.next(map(a1, a2, a3, a4)) + registerInvoker(selector) { (a: A, b: B, c: C) -> R in + return dispatch(pushStream, a, b, c) } return pushStream.toStream() } } - /// Provides a feed for specified protocol method. + /// Maps the given protocol method to a stream. Whenever the method on the target object is called, + /// the framework will call the provided `dispatch` closure that you can use to generate a stream. /// - /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! - public func feed(property: Property, to selector: Selector, map: (A, T, U, V, W) -> R) { - handlers[selector] = property - registerInvoker(selector) { (a1: T, a2: U, a3: V, a4: W) -> R in - return map(property.value, a1, a2, a3, a4) - } - } - - /// Maps the given protocol method to a stream. + /// - parameter selector: Selector of the method to map. + /// - parameter dispatch: A closure that dispatches calls to the given PushStream. /// - /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! - public func streamFor(selector: Selector, map: (T, U, V, W, X) -> Z) -> Stream { + /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String + /// in place of generic parameters A, B, C, D and R! + public func stream(selector: Selector, dispatch: (PushStream, A, B, C, D) -> R) -> Stream { if let stream = handlers[selector] { - return (stream as! PushStream).toStream() + return (stream as! PushStream).toStream() } else { - let pushStream = PushStream() + let pushStream = PushStream() handlers[selector] = pushStream - registerInvoker(selector) { a1, a2, a3, a4, a5 in - pushStream.next(map(a1, a2, a3, a4, a5)) + registerInvoker(selector) { (a: A, b: B, c: C, d: D) -> R in + return dispatch(pushStream, a, b, c, d) } return pushStream.toStream() } } - /// Provides a feed for specified protocol method. + /// Maps the given protocol method to a stream. Whenever the method on the target object is called, + /// the framework will call the provided `dispatch` closure that you can use to generate a stream. /// - /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! - public func feed(property: Property, to selector: Selector, map: (A, T, U, V, W, X) -> R) { - handlers[selector] = property - registerInvoker(selector) { (a1: T, a2: U, a3: V, a4: W, a5: X) -> R in - return map(property.value, a1, a2, a3, a4, a5) + /// - parameter selector: Selector of the method to map. + /// - parameter dispatch: A closure that dispatches calls to the given PushStream. + /// + /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String + /// in place of generic parameters A, B, C, D, E and R! + public func stream(selector: Selector, dispatch: (PushStream, A, B, C, D, E) -> R) -> Stream { + if let stream = handlers[selector] { + return (stream as! PushStream).toStream() + } else { + let pushStream = PushStream() + handlers[selector] = pushStream + registerInvoker(selector) { (a: A, b: B, c: C, d: D, e: E) -> R in + return dispatch(pushStream, a, b, c, d, e) + } + return pushStream.toStream() } } @@ -307,3 +308,103 @@ extension NSObject { } } } + +// MARK: - Deprecated + +extension ProtocolProxy { + + /// Maps the given protocol method to a stream. + /// + /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! + @available(*, deprecated=2.1, message="Please use method stream(selector:dispatch:)") + public func streamFor(selector: Selector, map: T -> Z) -> Stream { + return stream(selector) { (pushStream: PushStream, a1: T) in + pushStream.next(map(a1)) + } + } + + /// Provides a feed for specified protocol method. + /// + /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! + public func feed(property: Property, to selector: Selector, map: (A, T) -> R) { + let _ = stream(selector) { (_: PushStream, a1: T) -> R in + return map(property.value, a1) + } + } + + /// Maps the given protocol method to a stream. + /// + /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! + @available(*, deprecated=2.1, message="Please use method stream(selector:dispatch:)") + public func streamFor(selector: Selector, map: (T, U) -> Z) -> Stream { + return stream(selector) { (pushStream: PushStream, a1: T, a2: U) in + pushStream.next(map(a1, a2)) + } + } + + /// Provides a feed for specified protocol method. + /// + /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! + public func feed(property: Property, to selector: Selector, map: (A, T, U) -> R) { + let _ = stream(selector) { (_: PushStream, a1: T, a2: U) -> R in + return map(property.value, a1, a2) + } + } + + /// Maps the given protocol method to a stream. + /// + /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! + @available(*, deprecated=2.1, message="Please use method stream(selector:dispatch:)") + public func streamFor(selector: Selector, map: (T, U, V) -> Z) -> Stream { + return stream(selector) { (pushStream: PushStream, a1: T, a2: U, a3: V) in + pushStream.next(map(a1, a2, a3)) + } + } + + /// Provides a feed for specified protocol method. + /// + /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! + public func feed(property: Property, to selector: Selector, map: (A, T, U, V) -> R) { + let _ = stream(selector) { (_: PushStream, a1: T, a2: U, a3: V) -> R in + return map(property.value, a1, a2, a3) + } + } + + /// Maps the given protocol method to a stream. + /// + /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! + @available(*, deprecated=2.1, message="Please use method stream(selector:dispatch:)") + public func streamFor(selector: Selector, map: (T, U, V, W) -> Z) -> Stream { + return stream(selector) { (pushStream: PushStream, a1: T, a2: U, a3: V, a4: W) in + pushStream.next(map(a1, a2, a3, a4)) + } + } + + /// Provides a feed for specified protocol method. + /// + /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! + public func feed(property: Property, to selector: Selector, map: (A, T, U, V, W) -> R) { + let _ = stream(selector) { (_: PushStream, a1: T, a2: U, a3: V, a4: W) -> R in + return map(property.value, a1, a2, a3, a4) + } + } + + /// Maps the given protocol method to a stream. + /// + /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! + @available(*, deprecated=2.1, message="Please use method stream(selector:dispatch:)") + public func streamFor(selector: Selector, map: (T, U, V, W, X) -> Z) -> Stream { + return stream(selector) { (pushStream: PushStream, a1: T, a2: U, a3: V, a4: W, a5: X) in + pushStream.next(map(a1, a2, a3, a4, a5)) + } + } + + /// Provides a feed for specified protocol method. + /// + /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! + public func feed(property: Property, to selector: Selector, map: (A, T, U, V, W, X) -> R) { + let _ = stream(selector) { (_: PushStream, a1: T, a2: U, a3: V, a4: W, a5: X) -> R in + return map(property.value, a1, a2, a3, a4, a5) + } + } +} diff --git a/Tests/ProtocolProxyTests.swift b/Tests/ProtocolProxyTests.swift new file mode 100644 index 0000000..ce08937 --- /dev/null +++ b/Tests/ProtocolProxyTests.swift @@ -0,0 +1,95 @@ +// +// ProcolProxyTests.swift +// ReactiveKit +// +// Created by Srdan Rasic on 06/06/16. +// Copyright © 2016 Srdan Rasic. All rights reserved. +// + +import XCTest +@testable import ReactiveKit + +@objc protocol TestDelegate { + func methodA() + func methodB(object: TestObject) + func methodC(object: TestObject, value: Int) + func methodD(object: TestObject, value: Int) -> NSString +} + +class TestObject: NSObject { + weak var delegate: TestDelegate! = nil + + override init() { + super.init() + } + + func callMethodA() { + delegate.methodA() + } + + func callMethodB() { + delegate.methodB(self) + } + + func callMethodC(value: Int) { + delegate.methodC(self, value: value) + } + + func callMethodD(value: Int) -> NSString { + return delegate.methodD(self, value: value) + } +} + +class ProtocolProxyTests: XCTestCase { + + var object: TestObject! = nil + + var delegate: ProtocolProxy { + return object.protocolProxyFor(TestDelegate.self, setter: NSSelectorFromString("setDelegate:")) + } + + override func setUp() { + object = TestObject() + } + + func testCallbackA() { + let stream = delegate.stream(#selector(TestDelegate.methodA)) { (stream: PushStream) in + stream.next(0) + } + + stream.expectNext([0, 0]) + object.callMethodA() + object.callMethodA() + } + + func testCallbackB() { + let stream = delegate.stream(#selector(TestDelegate.methodB(_:))) { (stream: PushStream, _: TestObject) in + stream.next(0) + } + + stream.expectNext([0, 0]) + object.callMethodB() + object.callMethodB() + } + + func testCallbackC() { + let stream = delegate.stream(#selector(TestDelegate.methodC(_:value:))) { (stream: PushStream, _: TestObject, value: Int) in + stream.next(value) + } + + stream.expectNext([10, 20]) + object.callMethodC(10) + object.callMethodC(20) + } + + func testCallbackD() { + let stream = delegate.stream(#selector(TestDelegate.methodD(_:value:))) { (stream: PushStream, _: TestObject, value: Int) -> NSString in + stream.next(value) + return "\(value)" + } + + stream.expectNext([10, 20]) + XCTAssertEqual(object.callMethodD(10), "10") + XCTAssertEqual(object.callMethodD(20), "20") + } +}