// // LinphoneAsyncWrapper.swift // CallKitTutorial // // Created by CD on 13/09/2023. // Copyright © 2023 BelledonneCommunications. All rights reserved. // import Foundation import Combine import linphonesw var coreQueue : DispatchQueue = DispatchQueue(label:"core.queue") var cancellables = Set() // A publisher object that can old one or many LinphoneObject objects. public class LinphoneObjectsPublisher : Publisher { public typealias Output = T public typealias Failure = Error let passThroughSubject = PassthroughSubject() public func receive(subscriber: S) where S : Subscriber, S.Failure == Failure, S.Input == Output { passThroughSubject.receive(subscriber: subscriber) } public func send(completion: Subscribers.Completion) { passThroughSubject.send(completion: completion) } @discardableResult public func postOnMainQueue(onError :@escaping ((Error) -> Void) = {_ in }, receiveValue:@escaping ((Output) -> Void)) -> LinphoneObjectsPublisher { doOnQueue(onError,receiveValue,queue: DispatchQueue.main) return self } @discardableResult public func postOnCoreQueue(onError :@escaping ((Error) -> Void) = {_ in }, receiveValue:@escaping ((Output) -> Void)) -> LinphoneObjectsPublisher { doOnQueue(onError,receiveValue,queue: coreQueue) return self } private func doOnQueue(_ onError :@escaping ((Error) -> Void) = {_ in }, _ receiveValue:@escaping ((Output) -> Void), queue:DispatchQueue) { passThroughSubject.receive(on:queue) .sink { error in onError(error as! Error) } receiveValue: { result in receiveValue(result) }.store(in: &cancellables) } var savedReference : Any? = nil // Used when a reference is needed to avoid object from beeing GCd (example delegate stubs) convenience init (reference: Any) { self.init() savedReference = reference } } public class LinphoneAsyncHelper { static func postOnCoreQueue(lambda : @escaping ()->()) { coreQueue.async { lambda() } } static func postOnMainQueue(lambda : @escaping()->()) { DispatchQueue.main.async { lambda() } } /* // Creates a publisher from the object created by the action passed as parameter // For example if passed a create core call this function will create the LinphoneObject Core on core queue, and created object will be published through the built publisher func createLinphoneObjectWithPublisher(createAction:@escaping()throws -> LinphoneObject ) -> LinphoneObjectsPublisher { let publisher = LinphoneObjectsPublisher() coreQueue.async { do { publisher.passThroughSubject.send(try createAction()) } catch { publisher.send(completion: .failure(error)) } } return publisher } */ } extension Core { // Methods below would generated by a script similar to the one that creates LinphoneWrapper public func createAccountRegistrationStateChangedPublisher() -> LinphoneObjectsPublisher<(core:Core, account:Account, state:RegistrationState, message:String)> { let publisher = LinphoneObjectsPublisher<(core:Core, account:Account, state:RegistrationState, message:String)>() let coreDelegate = CoreDelegateStub ( onAccountRegistrationStateChanged: { (core: Core, account: Account, state: RegistrationState, message: String) in publisher.passThroughSubject.send((core,account,state,message)) }) publisher.savedReference = coreDelegate addDelegate(delegate: coreDelegate) return publisher } public func createOnCallStateChangedPublisher() -> LinphoneObjectsPublisher<(core: Core, call: Call, state: Call.State, message: String)> { let publisher = LinphoneObjectsPublisher<(core: Core, call: Call, state: Call.State, message: String)>() let coreDelegate = CoreDelegateStub ( onCallStateChanged: { (core: Core, call: Call, state: Call.State, message: String) in publisher.passThroughSubject.send((core,call,state,message)) }) publisher.savedReference = coreDelegate addDelegate(delegate: coreDelegate) return publisher } // ... } /* extension Factory { static var linphoneObjectsPublishers = [String:LinphoneObjectsPublisher]() var publisher: LinphoneObjectsPublisher { get { let ref = String(format: "%p", unsafeBitCast(self, to: Int.self)) if (Factory.linphoneObjectsPublishers[ref] == nil) { let publisher = LinphoneObjectsPublisher() Factory.linphoneObjectsPublishers[ref] = publisher } coreQueue.async { Factory.linphoneObjectsPublishers[ref]!.passThroughSubject.send(self) } return Factory.linphoneObjectsPublishers[ref]! } set(newValue) { let ref = String(format: "%p", unsafeBitCast(self, to: Int.self)) Factory.linphoneObjectsPublishers[ref] = newValue } } } extension LinphoneObject { static var linphoneObjectsPublishers = [String:LinphoneObjectsPublisher]() private func refFromSelf() -> String { return String(format: "%p", unsafeBitCast(self, to: Int.self)) } var publisher: LinphoneObjectsPublisher { get { let ref = refFromSelf() if (LinphoneObject.linphoneObjectsPublishers[ref] == nil) { let publisher = LinphoneObjectsPublisher() LinphoneObject.linphoneObjectsPublishers[ref] = publisher } coreQueue.async { LinphoneObject.linphoneObjectsPublishers[ref]!.passThroughSubject.send(self) } return LinphoneObject.linphoneObjectsPublishers[ref]! } set(newValue) { let ref = refFromSelf() LinphoneObject.linphoneObjectsPublishers[ref] = newValue } } } // Could not find a way to put the below inside the generic LinphoneObject extension extension Factory { func postOnCoreQueue(lambda:@escaping ((Factory) -> Void)) { publisher.postOnCoreQueue { result in lambda(result as! Factory) } } func postOnMainQueue(lambda:@escaping ((Factory) -> Void)) { publisher.postOnMainQueue { result in lambda(result as! Factory) } } } extension Core { func postOnCoreQueue(lambda:@escaping ((Core) -> Void)) { publisher.postOnCoreQueue { result in lambda(result as! Core) } } func postOnMainQueue(lambda:@escaping ((Core) -> Void)) { publisher.postOnMainQueue { result in lambda(result as! Core) } } } // .. add extensions to other needed objects, or have this generated in the wrapper */