swift iOS generic Realm manager - ios

I am looking to create generic Realm manager to perform CRUD operations. I can addOrUpdate single object to local db using below code
var profileManager: RealmManagerN<Profile>?
self.profileManager?.addOrUpdate(object: value.profile!,
completion: { error in
if let err = error {
print("Error \(err.localizedDescription)")
} else {
}
})
But I am facing issue while adding array object. I have tried below code
var deviceManager: RealmManagerN<Devices>?
self.deviceManager?.addOrUpdate(object: value.devices!,
completion: { error in
if let err = error {
print("Error \(err.localizedDescription)")
} else {
}
})
Below is my Devices model
import RealmSwift
class Devices: Object, Decodable {
#objc dynamic var addedOn: Double = 0.0
#objc dynamic var category: String? = nil
#objc dynamic var deviceId = ""
#objc dynamic var deviceName: String? = nil
#objc dynamic var deviceType : String? = nil
#objc dynamic var firmware: String? = nil
#objc dynamic var isActive: Bool = true
#objc dynamic var lastUpdate: Double = 0.0
#objc dynamic var uuDeviceId = ""
#objc dynamic var model: String? = nil
#objc dynamic var typeId = 0
#objc dynamic var userId = 0
enum CodingKeys: String, CodingKey {
case addedOn = "added_on"
case category = "category"
case deviceId = "device_id"
case deviceName = "device_name"
case deviceType = "device_type"
case firmware = "firmware"
case isActive = "is_active"
case lastUpdate = "last_update"
case uuDeviceId = "uu_device_id"
case model = "model"
case typeId = "type_id"
case userId = "user_id"
}
override public static func primaryKey() -> String? {
return "uuDeviceId"
}
}
Note: value.devices is the array of Devices
I am getting this error Cannot convert value of type '[Devices]' to expected argument type 'Devices'. How can I insert collection?
EDIT : This is my RealmManagerN.swift
import Foundation
import Realm
import RealmSwift
/**
Realm manager class that reduces the boiler plate needed when creating a realm transaction.
createOrUpdate, and Delete uses background thread
- warning: This class assumes that every existing model being passed has a primaryKey set to it
*/
public class RealmManagerN<T> {
public typealias Completion = ((_ error: Error?) -> Void)
var realm: Realm?
var background: RealmThread?
init(configuration: Realm.Configuration?,
fileUrl: URL?) {
background = RealmThread(start: true,
queue: nil)
background?.enqueue {[weak self] in
guard let self = self else { return }
do {
if let config = configuration {
self.realm = try Realm(configuration: config)
} else if let fileUrl = fileUrl {
self.realm = try Realm(fileURL: fileUrl)
} else {
self.realm = try Realm()
}
} catch let error {
fatalError(error.localizedDescription)
}
}
}
}
extension RealmManager {
fileprivate func addOrUpdateWithRealm<Q: Collection>(realm: Realm,
object: Q,
completion: #escaping Completion) where Q.Element == Object {
do {
try realm.write {
realm.add(object,
update: .error)
DispatchQueue.main.async {
completion(nil)
}
}
} catch (let error) {
DispatchQueue.main.async {
completion(error)
}
}
}
fileprivate func addOrUpdateWithRealm<T: Object>(realm: Realm,
object: T,
completion: #escaping Completion) {
do {
try realm.write {
realm.add(object,
update: .error)
DispatchQueue.main.async {
completion(nil)
}
}
} catch (let error) {
DispatchQueue.main.async {
completion(error)
}
}
}
fileprivate func write(rlmObject: Realm, writeBlock:()-> Void) -> Error? {
do {
//try to do a realm transaction
try rlmObject.write {
writeBlock()
}
} catch let error {
//catch and return the error if occurs
return error
}
//no error
return nil
}
fileprivate func fetch<Q: Object>(condition: String?,
completion: #escaping(_ result: Results<Q>) -> Void) {
guard let realm = realm else { return }
// All object inside the model passed.
var bufferObjects = realm.objects(Q.self)
if let cond = condition {
// filters the result if condition exists
bufferObjects = bufferObjects.filter(cond)
}
DispatchQueue.main.async {
//if let safe = bufferObjects.copy() as? Results<Q> {
completion(safe)
//}
}
}
}
extension RealmManagerN where T: Collection, T.Element == Object {
/// Add or Update an object to existing Model
///
/// Accept any object that conforms to Collection Protocol,
/// Takes a closure as escaping
/// parameter.
///
/// - Parameter object: [Object] to be saved.
/// - Parameter completion: Closure called after
/// realm transaction
/// - Parameter error: an optional value containing error
public func addOrUpdate(object: T,
completion:#escaping Completion) {
background?.enqueue {[weak self] in
guard let self = self else { return }
guard let realm = self.realm else { return }
self.addOrUpdateWithRealm(realm: realm,
object: object,
completion: completion)
}
}
//MARK: - File Private
fileprivate func delete(condition: String?,
objects: T,
completion:#escaping(_ error: Error?) -> Void) {
let group = DispatchGroup()
var error: Error?
background?.enqueue {[weak self] in
group.enter()
guard let self = self else { return }
guard let realm = self.realm else { return }
error = self.write(rlmObject: realm, writeBlock: {
realm.delete(objects)
group.leave()
})
}
group.wait()
DispatchQueue.main.async {
completion(error)
}
}
}
extension RealmManagerN where T: Object {
/// Add or Update an object to existing Model
///
/// Accept any object that is a subclass of Object or RealmObject,
/// Takes a closure as escaping
/// parameter.
///
/// - Parameter object: Object to be saved.
/// - Parameter completion: Closure called after
/// realm transaction
/// - Parameter error: an optional value containing error
public func addOrUpdate(configuration: Realm.Configuration? = nil,
object: T,
completion: #escaping Completion) {
background?.enqueue {[weak self] in
guard let self = self else { return }
guard let realm = self.realm else { return }
self.addOrUpdateWithRealm(realm: realm,
object: object,
completion: completion)
}
}
/// Fetches object from existing model
///
///
/// - Parameter type: Type representing the object to be fetch, must be
/// subclass of Object
/// - Parameter condition: Predicate to be used when fetching
/// data from the Realm database (Optional: String)
/// - Parameter completion: Closure called after the
/// realm transaction
/// - Parameter result: An Array of Object as result from
/// the fetching
public func fetchWith(condition: String?,
completion:#escaping(_ result: Results<T>) -> Void) {
background?.enqueue {[weak self] in
guard let self = self else { return }
self.fetch(condition: condition,
completion: completion)
}
}
/// Deletes an object from the existing model
///
///
/// - Parameter configuration: Realm Configuration to be used
/// - Parameter model: A string of any class NAME that inherits from 'Object' class
/// - Parameter condition: Predicate to be used when deleting
/// data from the Realm database (Optional: String)
/// - Parameter completion: Closure called after the
/// realm transaction
/// - Parameter error: an optional value containing error
public func deleteWithObject(_ object: T?,
condition: String,
completion:#escaping(_ error: Error?) -> Void) {
background?.enqueue {[weak self] in
guard let self = self else { return }
self.delete(object: object,
condition: condition,
completion: completion)
}
}
///MARK: FilePrivates
fileprivate func delete(object: T?,
condition: String?,
completion:#escaping(_ error: Error?) -> Void) {
guard let realm = realm else { return }
let group = DispatchGroup()
var error: Error?
background?.enqueue {[weak self] in
group.enter()
guard let self = self else { return }
if object == nil {
var fetched = realm.objects(T.self)
if let cond = condition {
// filters the result if condition exists
fetched = fetched.filter(cond)
}
error = self.write(rlmObject: realm, writeBlock: {
realm.delete(fetched)
group.leave()
})
} else {
if let object = object {
error = self.write(rlmObject: realm, writeBlock: {
realm.delete(object)
group.leave()
})
}
}
}
group.wait()
DispatchQueue.main.async {
completion(error)
}
}
}
Below is code for RealmThread
import Foundation
/// FIFO. First-In-First-Out guaranteed on exactly same thread.
class RealmThread: Thread {
typealias Block = () -> ()
private let condition = NSCondition()
private(set) var queue = [Block]()
private(set) var paused: Bool = false
/**
Designated initializer.
- parameters:
- start: Boolean whether thread should start immediately. Defaults to true.
- queue: Initial array of blocks to add to enqueue. Executed in order of objects in array.
*/
init(start: Bool = true, queue: [Block]? = nil) {
super.init()
// Add blocks initially to queue
if let queue = queue {
for block in queue {
enqueue(block: block)
}
}
// Start thread
if start {
self.start()
}
}
/**
The main entry point routine for the thread.
You should never invoke this method directly. You should always start your thread by invoking the start method.
Shouldn't invoke `super`.
*/
final override func main() {
// Infinite loops until thread is cancelled
while true {
// Use NSCondition. Comments are from Apple documentation on NSCondition
// 1. Lock the condition object.
condition.lock()
// 2. Test a boolean predicate. (This predicate is a boolean flag or other variable in your code that indicates whether it is safe to perform the task protected by the condition.)
// If no blocks (or paused) and not cancelled
while (queue.count == 0 || paused) && !isCancelled {
// 3. If the boolean predicate is false, call the condition object’s wait or waitUntilDate: method to block the thread. Upon returning from these methods, go to step 2 to retest your boolean predicate. (Continue waiting and retesting the predicate until it is true.)
condition.wait()
}
// 4. If the boolean predicate is true, perform the task.
// If your thread supports cancellation, it should check this property periodically and exit if it ever returns true.
if (isCancelled) {
condition.unlock()
return
}
// As per http://stackoverflow.com/a/22091859 by Marc Haisenko:
// Execute block outside the condition, since it's also a lock!
// We want to give other threads the possibility to enqueue
// a new block while we're executing a block.
let block = queue.removeFirst()
condition.unlock()
// Run block
block()
}
}
/**
Add a block to be run on the thread. FIFO.
- parameters:
- block: The code to run.
*/
final func enqueue(block: #escaping Block) {
// Lock to ensure first-in gets added to array first
condition.lock()
// Add to queue
queue.append(block)
// Release from .wait()
condition.signal()
// Release lock
condition.unlock()
}
/**
Start the thread.
- Warning: Don't start thread again after it has been cancelled/stopped.
- SeeAlso: .start()
- SeeAlso: .pause()
*/
final override func start() {
// Lock to let all mutations to behaviour obey FIFO
condition.lock()
// Unpause. Might be in pause state
// Start
super.start()
// Release from .wait()
condition.signal()
// Release lock
condition.unlock()
}
/**
Cancels the thread.
- Warning: Don't start thread again after it has been cancelled/stopped. Use .pause() instead.
- SeeAlso: .start()
- SeeAlso: .pause()
*/
final override func cancel() {
// Lock to let all mutations to behaviour obey FIFO
condition.lock()
// Cancel NSThread
super.cancel()
// Release from .wait()
condition.signal()
// Release lock
condition.unlock()
}
/**
Pause the thread. To completely stop it (i.e. remove it from the run-time), use `.cancel()`
- Warning: Thread is still runnin,
- SeeAlso: .start()
- SeeAlso: .cancel()
*/
final func pause() {
// Lock to let all mutations to behaviour obey FIFO
condition.lock()
//
paused = true
// Release from .wait()
condition.signal()
// Release lock
condition.unlock()
}
/**
Resume the execution of blocks from the queue on the thread.
- Warning: Can't resume if thread was cancelled/stopped.
- SeeAlso: .start()
- SeeAlso: .cancel()
*/
final func resume() {
// Lock to let all mutations to behaviour obey FIFO
condition.lock()
//
paused = false
// Release from .wait()
condition.signal()
// Release lock
condition.unlock()
}
/**
Empty the queue for any blocks that hasn't been run yet
- SeeAlso:
- .enqueue(block: Block)
- .cancel()
*/
final func emptyQueue() {
// Lock to let all mutations to behaviour obey FIFO
condition.lock()
// Remove any blocks from the queue
queue.removeAll()
// Release from .wait()
condition.signal()
// Release lock
condition.unlock()
}
}

Related

Swift How to solve a Memory "Leak"

I have encountered a memory "leak" that I don't understand. I have a caching class whose purpose is to limit the number of instances of heavy Data items. Even when it stores NO instances of those items, they are being retained somehow. If they are from a background thread, I believe they are released upon completion of the task. But, of course, as demonstrated by the code, on the main thread they persist.
This occurs on iPadOS 14.8, iPadOS 15 and MacCatalyst.
What am I missing or don't understand?
class DataCache {
var id: String
var data: Data? {
get {
let url = FileManager.default.temporaryDirectory
.appendingPathComponent("\(id).txt")
return try! Data(contentsOf: url)
}
set {
let url = FileManager.default.temporaryDirectory
.appendingPathComponent("\(id).txt")
try! newValue!.write(to: url)
}
}
init(id: String, data: Data) {
self.id = id
self.data = data
}
}
class Item {
static var selection = [Item]()
static var items = [String:Item]()
var id: String = UUID().uuidString
var itemData: DataCache
init() {
itemData = DataCache(id: id,
data: Data(String(repeating: "dummy", count: 4_000_000).utf8)
)
}
required init(_ other: Item) {
self.itemData = DataCache(id: self.id, data: other.itemData.data!)
}
func duplicate(times: Int) {
for index in 0..<times {
print(index)
Item.selection.append(Item(self))
}
}
}
#main struct Main {
static func main() throws {
let item = Item()
perform(item)
performOnSelection() { item in
let _ = Item(item)
}
while (true) {}
}
static func perform(_ item: Item) {
item.duplicate(times: 100)
}
static func performOnSelection(perform action: #escaping (Item)->Void) {
var done = false
DispatchQueue.global().async {
for item in Item.selection {
action(item)
}
done = true
}
while !done { sleep (1) }
}
}
You have autorelease objects being created. Insert an autoreleasepool to drain the pool periodically, e.g.:
func performOnSelection(perform action: #escaping (Item) -> Void) {
...
autoreleasepool {
perform(item)
}
performOnSelection() { item in
autoreleasepool {
let _ = Item(item)
}
}
...
}
and
func duplicate(times: Int) {
for index in 0..<times {
print(index)
autoreleasepool { [self] in
Item.selection.append(Item(self))
}
}
}
E.g. without autoreleasepool:
And with:
It turns off to be much faster when it is not dealing with all of those lingering autorelease objects, too. And peak memory usage has gone from 3.4gb to 47mb.

How can we test class which generates random states, and which can not generate same states twice?

We have three states.How can we test(with unit tests) our class which generates random state every 5 seconds, and which can not generate the same state twice in a row? The code of our random generator class is below
`
final class StateRandomGenerator: RandomGeneratorProtocol {
private var sourceObservable: Disposable?
private(set) var previousValue: Int?
var generatedValue: PublishSubject = PublishSubject()
init(_ interval: RxTimeInterval,_ scheduler: SchedulerType = MainScheduler.instance) {
sourceObservable = Observable<Int>
.interval(interval, scheduler: scheduler)
.flatMap { [unowned self] _ in self.generateRandom()}
.compactMap { state in
return state?.description
}
.subscribe(onNext: { [weak self] description in
self?.generatedValue.onNext(description)
})
}
func generateRandom() -> Observable<ConnectionState?> {
return Observable.create { [weak self] observer in
var randomNumber = Int.random(in: 0..<ConnectionState.count)
guard let previousValue = self?.previousValue else {
let value = ConnectionState(rawValue: randomNumber)
self?.previousValue = randomNumber
observer.onNext(value)
return Disposables.create()
}
while randomNumber == previousValue {
randomNumber = Int.random(in: 0..<ConnectionState.count)
}
self?.previousValue = randomNumber
let value = ConnectionState(rawValue: randomNumber)
observer.onNext(value)
return Disposables.create()
}
}
enum ConnectionState: Int {
case error
case connecting
case established
var description: String {
switch self {
case .connecting:
return "It is connecting"
case .error:
return "There is an error"
case .established:
return "Thе connection is established"
}
}
}
`
You can't successfully unit test your class because it doesn't halt. It just pegs the CPU and chews up memory until the system is finally starved and crashes.
Below is a working and tested Observable that does what you want... The test creates 100,000 ConnectionStates and then checks to ensure that no two adjacent are identical.
The main logic of the function is the closure passed to map which grabs all the cases and filters out the previous case. A random element is chosen from the remainder.
It would be pretty easy to make this generic across any enum I expect. I'll leave that as an exercise for the reader.
func stateRandom(_ interval: RxTimeInterval,_ scheduler: SchedulerType = MainScheduler.instance) -> Observable<ConnectionState> {
let previous = BehaviorRelay<ConnectionState?>(value: nil)
return Observable<Int>.interval(interval, scheduler: scheduler)
.withLatestFrom(previous)
.map { ConnectionState.allExcept($0) }
.flatMap { Observable.just($0.randomElement()!) }
.do(onNext: { previous.accept($0) })
}
extension CaseIterable where Self: Equatable {
static func allExcept(_ value: Self?) -> [Self] {
allCases.filter { $0 != value }
}
}
enum ConnectionState: CaseIterable, Equatable {
case error
case connecting
case established
}
class Tests: XCTestCase {
func test() throws {
let scheduler = TestScheduler(initialClock: 0)
let result = scheduler.start { stateRandom(.seconds(1), scheduler).take(100000) }
for (prev, current) in zip(result.events, result.events.dropFirst()) {
XCTAssertNotEqual(prev.value, current.value)
}
}
}

How to use touchIDAuthenticationAllowableReuseDuration

I am authenticating a user via LAContext when an app is launching or when will enter foreground. If a device was locked then the user will be asked twice to authorize himself. To avoid that behavior, I set context.touchIDAuthenticationAllowableReuseDuration value to 240 but It doesn't work as expected. The user still has to authorize himself twice.
What I am doing wrong?
import LocalAuthentication
class AccessControl {
internal var context = LAContext()
private var policy: LAPolicy = .deviceOwnerAuthentication
private var reason: String = NSLocalizedString("auhenticationLocalizedFallbackTitle", comment: "")
init() {
context.touchIDAuthenticationAllowableReuseDuration = 240
}
func evaluateUserWithBiometricsOrPasscode(success: #escaping () -> Void, error: #escaping () -> Void) {
guard context.canEvaluatePolicy(policy, error: nil) else {
error()
return
}
context.evaluatePolicy(policy, localizedReason: reason) { eStatus, eError in
DispatchQueue.main.async {
if eStatus {
success()
} else {
error()
}
}
}
}
}
You need to use the same LAContext object everytime to get that behaviour.
class AccessControl {
// MARK: - Singleton
public static let shared = AccessControl()
// Policy
private var policy: LAPolicy = .deviceOwnerAuthentication
// Reason
private var reason: String = NSLocalizedString("auhenticationLocalizedFallbackTitle", comment: "")
// Context
lazy var context: LAContext = {
let mainContext = LAContext()
if #available(iOS 9.0, *) {
// specify your interval
mainContext.touchIDAuthenticationAllowableReuseDuration = 60
}
return mainContext
}()
// Evaluate
func evaluateUserWithBiometricsOrPasscode(success: #escaping () -> Void, error: #escaping () -> Void) {
guard context.canEvaluatePolicy(policy, error: nil) else {
error()
return
}
context.evaluatePolicy(policy, localizedReason: reason) { eStatus, eError in
DispatchQueue.main.async {
if eStatus {
success()
} else {
error()
}
}
}
}
}
And calling this function like below: This will work for FaceID Authentication also.
AccessControl.shared.evaluateUserWithBiometricsOrPasscode(success: {
}) {
}

how to pass expression as argument in a function in Swift?

I am a junior iOS developer. I want to refactor my code. currently I want to refactor my realm methods. to save or delete data in Realm, I need to use a lot of do try catch block all over the place, so I want to make a service to handle this. I have tried but I don't know if this is the correct way or not
import RealmSwift
class RealmService {
private init() {}
static let shared = RealmService()
var realm = try! Realm()
func save(expression: ()) {
do {
try realm.write {
// I want to pass the expression in this block here
expression
}
} catch {
post(error)
}
}
}
usage:
func saveToRealmDatabase(wishList: WishList, product: Product) {
RealmService.shared.save(expression: {
// I want to pass expression in the block in here as argument
wishList.products.append(product)
}())
}
I want to pass the expression wishList.products.append(product) as argument of the function in RealmService class. is there a better way to achieve that ?
Your "expression" can be represented by the type () -> Void - a closure that accepts no parameters and returns nothing.
This is how your method should be declared:
func save(expression: #escaping () -> Void) {
do {
try realm.write {
expression()
}
} catch {
post(error)
}
}
This is how you should use it:
func saveToRealmDatabase(wishList: WishList, product: Product) {
RealmService.shared.save(expression: {
wishList.products.append(product)
})
}
From the approach you are trying i can guess you want to do something like,
RealmService.shared.save(expression: {
wishList.products.append(product)
realm.add(wishList.products)
})
So based on the understanding that you want to perform the addition operation in that callback, i would rather suggest the below implementation where you don't need to implement the callback for every operation. Rather when you are interested to know the status of the operation then only you would implement the callback. Check the below snippet,
class RealmService {
private init() {}
static let shared = RealmService()
var realm = try! Realm()
/// Saving a list of object i.e, List<Object>
func save<O, L: List<O>>(_ list: L, callback: ((Error?) -> Void)? = nil) where O: Object {
do {
try realm.write {
realm.add(list)
callback?(nil)
}
} catch {
callback?(error)
}
}
/// Saving a single object i.e, `Object`
func save(_ object: Object, callback: ((Error?) -> Void)? = nil) {
do {
try realm.write {
realm.add(object)
callback?(nil)
}
} catch {
callback?(error)
}
}
}
Usage
class Dog: Object {
#objc dynamic var name = ""
#objc dynamic var age = 0
}
let tomy = Dog()
tomy.name = "Tomy"
let johny = Dog()
johny.name = "Johny"
let dogs = List<Dog>()
dogs.append(tomy)
dogs.append(johny)
//When you are not interested in save operation status
RealmService.shared.save(dogs)
//When you want to handle error while saving
RealmService.shared.save(dogs) { [weak self] in self?.showAlert($0) }
func showAlert(_ error: Error?) {
guard let e = error else { return }
showAlert(alertTitle: "Error", alertMessage: e.localizedDescription, actionTitle: "Back")
}

RXswift, repeat observable until complete

I am trying to upload firmware to a BLE device. Therefore I push some data, wait for an acknowledgment and then push some more, and so on.
But I am struggling with repeating the observer until the observer returns complete.
This is the observer, so if there are no more packets to be sent, it should complete and stop repeating.
let transferImage = Observable<Void>.create { (obs) -> Disposable in
guard let nextPacket = dataSendingTracker.getNextPacket() else {
obs.onCompleted()
return Disposables.create()
}
return self.instrument()
.flatMap{ $0.sendFWpacket(packet: nextPacket) }
.subscribe(onNext: { () in
obs.onNext(())
}, onError: { (error) in
obs.onError(error)
})
}
Any suggestion on how I can achieve this?
Try something like this but I consider this ugly way around.... not Rx Solution
transferImage.takeWhile will stop when getNextPacket returns nil or this case Integer smaller than zero..
func test() {
let transferImage = Observable<Int>.create { (obs) -> Disposable in
if let nextPacket = self.getNextPacket() {
obs.onNext(nextPacket)
} else{
obs.onNext(-1)
obs.onCompleted()
}
return Disposables.create()
}
transferImage.takeWhile{$0 > 0}.subscribe(onNext: { nextPacket in
print(nextPacket)
let completed = self.sendFWpacket()
if !completed {
self.test()
}
}, onError: { error in
print(error)
}, onCompleted: {
print("onCompleted")
}) {
print("disposed")
}.disposed(by: disposeBag)
}
func sendFWpacket()-> Bool {
return false
}
func getNextPacket() -> Int? {
return 1
}
Assuming you have a function that writes a block of data like this test function:
func writeDataBlock(offset: Int, blockSize: Int) -> Observable<Int> {
let writtenBytesCount = min(Int(arc4random_uniform(5) + 5), blockSize)
return Observable<Int>.just(writtenBytesCount)
}
In reality this function would also use some data buffer and try to push a block of a given size from that data at a given offset, and return the number of bytes written when done. Here you could use your logic from transferImage.
Then the complete transfer function could be written using recursion like so:
func writeAllDataRec(offset: Int, totalSize: Int, observer: AnyObserver<String>) {
guard offset < totalSize else {
observer.onNext("writeAllData completed")
observer.onCompleted()
return
}
var subscriber: Disposable?
let blockSize = min(10, totalSize - offset)
subscriber = writeDataBlock(offset: offset, blockSize: blockSize).subscribe { ev in
switch ev {
case let .next(writtenBytesCount):
debugPrint("writeNextBlock from offset: \(offset); writtenBytesCount = \(writtenBytesCount)")
let newOffset = offset + writtenBytesCount
writeAllDataRec(offset: newOffset, totalSize: totalSize, observer: observer)
case .completed:
subscriber?.dispose()
//debugPrint("writeAllData block completed")
case let .error(err):
subscriber?.dispose()
observer.onError(err)
observer.onCompleted()
}
}
}
func writeAllData(totalSize: Int) -> Observable<String> {
return Observable<String>.create { (observer) -> Disposable in
writeAllDataRec(offset: 0, totalSize: totalSize, observer: observer)
return Disposables.create()
}
}
This could be tested like so:
var subscriber: Disposable?
...
self.subscriber = writeAllData(totalSize: 100).subscribe(onNext: { (message) in
print("\(message)")
})
In this solution I assume that your next block of data depends on how much of the data was written previously and that there's no shared global state.
This could be simplified a lot with the logic of RxJS expand, but unfortunately this function is not present in RxSwift (4.1.2).

Resources