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")
}
Related
Creating an example for a struct is very easy and straightforward. For example,
import Foundation
struct User: Identifiable, Codable {
let id: UUID
let isActive: Bool
let name: String
let age: Int
let company: String
static let example = User(id: UUID(), isActive: true, name: "Rick Owens", age: 35, company: "Rick Owens Inc.")
}
Now, how can I create an example if I made this an entity in core data? I can't just put let example = CachedUser(id: UUID(), ...) like I did with the struct. I want this example to be part of my core data automatically without having to manually create it by using forms, buttons, etc... Thanks in advance!
You can simply check if your default user exists in database. If it does not then you need to create one and save it. Something like the following would work if you have synchronous operations:
class CachedUser {
static var example: CachedUser = {
let exampleUUID = UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!
if let existingUser = Database.fetchUser(id: exampleUUID) {
return existingUser
} else {
let newUser = CachedUser()
// TODO: apply example values to user
Database.saveUser(newUser)
return newUser
}
}()
}
This will lazily return existing or generate a new user for you. This user will then be persistent in your database.
The code will only be executed once per session, first time you call CachedUser.example.
If you have your database setup asynchronous then with closures it should look something like this:
class User {
static private(set) var example: User!
static func prepareExampleUser(_ completion: () -> Void) {
let exampleUUID = UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!
Database.fetchUser(id: exampleUUID) { user in
if let user = user {
example = user
completion()
} else {
let newUser = User()
newUser.id = exampleUUID
// TODO: apply example values to user
Database.saveUser(newUser) {
example = newUser
completion()
}
}
}
}
But in this case it makes sense to warmup your application before you show screens that require this user to be present. You can for instance have a loading screen when your app first starts and continue to next screen once this method has finished...
// Loading screen enters
self.startLoading()
User.prepareExampleUser {
self.navigateToNextScreen()
self.stopLoading()
}
In both cases you now hold a static property to your example entry such as User.example which can be used anywhere.
But in both cases you may stumble to issue if user (if able to) deletes this entry from database. You would need to handle that case. Either prevent that action or create a new example user once the old one is deleted.
To access this manager put
let mgr = CachedUserPersistenceManager()
In a ViewModel or a View
/// Manager for the Item entity
class CachedUserPersistenceManager: PersistenceManager<CachedUser>{
let sampleUUID = UUID(uuidString: "00000000-0000-0000-0000-000000000000")!
init(isTest: Bool = false) {
super.init(entityType: CachedUser.self, isTest: isTest)
//Preloads the user
preloadSample()
}
///Preloads a sample object to the context
func preloadSample(){
let list = retrieveObjects(sortDescriptors: nil, predicate: NSPredicate(format: "%K == %#", #keyPath(CachedUser.uuid), sampleUUID as CVarArg)
)
if list.isEmpty{
let sampleItem = createObject()
sampleItem.uuid = sampleUUID
save()
}
}
override func addSample() -> CachedUser {
let new = super.addSample() as CachedUser
//add any sample code
return new
}
override func createObject() -> CachedUser {
super.createObject()!
}
override func updateObject(object: CachedUser) -> Bool {
//Replace the uuid if needed
if object.uuid == sampleUUID{
object.uuid = UUID()
}
return super.updateObject(object: object)
}
}
The generic classes that are a part of this code are below. You don't need them per say it just makes some of the code reusable through the app.
//Manager for any Entity
class PersistenceManager<T : NSManagedObject>{
let serviceSD: CoreDataPersistenceService<T>
internal init(entityType: T.Type, isTest: Bool = false) {
self.serviceSD = CoreDataPersistenceService(isTest: isTest, entityType: entityType)
}
//MARK: convenience
func addSample() -> T {
let newItem = createObject()
return newItem!
}
//MARK: Persistence Service Methods
func createObject() -> T? {
let result = serviceSD.createObject()
return result
}
func updateObject(object: T) -> Bool {
return serviceSD.updateObject(object: object)
}
func deleteObject(object: T) -> Bool {
return serviceSD.deleteObject(object: object)
}
func deleteAllObjects(entityName: String, isConfirmed: Bool) -> Bool {
return serviceSD.deleteAllObjects(isConfirmed: isConfirmed)
}
func retrieveObjects(sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?) -> [T]{
return serviceSD.retrieveObjects(sortDescriptors: sortDescriptors, predicate: predicate)
}
func retrieveObject(id: String) -> T? {
return serviceSD.retrieveObject(sortDescriptors: nil, id: id).first
}
func resetChanges() {
serviceSD.resetChanges()
}
func save() {
_ = serviceSD.save()
}
}
//Service for Any Entity
class CoreDataPersistenceService<T: NSManagedObject>: NSObject {
var persistenceController: PersistenceController
let entityType: T.Type
required init(isTest: Bool = false, entityType: T.Type) {
if isTest{
self.persistenceController = PersistenceController.preview
}else{
self.persistenceController = PersistenceController.previewAware
}
self.entityType = entityType
super.init()
}
//MARK: CRUD methods
func createObject() -> T? {
let result = entityType.init(context: persistenceController.container.viewContext)
return result
}
func updateObject(object: T) -> Bool {
var result = false
result = save()
return result
}
func deleteObject(object: T) -> Bool {
var result = false
persistenceController.container.viewContext.delete(object)
result = save()
return result
}
func deleteAllObjects(isConfirmed: Bool) -> Bool {
var result = false
//Locked in so only the Generic "Item" can be deleted like this
if entityType == Item.self && isConfirmed == true{
let deleteRequest = NSBatchDeleteRequest(fetchRequest: entityType.fetchRequest())
do {
try persistenceController.container.persistentStoreCoordinator.execute(deleteRequest, with: persistenceController.container.viewContext)
} catch {
print(error)
result = false
}
}
return result
}
func resetChanges() {
persistenceController.container.viewContext.rollback()
_ = save()
}
func save() -> Bool {
var result = false
do {
if persistenceController.container.viewContext.hasChanges{
try persistenceController.container.viewContext.save()
result = true
}else{
result = false
}
} catch {
print(error)
}
return result
}
func retrieveObject(sortDescriptors: [NSSortDescriptor]? = nil, id: String) -> [T]{
return retrieveObjects(sortDescriptors: sortDescriptors, predicate: NSPredicate(format: "id == %#", id))
}
func retrieveObjects(sortDescriptors: [NSSortDescriptor]? = nil, predicate: NSPredicate? = nil) -> [T]
{
let request = entityType.fetchRequest()
if let sortDescriptor = sortDescriptors
{
request.sortDescriptors = sortDescriptor
}
if let predicate = predicate
{
request.predicate = predicate
}
do
{
let results = try persistenceController.container.viewContext.fetch(request)
return results as! [T]
}
catch
{
print(error)
return []
}
}
}
The previewAware variable that is mentioned goes with the Apple standard code in the PersistenceController
It automatically give you the preview container so you don't have to worry about adapting your code for samples in Canvas. Just add the below code to the PersistenceController
static var previewAware : PersistenceController{
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
return PersistenceController.preview
}else{
return PersistenceController.shared
}
}
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()
}
}
I'm studying Swift and Core Data and I plan to use a simple wrapper over it for my models.
At this point, protocol and extension looks like this:
protocol CRUD {
associatedtype T: NSManagedObject
var context: NSManagedObjectContext { get }
var items: [T]! { get set }
func getAll() -> [T]
mutating func addOrUpdate(_ item: T) -> T
mutating func delete(_ item: T)
}
extension CRUD where T: NSManagedObject {
var context: NSManagedObjectContext {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
func save() {
do {
try context.save()
} catch {
fatalError("Saving of \(String(describing: self)) failed")
}
}
func getAll() -> [T] {
let fetchRequest = NSFetchRequest<T>(entityName: String(describing: T.self))
let list: [T]
do {
list = try context.fetch(fetchRequest)
} catch {
fatalError("Fetching of \(String(describing: self)) failed")
}
return list
}
mutating func delete(_ item: T) {
if let index = items.index(of: item) {
items.remove(at: index)
}
context.delete(item)
save()
}
mutating func addOrUpdate(_ item: T) -> T {
if (items.contains(item)) {
items.append(item)
}
save()
return item
}
}
And each model is declared like this:
class TaskModel : CRUD {
typealias T = Task
var items: [Task]!
init() {
self.items = getAll()
}
}
How much does this code correspond to the principles of OOP (in particular, can I call this protocol the implementation of the DAO pattern)? Are such wrappers needed? Or does Core Data imply the direct use of models in the code?
What possible problems can reveal with it in the future?
I will be very grateful for the advice from more experienced iOS developers. Thank you in advance.
A protocol might be too much for this kind of functionality, as protocols main goal is still polymorphism. You could use a generic struct instead:
struct CRUD<T: NSManagedObject> {
var context: NSManagedObjectContext {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
var items = [T]()
// let's provide two approaches for initialization
init() {
self.init(items: getAll())
}
init(items: [T]) {
self.items = items
}
func save() {
do {
try context.save()
} catch {
fatalError("Saving of \(String(describing: self)) failed")
}
}
func getAll() -> [T] {
let fetchRequest = NSFetchRequest<T>(entityName: String(describing: T.self))
let list: [T]
do {
list = try context.fetch(fetchRequest)
} catch {
fatalError("Fetching of \(String(describing: self)) failed")
}
return list
}
mutating func delete(_ item: T) {
if let index = items.index(of: item) {
items.remove(at: index)
}
context.delete(item)
save()
}
mutating func addOrUpdate(_ item: T) -> T {
if (items.contains(item)) {
items.append(item)
}
save()
return item
}
}
You can then use it in your class:
class TaskModel {
// making sure no-one from outside can mutate our CRUD
private(set) lazy var crud = CRUD<Task>()
init() {
// nothing to do here, the items are already populated
}
}
let model = TaskModel()
// the following won't compile
model.crud.delete(someTask)
IMO, this better transmit the intent of having a Facade over CoreData.
I'm fairly new to RxSwift, so I have the following problem, lets suppose I have 3 Observable example functions which return different observable types:
func observableFunc1(item1: DummyObject) -> Observable<AVURLAsset> {
return Observable.create { observer in
let something_with_AVURLAsset = AVURLAsset(url: URL(fileURLWithPath: "file"))
observer.onNext(something_with_AVURLAsset)
observer.onCompleted()
return Disposables.create()
}
}
func observableFunc2(item: AVURLAsset) -> Observable<Data> {
return Observable.create { observer in
let something_with_data = Data()
observer.onNext(something_with_data)
observer.onCompleted()
return Disposables.create()
}
}
func observableFunc3(_ params: [String:Any]) -> Observable<Any> {
let disposeBag = DisposeBag()
return Observable.create { observer in
RxAlamofire.request(api.sendData(params)).debug().subscribe(
onNext: { reponse in
observer.onCompleted()
},
onError: { error in
observer.onError(customError.theError("error"))
}
)
.addDisposableTo(disposeBag)
return Disposables.create()
}
}
How can I execute these 3 functions sequentially with the return value of func1 to be used on func2 and then when func2 is completed finally run func3.
I hope I made the question clear enough, but then again I'm really new to RxSwift and I don't know if these operations are possible or not.
Here's an example...
Assuming you have the three functions:
func func1() -> Observable<Data1>
func func2(_ data: Data1) -> Observable<Data2>
func func3(_ data: Data2) -> Observable<Data3>
Then you can:
let a = func1()
let b = a.flatMap { func2($0) }
let c = b.flatMap { func3($0) }
or:
let c = func1()
.flatMap { func2($0) }
.flatMap { func3($0) }
That said, your observableFunc3 is quite broken. You need to remove the dispose bag from it. As it stands, the network call will cancel before it starts.
If you really don't want it to emit any values then:
func observableFunc3(_ params: [String:Any]) -> Observable<Void> {
return RxAlamofire.request(api.sendData(params))
.filter { false }
}
The above will emit either a completed or an error but no next values.
Better would be to write it like:
func observableFunc3(_ params: [String:Any]) -> Observable<Void> {
RxAlamofire.request(api.sendData(params))
.map { _ in }
}
The above will emit one next and then a completed or an error. This is better because you can map or flatMap after it to have things happen once it's done.
I have a class like this
// a.swift
public class ComServiceString<Result>: ComService<Result> {
override func execute() {
if (method == Method.GET) {
manager.GET(getUrl(),
parameters: nil,
success: { (operation: AFHTTPRequestOperation!,responseObject: AnyObject!) in
self.onSuccessListener?(self.parseResult(responseObject.description))
},
failure: { (operation: AFHTTPRequestOperation!,error: NSError!) in
println("Error: " + error.localizedDescription)
var errorSd = ComServiceError(error.localizedDescription)
if (operation.response != nil) {
errorSd.errorCode = operation.response.statusCode
}
self.onFailedListener?(errorSd)
}
)
}
}
func parseResult(result: String) -> Result? {
fatalError("This method need to be implemented")
}
}
and I extend it to a new class like this
// b.swift:
public class BranchListService<T>: ComServiceString<BranchListModel> {
override func parseResult(result: String) -> BranchListModel? {
let branchListMode = BranchListModel(stringResult: result)
return branchListMode
}
}
my BranchListModel looks like this
public class BranchListModel {
public var total = 0
public var stringResult = ""
init() {}
init (stringResult result: String) {
self.stringResult = result
if let jsonArray = JSONUtil.stringToJSONArray(result) {
if let resultObject = jsonArray[0] as? NSDictionary {
if let total = resultObject["total"] as? NSString {
self.total = total.integerValue;
}
}
}
}
}
but I got BAD_ACCESS error in this line:
let branchListMode = BranchListModel(stringResult: self.resultString)
inside parseResult function on b.swift. I'm still learning this language with trying to convert my java code to swift. Do you guys know what's wrong from the code?
I think there is a bug of the Swift compiler. I've tested a relatively-simplified code below and that crashed in the same way.
class ResultParser<T> {
func parse(result: String) -> T? { abort() }
}
class MyResultParser<T>: ResultParser<Int> {
override func parse(result: String) -> Int? {
return result.toInt()
}
}
let parser: ResultParser<Int> = MyResultParser<()>()
parser.parse("2525")
At the moment, the Swift compiler cannot correctly treat virtual functions directly receiving and/or returning values, whose type is generic. I guess the compiler regards T as a pointer (object) value whatever an actual substituted type is.
I've found a workaround to do nearly the same thing.
class Box<T> {
let raw: T
init(_ raw: T) { self.raw = raw }
}
class ResultParser<T> {
func parse(result: String) -> Box<T?> { abort() }
}
class MyResultParser<T>: ResultParser<Int> {
override func parse(result: String) -> Box<Int?> {
return Box(result.toInt())
}
}
In the code above, the returned value is wrapped in the Box<>. Box<> is an object, so that works no matter whether or not the compiler can tell value types from object types.