Socket.io Javascript emit to Swift - swift

I have this Javascript socket.io emit function and I need to write it in Swift, SwiftUI with those instructions from a documentation:
To subscribe and get archiving process events:
subscribe to 'galleryArchive' socket event
emit via socket:
socket.emit("subscribeOnGalleryArchiveInfo", { archiveEventName, galleryId }, data => {});
The script:
socketClient.emit(
'subscribeOnGalleryArchiveInfo',
{ archiveEventName: eventName, galleryId },
(event: GalleryArchiveEvent) => {
const { data } = event;
setArchivingData(data);
}
);
All I did is initialized the Socket viewModel in Swift, but can't seem to recreate this emit function without bunch of errors.
My Code:
class GalleryArchiveSocket: ObservableObject {
#Published var archiveEventName: String
#Published var galleryId: String
var manager: SocketManager?
var socket: SocketIOClient?
init(archiveEventName: String, galleryId: String) {
self.archiveEventName = archiveEventName
self.galleryId = galleryId
guard let apiUrl = AppConfig.rootURL
else {
return
}
manager = SocketManager(
socketURL: apiUrl,
config: [
.extraHeaders(
[
HTTPHeader.authorization.key: HTTPHeader.authorization.value.headerValue
]
),
.log(true),
.compress
]
)
guard let socket = manager?.defaultSocket
else {
return
}
socket.on(clientEvent: .connect) {_, _ in
print("I'm connected")
// Emit function with param ? and receive data ?
}
socket.connect()
}
}

I was able to find a solution with looking into a lot of Socket.io swift examples until I saw someone passes the data like: ["archiveEventName": self.archiveEventName, "galleryId": self.galleryId]
and #workingdog helped with the initial syntax.
socket.on(clientEvent: .connect) {_, _ in
socket.emit("subscribeOnGalleryArchiveInfo", ["archiveEventName": self.archiveEventName, "galleryId": self.galleryId])
socket.on("GalleryArchiveEvent") { data, ack in
print(data)
}
}

Related

Using Combine Publisher with AsyncSequence

I'm currently migrating code that was using Combine Publisher to an AsyncSequence. I previously used this alongside #Published search query that user could type in and now trying to "combine" that search term with AsyncSequence based data source such as following (using values to convert the search query to AsyncSequence as well). However, I'm only seeing the flatMap code being executed once initially.
#MainActor
class FantasyPremierLeagueViewModel: ObservableObject {
#Published var playerList = [Player]()
#Published var query: String = ""
private let repository: FantasyPremierLeagueRepository
init(repository: FantasyPremierLeagueRepository) {
self.repository = repository
Task {
let playerStream = asyncStream(for: repository.playerListNative)
let filteredPlayerStream = $query
.debounce(for: 0.5, scheduler: DispatchQueue.main)
.values
.flatMap { query in
playerStream
.map { $0.filter { uery.isEmpty || $0.name.contains(query) } }
}
.map { $0.sorted { $0.points > $1.points } }
for try await data in filteredPlayerStream {
self.playerList = data
}
}
}
}
Code pushed to branch and can also be viewed in https://github.com/joreilly/FantasyPremierLeague/blob/kmp_native_coroutines/ios/FantasyPremierLeague/FantasyPremierLeague/ViewModel.swift
Ok, looks like this can be done in a much cleaner way using combineLatest() from new Swift Async Algorithms package (https://github.com/apple/swift-async-algorithms).
Task {
let playerStream = asyncStream(for: repository.playerListNative)
.map { $0.sorted { $0.points > $1.points } }
for try await (players, searchQuery) in combineLatest(playerStream, $query.values) {
self.playerList = players
.filter { searchQuery.isEmpty || $0.name.localizedCaseInsensitiveContains(query) }
}
}

Swift Combine with Sync

I am new to combine.... I have a function that has a subscriber, and that function will return a value from the publisher, but the function will return an empty value before waiting for sink to complete.... is there a way I can wait for the sink and assign the value to the function return variable, then return the String back to the caller of this function.
func getState() -> String{
var state = ""
let statesub = API.getCheckedState(employeeId: "342344", user: user!)
statesub
.mapError({ (error) -> APIError in // 5
NSLog(error.errorDescription!)
return error
})
.sink(receiveCompletion: { _ in},
receiveValue:{
NSLog("State : \($0.state)")
state = $0.state
})
.store(in: &tokens)
return state
In other programming languages, such as C#, there is the concept of async/await. You declare a part of your program as asynchronous by the keyword async and wait for the result with the await keyword in a non-blocking manner. However, this concept is not implemented in Swift (even tough it is heavily discussed).
So, is there an alternative in Swift? Yes, using a DispatchSemaphore. Your method should look like:
func getState() -> String {
var state = ""
var sema = DispatchSemaphore(value: 0)
let statesub = API.getCheckedState(employeeId: "342344", user: user!)
statesub
.mapError({ (error) -> APIError in // 5
NSLog(error.errorDescription!)
return error
})
.sink(receiveCompletion: { _ in},
receiveValue: {
defer { sema.signal() }
NSLog("State : \($0.state)")
state = $0.state
})
.store(in: &tokens)
sema.wait()
return state
}

Swift Combine Chunk Operator

I'm trying to create chunks of a stream in Apple's Combine framework.
What I'm going for is something like this:
Stream a:
--1-2-3-----4-5--->
Stream b:
--------0-------0->
a.chunk(whenOutputFrom: b)
-------[1, 2, 3]---[4, 5]-->
Can this be implemented in Combine?
What you are looking for is the buffer operator in the ReactiveX world.
There is no built in buffer operator (in the ReactiveX sense) in Combine. The built-in buffer is seems to be more like a bufferCount in ReactiveX.
I found this answer by Daniel T, which recreates the buffer operator in RxSwift, and also this cheatsheet, which tells you how to port RxSwift to Combine.
However, the answer by Daniel T uses Observable.create, which isn't available in Combine. I looked a bit deeper, and found this other answer, that recreates Observable.create in Combine.
Combining the three things I've found (no pun intended), this is what I came up with:
// -------------------------------------------------
// from https://stackoverflow.com/a/61035663/5133585
struct AnyObserver<Output, Failure: Error> {
let onNext: ((Output) -> Void)
let onError: ((Failure) -> Void)
let onCompleted: (() -> Void)
}
struct Disposable {
let dispose: () -> Void
}
extension AnyPublisher {
static func create(subscribe: #escaping (AnyObserver<Output, Failure>) -> Disposable) -> Self {
let subject = PassthroughSubject<Output, Failure>()
var disposable: Disposable?
return subject
.handleEvents(receiveSubscription: { subscription in
disposable = subscribe(AnyObserver(
onNext: { output in subject.send(output) },
onError: { failure in subject.send(completion: .failure(failure)) },
onCompleted: { subject.send(completion: .finished) }
))
}, receiveCancel: { disposable?.dispose() })
.eraseToAnyPublisher()
}
}
// -------------------------------------------------
// -------------------------------------------------
// adapted from https://stackoverflow.com/a/43413167/5133585
extension Publisher {
/// collects elements from the source sequence until the boundary sequence fires. Then it emits the elements as an array and begins collecting again.
func buffer<T: Publisher, U>(_ boundary: T) -> AnyPublisher<[Output], Failure> where T.Output == U {
return AnyPublisher.create { observer in
var buffer: [Output] = []
let lock = NSRecursiveLock()
let boundaryDisposable = boundary.sink(receiveCompletion: {
_ in
}, receiveValue: {_ in
lock.lock(); defer { lock.unlock() }
observer.onNext(buffer)
buffer = []
})
let disposable = self.sink(receiveCompletion: { (event) in
lock.lock(); defer { lock.unlock() }
switch event {
case .finished:
observer.onNext(buffer)
observer.onCompleted()
case .failure(let error):
observer.onError(error)
buffer = []
}
}) { (element) in
lock.lock(); defer { lock.unlock() }
buffer.append(element)
}
return Disposable {
disposable.cancel()
boundaryDisposable.cancel()
}
}
}
}
// -------------------------------------------------
I think you would be interested in Combine collect() method.
there is variation it it as well such as by time, count or both.
.collect(.byTimeOrCount(DispatchQueue.global(), 1.0, 10))
where we pass context -> for example global queue
time to wait for it such as 1s in above example
and the count of 10 elements.
Use case would look something like this:
let bufferSubject = PassthroughSubject<Int, Never>()
let cancelBag = Set<AnyCancellable>()
let subscriber = bufferSubject.eraseToAnyPublisher()
.collect(.byTimeOrCount(DispatchQueue.global(), 1.0, 10))
.sink { value in
print("🚀🚀 value: \(value)")
}
.store(in: &cancelBag)
be sure to test it :)
bufferSubject.send(1)
bufferSubject.send(2)
bufferSubject.send(3)
...
DispatchQueue.asyncAfter(...) {
bufferSubject.send(4)
bufferSubject.send(5)
bufferSubject.send(6)
}

Future Combine sink does not recieve any values

I want to add a value to Firestore. When finished I want to return the added value. The value does get added to Firestore successfully. However, the value does not go through sink.
This is the function that does not work:
func createPremium(user id: String, isPremium: Bool) -> AnyPublisher<Bool,Never> {
let dic = ["premium":isPremium]
return Future<Bool,Never> { promise in
self.db.collection(self.dbName).document(id).setData(dic, merge: true) { error in
if let error = error {
print(error.localizedDescription)
} else {
/// does get called
promise(.success(isPremium))
}
}
}.eraseToAnyPublisher()
}
I made a test function that works:
func test() -> AnyPublisher<Bool,Never> {
return Future<Bool,Never> { promise in
promise(.success(true))
}.eraseToAnyPublisher()
}
premiumRepository.createPremium(user: userID ?? "1234", isPremium: true)
.sink { receivedValue in
/// does not get called
print(receivedValue)
}.cancel()
test()
.sink { recievedValue in
/// does get called
print("Test", recievedValue)
}.cancel()
Also I have a similar code snippet that works:
func loadExercises(category: Category) -> AnyPublisher<[Exercise], Error> {
let document = store.collection(category.rawValue)
return Future<[Exercise], Error> { promise in
document.getDocuments { documents, error in
if let error = error {
promise(.failure(error))
} else if let documents = documents {
var exercises = [Exercise]()
for document in documents.documents {
do {
let decoded = try FirestoreDecoder().decode(Exercise.self, from: document.data())
exercises.append(decoded)
} catch let error {
promise(.failure(error))
}
}
promise(.success(exercises))
}
}
}.eraseToAnyPublisher()
}
I tried to add a buffer but it did not lead to success.
Try to change/remove .cancel() method on your subscriptions. Seems you subscribe to the publisher, and then immediately cancel the subscription. The better option is to retain and store all your subscriptions in the cancellable set.

Replay last request with RxSwift

I'm creating a networking layer where I inject API provider and call event to the initialize method.
class NetworkingLayer<T: Decodable, E: TargetType> {
var response: Driver<T>
init(provider: MoyaProvider<E>, request: Singal<E>) {
let requestState = request
.flatMapLatest({
provider.rx.request($0).map(Respose<T>.self)
.map { ReqestState.loaded($0) }
.asDriver(onErrorRecover: { error in
return Driver.just(.error(error))
})
.startWith(.loading)
})
.asDriver(onErrorRecover: { fatalError($0.localizedDescription) })
response = requestState
.map({ $0.data?.data })
.filterNil()
}
}
Using them in the following way:
class ViewModel {
let networking: DataNetworkingResponse<[TagItem], ScoutEnpoint>
init(provider: MoyaProvider<Endpoint>, event: Singal<[Int]>) {
let request = event
.map({ Endpoint.getNewItems(prevItemsIds: $0) })
self.networking = NetworkingLayer(provider: provider, request: request)
}
}
All working like charm. But now I have to implement refresh feature. Refresh my last request. I've added this let refresh: Signal<()> = Signal.empty() property to my network layer. But can't understand how to save my last request.
class NetworkingLayer<T: Decodable, E: TargetType> {
let refresh: BehaviorRelay<()> = BehaviorRelay.init(value: ())
...
}
How can I implement refreshing like this?
viewModel.networking.refresh.accept(())
Using a BehaviorRelay is a bad idea because it emits a value as soon as it's subscribed to. This means that your NetworkingLayer object would make a request as soon as it's constructed.
Better would be to pass another Signal into the init method:
init(provider: MoyaProvider<E>, request: Singal<E>, refresh: Signal<Void>)
I don't know Moya but here's how I would have written something like that using URLSession:
enum RequestState<T> {
case loaded(T)
case error(Error)
case loading
}
class NetworkingLayer<T: Decodable> {
let response: Driver<RequestState<T>>
init(request: Signal<URLRequest>, refresh: Signal<Void>, provider: URLSession = URLSession.shared, decoder: JSONDecoder = JSONDecoder()) {
response = Observable.merge(request.asObservable(), refresh.withLatestFrom(request).asObservable())
.flatMapLatest { request in
provider.rx.data(request: request)
.map { try decoder.decode(T.self, from: $0) }
.map { RequestState.loaded($0) }
.startWith(.loading)
.catchError { Observable<RequestState<T>>.just(.error($0)) }
}
.share(replay: 1)
.asDriver(onErrorRecover: { fatalError($0.localizedDescription) })
}
}
I threw the share(replay:) on there so that subsequent subscribers won't make separate network requests, and I made the decoder injectable so the caller can configure it how they want.