diff --git a/Publishers/OnPublisher.swift b/Publishers/OnPublisher.swift new file mode 100644 index 00000000..abf55523 --- /dev/null +++ b/Publishers/OnPublisher.swift @@ -0,0 +1,28 @@ +// +// OnPublisher.swift +// Observable +// +// Created by Zahid on 28/12/2020. +// + +import Foundation +import Combine + +struct UIControlPublisher: Publisher { + + typealias Output = Control + typealias Failure = Never + + let control: Control + let controlEvents: UIControl.Event + + init(control: Control, events: UIControl.Event) { + self.control = control + self.controlEvents = events + } + + func receive(subscriber: S) where S : Subscriber, S.Failure == UIControlPublisher.Failure, S.Input == UIControlPublisher.Output { + let subscription = UIControlSubscription(subscriber: subscriber, control: control, event: controlEvents) + subscriber.receive(subscription: subscription) + } +} diff --git a/Publishers/OnSubscription.swift b/Publishers/OnSubscription.swift new file mode 100644 index 00000000..ccc145d8 --- /dev/null +++ b/Publishers/OnSubscription.swift @@ -0,0 +1,33 @@ +// +// OnSubscription.swift +// Observable +// +// Created by Zahid on 28/12/2020. +// + +import Foundation +import Combine + +final class UIControlSubscription: Subscription where SubscriberType.Input == Control { + private var subscriber: SubscriberType? + private let control: Control + + init(subscriber: SubscriberType, control: Control, event: UIControl.Event) { + self.subscriber = subscriber + self.control = control + control.addTarget(self, action: #selector(eventHandler), for: event) + } + + func request(_ demand: Subscribers.Demand) { + // We do nothing here as we only want to send events when they occur. + // See, for more info: https://developer.apple.com/documentation/combine/subscribers/demand + } + + func cancel() { + subscriber = nil + } + + @objc private func eventHandler() { + _ = subscriber?.receive(control) + } +} diff --git a/README.md b/README.md index 855ff67d..54e8afb1 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,46 @@ SocketIOClient* socket = manager.defaultSocket; ``` +## Combine Support (available: iOS 13.0+ macOS 10.15+ tvOS 13.0+ watchOS 6.0+) + +Socket.IO-client adds support for combine framework. + +```swift +import SocketIO + +var disposeBag: AnyCancellableDisposeBag = [] + +let manager = SocketManager(socketURL: URL(string: "http://localhost:8080")!, config: [.log(true), .compress]) +let socket = manager.defaultSocket + +socket.publisher(on: "message") + .tryMap { output in + output.ack.with("Got your message") + let data = try JSONSerialization.data(withJSONObject: output.data[0], options: []) + let message = try self.decoder.decode(RTCMessage.self, from: data) + return message + }.catch { (error) -> AnyPublisher in + return Fail(error: HTTPError.encodingIssue(description: error.localizedDescription)).eraseToAnyPublisher() + }.sink { (error) in + print(error) // parse error + } receiveValue: { (message) in + print(message) // message received + }.add(to: &disposeBag) + + +socket.publisher(clientEvent: .statusChange) + .map { output -> ConnectionStatus in + guard let id = output.data.last as? Int else { return .notConnected } + return ConnectionStatus(rawValue: id) ?? .notConnected // parsing to local ConnectionStatus Enum + } + .sink {[unowned self] (status) in + + }.add(to: &disposeBag) + + socket.connect() +``` + + ## Features - Supports socket.io 2.0+ (For socket.io 1.0 use v9.x) - Supports binary diff --git a/Source/SocketIO/Publisher/DisposeBag.swift b/Source/SocketIO/Publisher/DisposeBag.swift new file mode 100644 index 00000000..93989396 --- /dev/null +++ b/Source/SocketIO/Publisher/DisposeBag.swift @@ -0,0 +1,25 @@ +// +// DisposeBag.swift +// Socket.IO-Client-Swift +// +// Created by Zahid on 28/12/2020. +// + +import Foundation +import Combine + +@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) +public typealias AnyCancellableDisposeBag = [AnyCancellable] + +// MARK: - AnyCancellable+DisposeBag +/// Adds dispose functionality to `AnyCancellable` class can be can be used to store subscriber tokens. +@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) +extension AnyCancellable { + + /// Add `AnyCancellable` to dispose bag + /// - Parameter bag: `inout` bag for disposal + public func add(to bag:inout AnyCancellableDisposeBag) -> Void { + bag.append(self) + } + +} diff --git a/Source/SocketIO/Publisher/OnPublisher.swift b/Source/SocketIO/Publisher/OnPublisher.swift new file mode 100644 index 00000000..fd93cda7 --- /dev/null +++ b/Source/SocketIO/Publisher/OnPublisher.swift @@ -0,0 +1,30 @@ +// +// OnPublisher.swift +// Observable +// +// Created by Zahid on 28/12/2020. +// + +import Foundation +import Combine + +@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) +public struct OnPublisher: Publisher { + + public typealias Output = OnSocketData + public typealias Failure = Never + + let socket: SocketIOClient + let controlEvent: String + + init(socket: SocketIOClient, event: String) { + self.socket = socket + self.controlEvent = event + } + + + public func receive(subscriber: S) where S : Subscriber, S.Failure == OnPublisher.Failure, S.Input == OnPublisher.Output { + let subscription = OnSubscription(subscriber: subscriber, socket: socket, event: controlEvent) + subscriber.receive(subscription: subscription) + } +} diff --git a/Source/SocketIO/Publisher/OnSubscription.swift b/Source/SocketIO/Publisher/OnSubscription.swift new file mode 100644 index 00000000..11919728 --- /dev/null +++ b/Source/SocketIO/Publisher/OnSubscription.swift @@ -0,0 +1,34 @@ +// +// OnSubscription.swift +// Observable +// +// Created by Zahid on 28/12/2020. +// + +import Foundation +import Combine + +public typealias OnSocketData = (data: [Any], ack: SocketAckEmitter) + +@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) +public final class OnSubscription: Subscription where OnSubscriber.Input == OnSocketData { + private var subscriber: OnSubscriber? + private let socket: SocketIOClient + var uuid: UUID? + init(subscriber: OnSubscriber, socket: SocketIOClient, event: String) { + self.subscriber = subscriber + self.socket = socket + uuid = socket.on(event) { (data, ack) in + _ = subscriber.receive((data, ack)) + } + } + + public func request(_ demand: Subscribers.Demand) { } + + public func cancel() { + guard let ud = self.uuid else { + return + } + socket.off(id: ud) + } +} diff --git a/Source/SocketIO/Publisher/SocketIOClient+Publisher.swift b/Source/SocketIO/Publisher/SocketIOClient+Publisher.swift new file mode 100644 index 00000000..fb61a46c --- /dev/null +++ b/Source/SocketIO/Publisher/SocketIOClient+Publisher.swift @@ -0,0 +1,28 @@ +// +// SocketIOClient+Publisher.swift +// Socket.IO-Client-Swift +// +// Created by Zahid on 28/12/2020. +// + +import Foundation + +@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) +public protocol SocketIOCombineCompatible { + func publisher(on event: String) -> OnPublisher + func publisher(clientEvent event: SocketClientEvent) -> OnPublisher +} + +@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) +extension SocketIOClient: SocketIOCombineCompatible { } + +@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) +extension SocketIOCombineCompatible where Self: SocketIOClient { + public func publisher(on event: String) -> OnPublisher { + return OnPublisher(socket: self, event: event) + } + + public func publisher(clientEvent event: SocketClientEvent) -> OnPublisher { + return OnPublisher(socket: self, event: event.rawValue) + } +}