Skip to content

Commit

Permalink
4.0 release, memory leaks fixes (#158)
Browse files Browse the repository at this point in the history
* 4.0 release. Memory leaks fixes

* Updated RxSwift dependency to support any 4.X relese of RxSwift

* Fixed multiline strings & review fixes
  • Loading branch information
Kacper20 authored Oct 20, 2017
1 parent 27b889b commit 6888ca4
Show file tree
Hide file tree
Showing 13 changed files with 242 additions and 124 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# 4.0
- App updated to newest iOS & macOS SDK
- Swift 4 adoption
- Possible memory leaks removed

# 3.1.1
- `listenedOnRestoredState` should not be missing events from delegate now in case of race condition
Expand Down
2 changes: 1 addition & 1 deletion Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
github "Quick/Nimble" "v7.0.2"
github "Quick/Quick" "v1.2.0"
github "ReactiveX/RxSwift" "4.0.0-rc.0"
github "ReactiveX/RxSwift" "4.0.0"
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ RxBluetoothKit is an Bluetooth library that makes interaction with BLE devices m
Provides nice API to work with, and makes your code more readable, reliable and easier to maintain.

* 3.0 version supports Swift 3.0 and 3.1
* 4.0.0-rc.0 version of the library supports Swift 3.2 and 4.0
* 4.0 version of the library supports Swift 3.2 and 4.0


For support head to [StackOverflow](http://stackoverflow.com/questions/tagged/rxiosble?sort=active), or open [an issue](https://github.com/Polidea/RxBluetoothKit/issues/new) on GitHub.
Expand All @@ -21,7 +21,7 @@ Read the official announcement at [Polidea Blog](https://www.polidea.com/blog/Rx
- [x] Scan sharing
- [x] Scan queueing
- [x] Bluetooth error bubbling
- [x] [Documentation](http://cocoadocs.org/docsets/RxBluetoothKit/3.0.6/)
- [x] [Documentation](http://cocoadocs.org/docsets/RxBluetoothKit/4.0.0/)

## Sample
In Example folder you can find application we've provided to you. It's a great place to dig in, once you want to see everything in action. App provides most of the common usages of RxBluetoothKit.
Expand Down
4 changes: 2 additions & 2 deletions RxBluetoothKit.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "RxBluetoothKit"
s.version = "4.0.0-rc.1"
s.version = "4.0.0"
s.summary = "Bluetooth library for RxSwift"

s.description = <<-DESC
Expand All @@ -20,5 +20,5 @@ Pod::Spec.new do |s|

s.source_files = 'Source/*.swift'
s.frameworks = 'CoreBluetooth'
s.dependency 'RxSwift', '4.0.0-rc.0'
s.dependency 'RxSwift', '~> 4.0.0'
end
9 changes: 9 additions & 0 deletions Source/BluetoothError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import CoreBluetooth

/// Bluetooth error which can be emitted by RxBluetoothKit created observables.
public enum BluetoothError: Error {
/// Emitted when the object that is the source of Observable was destroyed and event was emitted nevertheless.
/// To mitigate it dispose all of your subscriptions before deinitializing
/// object that created Observables that subscriptions are made to.
case destroyed
// States
case bluetoothUnsupported
case bluetoothUnauthorized
Expand Down Expand Up @@ -54,6 +58,11 @@ extension BluetoothError: CustomStringConvertible {
/// Human readable description of bluetooth error
public var description: String {
switch self {
case .destroyed:
return """
The object that is the source of this Observable was destroyed.
It's programmer's error, please check documentation of error for more details
"""
case .bluetoothUnsupported:
return "Bluetooth is unsupported"
case .bluetoothUnauthorized:
Expand Down
104 changes: 62 additions & 42 deletions Source/BluetoothManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,13 @@ public class BluetoothManager {
/// - returns: Infinite stream of scanned peripherals.
public func scanForPeripherals(withServices serviceUUIDs: [CBUUID]?, options: [String: Any]? = nil)
-> Observable<ScannedPeripheral> {

return .deferred {
let observable: Observable<ScannedPeripheral> = { () -> Observable<ScannedPeripheral> in
return .deferred { [weak self] in
guard let strongSelf = self else { throw BluetoothError.destroyed }
let observable: Observable<ScannedPeripheral> = { [weak self] () -> Observable<ScannedPeripheral> in
guard let strongSelf = self else { return .error(BluetoothError.destroyed) }
// If it's possible use existing scan - take if from the queue
self.lock.lock(); defer { self.lock.unlock() }
if let elem = self.scanQueue.first(where: { $0.shouldAccept(serviceUUIDs) }) {
strongSelf.lock.lock(); defer { strongSelf.lock.unlock() }
if let elem = strongSelf.scanQueue.first(where: { $0.shouldAccept(serviceUUIDs) }) {
guard let serviceUUIDs = serviceUUIDs else {
return elem.observable
}
Expand All @@ -139,44 +140,46 @@ public class BluetoothManager {
let scanOperationBox = WeakBox<ScanOperation>()

// Create new scan which will be processed in a queue
let operation = Observable.create { (element: AnyObserver<ScannedPeripheral>) -> Disposable in

let operation = Observable.create { [weak self] (element: AnyObserver<ScannedPeripheral>) -> Disposable in
guard let strongSelf = self else { return Disposables.create() }
// Observable which will emit next element, when peripheral is discovered.
let disposable = self.centralManager.rx_didDiscoverPeripheral
.map { (peripheral, advertisment, rssi) -> ScannedPeripheral in
let peripheral = Peripheral(manager: self, peripheral: peripheral)
let disposable = strongSelf.centralManager.rx_didDiscoverPeripheral
.flatMap { [weak self] (peripheral, advertisment, rssi) -> Observable<ScannedPeripheral> in
guard let strongSelf = self else { throw BluetoothError.destroyed }
let peripheral = Peripheral(manager: strongSelf, peripheral: peripheral)
let advertismentData = AdvertisementData(advertisementData: advertisment)
return ScannedPeripheral(peripheral: peripheral,
advertisementData: advertismentData, rssi: rssi)
return .just(ScannedPeripheral(peripheral: peripheral,
advertisementData: advertismentData, rssi: rssi))
}
.subscribe(element)

// Start scanning for devices
self.centralManager.scanForPeripherals(withServices: serviceUUIDs, options: options)
strongSelf.centralManager.scanForPeripherals(withServices: serviceUUIDs, options: options)

return Disposables.create {
return Disposables.create { [weak self] in
guard let strongSelf = self else { return }
// When disposed, stop all scans, and remove scanning operation from queue
self.centralManager.stopScan()
strongSelf.centralManager.stopScan()
disposable.dispose()
do { self.lock.lock(); defer { self.lock.unlock() }
if let index = self.scanQueue.index(where: { $0 == scanOperationBox.value! }) {
self.scanQueue.remove(at: index)
do { strongSelf.lock.lock(); defer { strongSelf.lock.unlock() }
if let index = strongSelf.scanQueue.index(where: { $0 == scanOperationBox.value! }) {
strongSelf.scanQueue.remove(at: index)
}
}
}
}
.queueSubscribe(on: self.subscriptionQueue)
.queueSubscribe(on: strongSelf.subscriptionQueue)
.publish()
.refCount()

let scanOperation = ScanOperation(uuids: serviceUUIDs, observable: operation)
self.scanQueue.append(scanOperation)
strongSelf.scanQueue.append(scanOperation)

scanOperationBox.value = scanOperation
return operation
}()
// Allow scanning as long as bluetooth is powered on
return self.ensure(.poweredOn, observable: observable)
return strongSelf.ensure(.poweredOn, observable: observable)
}
}

Expand All @@ -186,8 +189,9 @@ public class BluetoothManager {
/// - returns: Observable that emits `Next` immediately after subscribtion with current state of Bluetooth. Later,
/// whenever state changes events are emitted. Observable is infinite : doesn't generate `Complete`.
public var rx_state: Observable<BluetoothState> {
return .deferred {
self.centralManager.rx_didUpdateState.startWith(self.centralManager.state)
return .deferred { [weak self] in
guard let `self` = self else { throw BluetoothError.destroyed }
return self.centralManager.rx_didUpdateState.startWith(self.centralManager.state)
}
}

Expand Down Expand Up @@ -222,8 +226,12 @@ public class BluetoothManager {
throw BluetoothError.peripheralConnectionFailed(Peripheral(manager: self, peripheral: peripheral), error)
}

let observable = Observable<Peripheral>.create { observer in
if let error = BluetoothError(state: self.centralManager.state) {
let observable = Observable<Peripheral>.create { [weak self] observer in
guard let strongSelf = self else {
observer.onError(BluetoothError.destroyed)
return Disposables.create()
}
if let error = BluetoothError(state: strongSelf.centralManager.state) {
observer.onError(error)
return Disposables.create()
}
Expand All @@ -236,11 +244,12 @@ public class BluetoothManager {

let disposable = success.amb(error).subscribe(observer)

self.centralManager.connect(peripheral.peripheral, options: options)
strongSelf.centralManager.connect(peripheral.peripheral, options: options)

return Disposables.create {
return Disposables.create { [weak self] in
guard let strongSelf = self else { return }
if !peripheral.isConnected {
self.centralManager.cancelPeripheralConnection(peripheral.peripheral)
strongSelf.centralManager.cancelPeripheralConnection(peripheral.peripheral)
disposable.dispose()
}
}
Expand All @@ -255,9 +264,13 @@ public class BluetoothManager {
/// connect or has already connected.
/// - returns: Observable which emits next and complete events when peripheral successfully cancelled connection.
public func cancelPeripheralConnection(_ peripheral: Peripheral) -> Observable<Peripheral> {
let observable = Observable<Peripheral>.create { observer in
let disposable = self.monitorDisconnection(for: peripheral).take(1).subscribe(observer)
self.centralManager.cancelPeripheralConnection(peripheral.peripheral)
let observable = Observable<Peripheral>.create { [weak self] observer in
guard let strongSelf = self else {
observer.onError(BluetoothError.destroyed)
return Disposables.create()
}
let disposable = strongSelf.monitorDisconnection(for: peripheral).take(1).subscribe(observer)
strongSelf.centralManager.cancelPeripheralConnection(peripheral.peripheral)
return disposable
}
return ensure(.poweredOn, observable: observable)
Expand All @@ -270,11 +283,13 @@ public class BluetoothManager {
/// - returns: Observable which emits retrieved `Peripheral`s. They are in connected state and contain all of the
/// `Service`s with UUIDs specified in the `serviceUUIDs` parameter. Just after that complete event is emitted
public func retrieveConnectedPeripherals(withServices serviceUUIDs: [CBUUID]) -> Observable<[Peripheral]> {
let observable = Observable<[Peripheral]>.deferred {
return self.centralManager.retrieveConnectedPeripherals(withServices: serviceUUIDs)
.map { (peripheralTable: [RxPeripheralType]) ->
[Peripheral] in peripheralTable.map {
Peripheral(manager: self, peripheral: $0)
let observable = Observable<[Peripheral]>.deferred { [weak self] in
guard let strongSelf = self else { throw BluetoothError.destroyed }
return strongSelf.centralManager.retrieveConnectedPeripherals(withServices: serviceUUIDs)
.map { [weak self] (peripheralTable: [RxPeripheralType]) -> [Peripheral] in
guard let strongSelf = self else { throw BluetoothError.destroyed }
return peripheralTable.map {
Peripheral(manager: strongSelf, peripheral: $0)
}
}
}
Expand All @@ -285,11 +300,13 @@ public class BluetoothManager {
/// - parameter identifiers: List of `Peripheral`'s identifiers which should be retrieved.
/// - returns: Observable which emits next and complete events when list of `Peripheral`s are retrieved.
public func retrievePeripherals(withIdentifiers identifiers: [UUID]) -> Observable<[Peripheral]> {
let observable = Observable<[Peripheral]>.deferred {
return self.centralManager.retrievePeripherals(withIdentifiers: identifiers)
.map { (peripheralTable: [RxPeripheralType]) ->
[Peripheral] in peripheralTable.map {
Peripheral(manager: self, peripheral: $0)
let observable = Observable<[Peripheral]>.deferred { [weak self] in
guard let strongSelf = self else { throw BluetoothError.destroyed }
return strongSelf.centralManager.retrievePeripherals(withIdentifiers: identifiers)
.map { [weak self] (peripheralTable: [RxPeripheralType]) -> [Peripheral] in
guard let strongSelf = self else { throw BluetoothError.destroyed }
return peripheralTable.map {
Peripheral(manager: strongSelf, peripheral: $0)
}
}
}
Expand Down Expand Up @@ -357,7 +374,10 @@ public class BluetoothManager {
return centralManager
.rx_willRestoreState
.take(1)
.map { RestoredState(restoredStateDictionary: $0, bluetoothManager: self) }
.flatMap { [weak self] dict -> Observable<RestoredState> in
guard let strongSelf = self else { throw BluetoothError.destroyed }
return .just(RestoredState(restoredStateDictionary: dict, bluetoothManager: strongSelf))
}
}
#endif
}
4 changes: 2 additions & 2 deletions Source/Descriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ public class Descriptor {

/// Function that triggers read of current value of the `Descriptor` instance.
/// Read is called after subscription to `Observable` is made.
/// - Returns: Observable which emits `Next` with given descriptor when value is ready to read. Immediately after that
/// `.Complete` is emitted.
/// - Returns: Observable which emits `Next` with given descriptor when value is ready to read.
/// Immediately after that `.Complete` is emitted.
public func readValue() -> Observable<Descriptor> {
return characteristic.service.peripheral.readValue(for: self)
}
Expand Down
3 changes: 2 additions & 1 deletion Source/Observable+QueueSubscribeOn.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ extension ObservableType {
/// only when there are no registered subscription on queue or last running observable completed its stream
/// or was disposed before that event.
/// - parameter queue: Queue on which scheduled subscriptions will be executed in sequentially.
/// - returns: The source which will be subscribe when queue is empty or previous observable was completed or disposed.
/// - returns: The source which will be subscribe when queue is empty or previous
/// observable was completed or disposed.
func queueSubscribe(on queue: SerializedSubscriptionQueue) -> Observable<E> {
return QueueSubscribeOn(source: asObservable(), queue: queue).asObservable()
}
Expand Down
Loading

0 comments on commit 6888ca4

Please sign in to comment.