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

3.0 ">
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

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

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
let peer: Observable>
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

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
let peer: Observable>
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
let peer: Observable>

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
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 (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:

, string: String) in otherclient.send(toPeer: client, "Roger") } .concat() .subscribeNext { _ in print("Response sent") } .addDisposableTo(disposeBag) ">
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

Contributing

  • Indent with 2 spaces
  • Strip trailing whitespace
  • Write tests
  • Pull-request from feature branches.
You might also like...
GitHub iOS client in RxSwift and MVVM-C clean architecture
GitHub iOS client in RxSwift and MVVM-C clean architecture

GitHub iOS client in RxSwift and MVVM-C clean architecture. FlutterHub - Flutter version available at an early stage KotlinHub - Android version is co

GitTime is GitHub Tracking App. Using ReactorKit, RxSwift, Moya.
GitTime is GitHub Tracking App. Using ReactorKit, RxSwift, Moya.

GitTime Feature Activity: GitHub Contributions graph & Event lists Trending: Trending Repositories & Developers Buddys: Show your buddy's contribution

Simple REST Client based on RxSwift and Alamofire.

RxRestClient Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements iOS 10.0+ tvOS 10.

Lightweight lib around NSURLSession to ease HTTP calls

AeroGear iOS HTTP Thin layer to take care of your http requests working with NSURLSession. Project Info License: Apache License, Version 2.0 Build: Co

Http Request wrapper written in Swift

Net Net is a HttpRequest wrapper written in Swift Features GET, POST, PUT, DELETE method Powerful request params: nested params, number, string, dic,

Another network wrapper for URLSession. Built to be simple, small and easy to create tests at the network layer of your application.
Another network wrapper for URLSession. Built to be simple, small and easy to create tests at the network layer of your application.

Another network wrapper for URLSession. Built to be simple, small and easy to create tests at the network layer of your application. Install Carthage

A WIP Swift wrapper for Discord's Bot API.

Quickcord Quickcord is a WIP Swift wrapper for Discord's Bot API. Features (As of right now) Connecting the the gateway and identifying. Literally eve

Easy to use CFNetwork wrapper for HTTP requests, Objective-C, Mac OS X and iPhone

ASIHTTPRequest is an easy to use wrapper around the CFNetwork API that makes some of the more tedious aspects of communicating with web servers easier

A custom wrapper over AFNetworking library that we use inside RC extensively

AFNetworkingHelper A very simple wrapper over the most amazing networking library for objective C, AFNetworking. We extensively use it inside RC and i

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
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.

Manav Gabhawala 93 Aug 2, 2022
Thin wrapper around NSURLSession in swift. Simplifies HTTP requests.

SwiftHTTP SwiftHTTP is a thin wrapper around NSURLSession in Swift to simplify HTTP requests. Features Convenient Closure APIs Simple Queue Support Pa

Dalton 1.9k Dec 7, 2022
A glorious Swift wrapper around NSNotificationCenter

Kugel A glorious Swift wrapper around NSNotificationCenter. ⚠️ Deprecated ⚠️ This library is deprecated and will not be maintained anymore. With Swift

Scoop 75 Sep 22, 2022
A Combine-style wrapper around Network's NWConnection with a UDP protocol

A Combine-style wrapper around Network's NWConnection with a UDP protocol

Emlyn Bolton 3 Sep 26, 2022
Simple REST Client based on RxSwift and Alamofire.

RxRestClient Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements iOS 10.0+ tvOS 10.

STDev 16 Nov 19, 2022
Write clean, concise and declarative network code relying on URLSession, with the power of RxSwift. Inspired by Retrofit.

ReactiveAPI Reactive API library allows you to write clean, concise and declarative network code, with the power of RxSwift and URLSession! iOS Demo A

Sky UK Ltd 79 Nov 19, 2022
RxSwift+MVVM 4시간에 끝내기

RxSwift+MVVM 4시간에 끝내기 RxSwift 4시간에 끝내기 (시즌2) Preface 요즘 관심이 높은 RxSwift! RxSwift는

코코종 0 Jan 9, 2022
iOS Todo Application using RxSwift and ReactorKit

RxTodo RxTodo is an iOS application developed using ReactorKit. This project is for whom having trouble with learning how to build a RxSwift applicati

Suyeol Jeon 1.3k Jan 3, 2023
🧚 RxSwift + Moya + HandyJSON + Plugins.

RxNetworks ?? . RxSwift + Moya + HandyJSON + Plugins. ?? ?? ?? English | 简体中文 This is a set of infrastructure based on RxSwift + Moya MoyaNetwork This

77。 140 Jan 3, 2023
Permission Util for iOS (feat.RxSwift)

EzRxPermission Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements Installation EzR

Yunjae-Na 4 Jun 30, 2022