A testable RxSwift wrapper around MultipeerConnectivity

Overview

A testable RxSwift wrapper around MultipeerConnectivity

RxMultipeer is a RxSwift wrapper for MultipeerConnectivity.

Using the adapter pattern, we can test multipeer code with heavy mocking. In effect, we are trying to isolate all the untestable bits of MultipeerConnectivity into one library.

This library also gives you the flexibility to swap out the underlying mechanics of p2p with some other protocol such as websockets. At the moment it only comes with support for Apple's MultipeerConnectivity, however you can easily write your own adapters for different protocols.

Installation

Carthage

Add this to your Cartfile

github "RxSwiftCommunity/RxMultipeer" ~> 3.0

Example code

For a working example check out the RxMultipeer Example folder.

Advertise and accept nearby peers

import RxSwift
import RxCocoa
import RxMultipeer

let disposeBag: DisposeBag
let acceptButton: UIButton
let client: CurrentClient<MCPeerID>

client.startAdvertising()
let connectionRequests = client.incomingConnections().shareReplay(1)

acceptButton.rx_tap
  .withLatestFrom(connectionRequests)
  .subscribe(onNext: { (peer, context, respond) in respond(true) })
  .addDisposableTo(disposeBag)

client.incomingCertificateVerifications()
    .subscribe(onNext: { (peer, certificateChain, respond) in
      // Validate the certificateChain
      respond(true)
    })
    .addDisposableTo(disposeBag)

Browse for and connect to peers

import RxSwift
import RxMultipeer

let disposeBag: DisposeBag
let client: CurrentClient<MCPeerID>

client.startBrowsing()

let nearbyPeers = client.nearbyPeers().shareReplay(1)

// Attempt to connect to all peers
nearbyPeers
  .map { (peers: [Client<MCPeerID>]) in
    peers.map { client.connect(toPeer: $0, context: ["Hello": "there"], timeout: 12) }.zip()
  }
  .subscribe()
  .addDisposableTo(disposeBag)

Sending and receiving strings

Sending them:

import RxSwift
import RxCocoa
import RxMultipeer

let disposeBag: DisposeBag
let client: CurrentClient<MCPeerID>
let peer: Observable<Client<MCPeerID>>
let sendButton: UIButton

sendButton.rx_tap
  .withLatestFrom(peer)
  .map { client.send(toPeer: peer, string: "Hello!") }
  .switchLatest()
  .subscribe(onNext: { _ in print("Message sent") })
  .addDisposableTo(disposeBag)

And receiving them:

import RxSwift
import RxMultipeer

let disposeBag: DisposeBag
let client: CurrentClient<MCPeerID>

client.receive()
.subscribe(onNext: { (peer: Client<MCPeerID>, message: String) in
  print("got message \(message), from peer \(peer)")
})
.addDisposableTo(disposeBag)

Establishing a data stream

RxSwift makes sending streaming data to a persistent connection with another peer very intuitive.

The sender:

import RxSwift
import RxMultipeer

let disposeBag: DisposeBag
let client: CurrentClient<MCPeerID>
let peer: Observable<Client<MCPeerID>>
let queuedMessages: Observable<[UInt8]>

let pipe = peer.map { client.send(toPeer: peer, streamName: "data.stream") }
pipe.withLatestFrom(queuedMessages) { $0 }
  .subscribe(onNext: { (sender, message) in sender(message) })
  .addDisposableTo(disposeBag)

The receiver:

import RxSwift
import RxMultipeer

let disposeBag: DisposeBag
let client: CurrentClient<MCPeerID>
let peer: Observable<Client<MCPeerID>>

let incomingData = client.receive(fromPeer: peer, streamName: "data.stream").shareReplay(1)
incomingData
  .subscribe(onNext: { (data) in print(data) })
  .addDisposableTo(disposeBag)

Usage

Imports

import RxSwift
import RxMultipeer

Make a new build configuration for testing

Your project comes with Debug and Release build configurations by default, we need to make a new one called Testing. Please check here for step-by-step instructions.

Setting up the client

// See the link above,
// You'll need to define a new build configuration and give it the `TESTING` flag
let name = UIDevice.currentDevice().name
#if TESTING
typealias I = MockIden
let client = CurrentClient(session: MockSession(name: name))
#else
typealias I = MCPeerID
let client = CurrentClient(session: MultipeerConnectivitySession(
                 displayName: name,
                 serviceType: "multipeerex",
                 idenCacheKey: "com.rxmultipeer.example.mcpeerid",
                 encryptionPreference: .None))
#endif

Supported transfer resource types

String

func send(toPeer: Client, string: String, mode: MCSessionSendDataMode = .Reliable) -> Observable<()>
func receive() -> Observable<(Client, String)>

Data

func send(toPeer: Client, data: Data, mode: MCSessionSendDataMode = .Reliable) -> Observable<()>
func receive() -> Observable<(Client, Data)>

JSON

func send(toPeer: Client, json: [String: Any], mode: MCSessionSendDataMode = .Reliable) -> Observable<()>
func receive() -> Observable<(Client, [String: Any])>

NSURL

func send(toPeer: Client, name: String, url: NSURL, mode: MCSessionSendDataMode = .Reliable) -> Observable<NSProgress>
func receive() -> Observable<(Client, String, ResourceState)>

NSStream

func send(toPeer: Client, streamName: String, runLoop: NSRunLoop = NSRunLoop.mainRunLoop()) -> Observable<([UInt8]) -> Void>
func receive(fromPeer: Client, streamName: String, runLoop: NSRunLoop = NSRunLoop.mainRunLoop(), maxLength: Int = 512) -> Observable<[UInt8]>

Testing

When testing, use preprocesser macros to ensure that your code uses a MockSession instance instead of MultipeerConnectivitySession one. In order to achieve this you need to use preprocessor flags and swap out anywhere that references Client<T> (because T will be different depending on whether you are testing or not.) First you will need to set up a new build configuration, and then you can use preprocessor macros like so:

let name = UIDevice.currentDevice().name
#if TESTING
typealias I = MockIden
let client = CurrentClient(session: MockSession(name: name))
#else
typealias I = MCPeerID
let client = CurrentClient(session: MultipeerConnectivitySession(
                 displayName: name,
                 serviceType: "multipeerex",
                 idenCacheKey: "com.rxmultipeer.example.mcpeerid",
                 encryptionPreference: .None))
#endif

Don't worry, you should only really need preprocessor macros in one centralized place, the type of your client can be inferred by the compiler thereafter.

Mocking other nearby peers in the test environment then becomes as simple as creating other CurrentClient(session: MockSession(name: "other")). For example, if your app is running in a testing environment the following code will mock a nearby client:

let disposeBag: DisposeBag
let otherclient = CurrentClient(session: MockSession(name: "mockedother"))

// Accept all connections
otherclient.startAdvertising()

otherclient.incomingConnections()
  .subscribeNext { (client, context, respond) in respond(true) }
  .addDisposableTo(disposeBag)

// Starting from version 3.0.0 the following handler needs to be implemented
// for this library to work:
otherclient.incomingCertificateVerifications()
  .subscribeNext { (client, certificateChain, respond) in respond(true) }
  .addDisposableTo(disposeBag)

// Respond to all messages with 'Roger'
otherclient.receive()
  .map { (client: Client<MockIden>, string: String) in otherclient.send(toPeer: client, "Roger") }
  .concat()
  .subscribeNext { _ in print("Response sent") }
  .addDisposableTo(disposeBag)

Breaking changes

Version 3.0.0

Starting from version 3.0.0, incomingCertificateVerifications() -> Observable<(MCPeerID, [Any]?, (Bool) -> Void)> was introduced. This needs to be implemented in order for mock and real connections to work. Behaviour prior to this update can be reproduced by simply accepting all certificates:

let client: CurrentClient<MCPeerID
client.incomingCertificateVerifications()
    .subscribe(onNext: { (peer, certificateChain, respond) in
      // Validate the certificateChain
      respond(true)
    })
    .addDisposableTo(disposeBag)

Contributing

  • Indent with 2 spaces
  • Strip trailing whitespace
  • Write tests
  • Pull-request from feature branches.
You might also like...
🤖 RxSwift + State Machine, inspired by Redux and Elm.
🤖 RxSwift + State Machine, inspired by Redux and Elm.

RxAutomaton RxSwift port of ReactiveAutomaton (State Machine). Terminology Whenever the word "signal" or "(signal) producer" appears (derived from Rea

STDevRxExt contains some extension functions for RxSwift and RxCocoa which makes our live easy.

STDevRxExt Example To run the Example.playground, clone the repo, and run pod install from the Example directory first. Requirements iOS 9.0+ tvOS 9.0

RxAlamoRecord combines the power of the AlamoRecord and RxSwift libraries to create a networking layer that makes interacting with API's easier than ever reactively.
RxAlamoRecord combines the power of the AlamoRecord and RxSwift libraries to create a networking layer that makes interacting with API's easier than ever reactively.

Written in Swift 5 RxAlamoRecord combines the power of the AlamoRecord and RxSwift libraries to create a networking layer that makes interacting with

Support Combine Assign subscriber in RxSwift.

RxAssign Support Combine Assign subscriber in RxSwift. Assign uses a KeyPath which is really nice and useful. ** RxAssign extends Driver and Signal in

This is the demo of MVVM-C structure with dependency injection using RxSwift.
This is the demo of MVVM-C structure with dependency injection using RxSwift.

MVVMC-Demo Demo defination This is the demo project, I have integrated two APIs for MovieDB APIS (https://www.themoviedb.org/). One for the listing of

ReactiveCocoa wrapper for CLLocationManager.
ReactiveCocoa wrapper for CLLocationManager.

ReactiveSwift wrapper to observe location Our wrapper automatically asks for permission. It determines what kind of permission your app requires by ch

A testable RxSwift wrapper around MultipeerConnectivity

A testable RxSwift wrapper around MultipeerConnectivity RxMultipeer is a RxSwift wrapper for MultipeerConnectivity. Using the adapter pattern, we can

📱📲 A wrapper for the MultipeerConnectivity framework for automatic offline data transmission between devices
📱📲 A wrapper for the MultipeerConnectivity framework for automatic offline data transmission between devices

A wrapper for Apple's MultipeerConnectivity framework for offline data transmission between Apple devices. This framework makes it easy to automatical

Functional wrapper for Apple's MultipeerConnectivity framework.

A functional wrapper for the MultipeerConnectivity framework. PeerConnectivity is meant to have a lightweight easy to use syntax, be extensible and fl

📱📲 A wrapper for the MultipeerConnectivity framework for automatic offline data transmission between devices
📱📲 A wrapper for the MultipeerConnectivity framework for automatic offline data transmission between devices

A wrapper for Apple's MultipeerConnectivity framework for offline data transmission between Apple devices. This framework makes it easy to automatical

RxSwift wrapper around the elegant HTTP networking in Swift Alamofire

RxAlamofire RxAlamofire is a RxSwift wrapper around the elegant HTTP networking in Swift Alamofire. Getting Started Wrapping RxSwift around Alamofire

A wrapper around UICollectionViewController enabling a declarative API around it's delegate methods using protocols.

Thunder Collection Thunder Collection is a useful framework which enables quick and easy creation of collection views in iOS using a declarative appro

A demo app to showcase testable, modern iOS development with SwiftUI and Combine on MVVM-C architecture.

Coinz_App_iOS A demo app to showcase testable, modern iOS development with SwiftUI and Combine on MVVM-C architecture. Tech Stack: Swift, SwiftUI, Com

Testable Combine Publishers - An easy, declarative way to unit test Combine Publishers in Swift
Testable Combine Publishers - An easy, declarative way to unit test Combine Publishers in Swift

Testable Combine Publishers An easy, declarative way to unit test Combine Publishers in Swift About Combine Publishers are notoriously verbose to unit

Realm RxSwift - This application was written in order to use Realm, RxSwift frameworks in real example

Realm_RxSwift This simple app was written to introduce basic operations of some

a playground app using `MultipeerConnectivity` to transfor data wirelessly between iOS / iPadOS

README An example app using MultipeerConnectivity to transfor data wirelessly between iOS / iPadOS. Both using Data and Stream to transfer data are su

A peer to peer framework for OS X, iOS and watchOS 2 that presents a similar interface to the MultipeerConnectivity framework

This repository is a peer to peer framework for OS X, iOS and watchOS 2 that presents a similar interface to the MultipeerConnectivity framework (which is iOS only) that lets you connect any 2 devices from any platform. This framework works with peer to peer networks like bluetooth and ad hoc wifi networks when available it also falls back onto using a wifi router when necessary. It is built on top of CFNetwork and NSNetService. It uses the newest Swift 2's features like error handling and protocol extensions.

RxSwift reactive wrapper for view gestures
RxSwift reactive wrapper for view gestures

RxGesture Usage To run the example project, clone the repo, in the Example folder open RxGesture.xcworkspace. You might need to run pod install from t

DataKernel is a minimalistic wrapper around CoreData stack to ease persistence operations.

DataKernel What is DataKernel? DataKernel is a minimalistic wrapper around CoreData stack to ease persistence operations. It is heavily inspired by Su

Comments
  • Fix tv os build

    Fix tv os build

    This PR does the following:

    • Update XCode Project Settings
    • Fix info-plist path in tvOS target as the current is invalid and it fails the tvOS target build
    opened by alickbass 2
  • Stop Client.connected() emitting unnecessarily

    Stop Client.connected() emitting unnecessarily

    I found that I was seeing values emitted on the Client.connected() observable when a peer was connecting but not yet connected.

    A connecting peer does not change the connectedPeers property of the MCSession, but it does cause a call to the session(_:peer:didChange:) method on the delegate. This means that the delegate proxy will set the value of _connected even when there is no change to the connectedPeers value it passes.

    This change simply checks if the state of the peer is .connecting. If it is not (ie. it is connected or disconnected) then we update the value of _connected (and thus connected()).

    opened by mootpointer 1
  • Fix carthage build fail

    Fix carthage build fail

    Issue

    When use this library with Carthage. Building fail with below message because of config.

    This PR fix it.

    ➜  RxMultipeerTrial carthage update --platform iOS
    *** Fetching RxMultipeer
    *** Fetching RxSwift
    *** Checking out RxSwift at "2.6.0"
    *** Checking out RxMultipeer at "5d3ffcbfbadb31f8a824ea7d150eca7eb5eef51f"
    *** xcodebuild output can be found in /var/folders/9l/3_q0bnkx40jb9ywzrsprr0z56cd_w1/T/carthage-xcodebuild.hNpZgx.log
    *** Building scheme "RxBlocking-iOS" in Rx.xcworkspace
    *** Building scheme "RxTests-iOS" in Rx.xcworkspace
    *** Building scheme "RxSwift-iOS" in Rx.xcworkspace
    *** Building scheme "RxCocoa-iOS" in Rx.xcworkspace
    *** Building scheme "RxMultipeer" in RxMultipeer.xcodeproj
    ** BUILD FAILED **
    
    
    The following build commands failed:
        CompileSwift normal arm64 /Users/majima/develop/iOS/trial/RxMultipeerTrial/Carthage/Checkouts/RxMultipeer/RxMultipeerTests/IntegrationSpec.swift
        CompileSwiftSources normal arm64 com.apple.xcode.tools.swift.compiler
    (2 failures)
    /Users/majima/develop/iOS/trial/RxMultipeerTrial/Carthage/Checkouts/RxMultipeer/RxMultipeerTests/IntegrationSpec.swift:2:8: error: no such module 'Quick'
    A shell task (/usr/bin/xcrun xcodebuild -project /Users/majima/develop/iOS/trial/RxMultipeerTrial/Carthage/Checkouts/RxMultipeer/RxMultipeer.xcodeproj -scheme RxMultipeer -configuration Release -sdk iphoneos ONLY_ACTIVE_ARCH=NO BITCODE_GENERATION_MODE=bitcode CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= CARTHAGE=YES clean build) failed with exit code 65:
    ** BUILD FAILED **
    
    
    The following build commands failed:
        CompileSwift normal arm64 /Users/majima/develop/iOS/trial/RxMultipeerTrial/Carthage/Checkouts/RxMultipeer/RxMultipeerTests/IntegrationSpec.swift
        CompileSwiftSources normal arm64 com.apple.xcode.tools.swift.compiler
    (2 failures)
    
    
    opened by colorbox 1
  • Please fix at url: URL?,

    Please fix at url: URL?,

    Xcode 9, build for iOS 9.2+, class MultipeerConnectivitySession

    I made fix for self:

    public func session(_ session: MCSession,
                          didFinishReceivingResourceWithName name: String,
                          fromPeer peerID: MCPeerID,
                          at url: URL?,
                          withError err: Error?) {
        if let e = err {
          _receivedResource.on(.next(peerID, name, ResourceState.errored(e)))
          return
        }
        
        if let url = url {
            _receivedResource.on(.next(peerID, name,  ResourceState.finished(url)))
        }
      }
    
    opened by Alexandeer 2
Releases(1.1.2)
  • 1.1.2(May 31, 2016)

    Features

    • dbd64fc Exposes isAdvertising and isBrowsing on the MockSession

    Fixes

    • 7a229d3 Improves stability by implementing didReceiveCertificate:fromPeer:certificateHandler as per http://stackoverflow.com/a/19696074/1740868
    • 9ffdb1e Fix app submission issues by removing nested frameworks.
    Source code(tar.gz)
    Source code(zip)
  • 1.1.0(May 1, 2016)

    Features

    • f0a25ef Add ability to recycle MCPeerID's, using this feature should solve some fragility issues.

    Breaking Changes

    • 7252bbf The ClientType protocol has been removed as it was redundant. This should never really have been used in the first place, so it is only technically 'breaking'.

    Other

    • 4927c87 A lot of documentation has been added to the code, so autocompletion should be more helpful now.
    Source code(tar.gz)
    Source code(zip)
Owner
RxSwift Community
RxSwift ecosystem projects
RxSwift Community
Realm RxSwift - This application was written in order to use Realm, RxSwift frameworks in real example

Realm_RxSwift This simple app was written to introduce basic operations of some

Elbek Khasanov 3 Apr 7, 2022
RxSwift reactive wrapper for view gestures

RxGesture Usage To run the example project, clone the repo, in the Example folder open RxGesture.xcworkspace. You might need to run pod install from t

RxSwift Community 1.3k Dec 30, 2022
A reactive wrapper built around UIImagePickerController.

RxMediaPicker RxMediaPicker is a RxSwift wrapper built around UIImagePickerController consisting in a simple interface for common actions like picking

RxSwift Community 180 Apr 24, 2022
Super Simple Pager with RxSwift extension

SSPager Super Simple Pager Example To run the example project, clone the repo, and run pod install from the SSPagerExample directory first. Installati

9oya 4 Jul 10, 2022
RxSwift extentions for Swift optionals and "Occupiable" types

RxOptional RxSwift extentions for Swift optionals and "Occupiable" types. Usage All operators are available on Driver as well unless otherwise marked.

Thane Gill 8 Jun 28, 2020
RxSwift bindings for Permissions API in iOS.

RxPermission RxSwift bindings for Permission API that helps you with Permissions in iOS. Installation RxPermission is available through CocoaPods. I c

Luke 230 Dec 27, 2022
RxSwift extension for RealmSwift's types

RxRealm This library is a thin wrapper around RealmSwift ( Realm Docs ). Table of contents: Observing object collections Observing a single object Wri

RxSwift Community 1.1k Jan 6, 2023
iOS & OSX Bluetooth library for RxSwift

RxBluetoothKit is a Bluetooth library that makes interaction with BLE devices much more pleasant. It's backed by RxSwift and CoreBluetooth and it prov

Polidea 1.3k Dec 16, 2022
Handy RxSwift extensions on NSObject, including rx.disposeBag.

NSObject+Rx If you're using RxSwift, you've probably encountered the following code more than a few times. class MyObject: Whatever { let disposeBag

RxSwift Community 625 Dec 12, 2022
RxSwift extensions for Core Data

RxCoreData Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements Xcode 9.0 Swift 4.0

RxSwift Community 164 Oct 14, 2022