This commit is contained in:
DDIsFriend
2023-08-18 17:28:57 +08:00
commit f0e8a1709d
4282 changed files with 192396 additions and 0 deletions

View File

@@ -0,0 +1,68 @@
//
// KVORepresentable+CoreGraphics.swift
// RxCocoa
//
// Created by Krunoslav Zaher on 11/14/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
#if !os(Linux)
import RxSwift
import CoreGraphics
import Foundation
#if arch(x86_64) || arch(arm64)
let CGRectType = "{CGRect={CGPoint=dd}{CGSize=dd}}"
let CGSizeType = "{CGSize=dd}"
let CGPointType = "{CGPoint=dd}"
#elseif arch(i386) || arch(arm) || os(watchOS)
let CGRectType = "{CGRect={CGPoint=ff}{CGSize=ff}}"
let CGSizeType = "{CGSize=ff}"
let CGPointType = "{CGPoint=ff}"
#endif
extension CGRect : KVORepresentable {
public typealias KVOType = NSValue
/// Constructs self from `NSValue`.
public init?(KVOValue: KVOType) {
if strcmp(KVOValue.objCType, CGRectType) != 0 {
return nil
}
var typedValue = CGRect(x: 0, y: 0, width: 0, height: 0)
KVOValue.getValue(&typedValue)
self = typedValue
}
}
extension CGPoint : KVORepresentable {
public typealias KVOType = NSValue
/// Constructs self from `NSValue`.
public init?(KVOValue: KVOType) {
if strcmp(KVOValue.objCType, CGPointType) != 0 {
return nil
}
var typedValue = CGPoint(x: 0, y: 0)
KVOValue.getValue(&typedValue)
self = typedValue
}
}
extension CGSize : KVORepresentable {
public typealias KVOType = NSValue
/// Constructs self from `NSValue`.
public init?(KVOValue: KVOType) {
if strcmp(KVOValue.objCType, CGSizeType) != 0 {
return nil
}
var typedValue = CGSize(width: 0, height: 0)
KVOValue.getValue(&typedValue)
self = typedValue
}
}
#endif

View File

@@ -0,0 +1,88 @@
//
// KVORepresentable+Swift.swift
// RxCocoa
//
// Created by Krunoslav Zaher on 11/14/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
extension Int : KVORepresentable {
public typealias KVOType = NSNumber
/// Constructs `Self` using KVO value.
public init?(KVOValue: KVOType) {
self.init(KVOValue.int32Value)
}
}
extension Int32 : KVORepresentable {
public typealias KVOType = NSNumber
/// Constructs `Self` using KVO value.
public init?(KVOValue: KVOType) {
self.init(KVOValue.int32Value)
}
}
extension Int64 : KVORepresentable {
public typealias KVOType = NSNumber
/// Constructs `Self` using KVO value.
public init?(KVOValue: KVOType) {
self.init(KVOValue.int64Value)
}
}
extension UInt : KVORepresentable {
public typealias KVOType = NSNumber
/// Constructs `Self` using KVO value.
public init?(KVOValue: KVOType) {
self.init(KVOValue.uintValue)
}
}
extension UInt32 : KVORepresentable {
public typealias KVOType = NSNumber
/// Constructs `Self` using KVO value.
public init?(KVOValue: KVOType) {
self.init(KVOValue.uint32Value)
}
}
extension UInt64 : KVORepresentable {
public typealias KVOType = NSNumber
/// Constructs `Self` using KVO value.
public init?(KVOValue: KVOType) {
self.init(KVOValue.uint64Value)
}
}
extension Bool : KVORepresentable {
public typealias KVOType = NSNumber
/// Constructs `Self` using KVO value.
public init?(KVOValue: KVOType) {
self.init(KVOValue.boolValue)
}
}
extension RawRepresentable where RawValue: KVORepresentable {
/// Constructs `Self` using optional KVO value.
init?(KVOValue: RawValue.KVOType?) {
guard let KVOValue = KVOValue else {
return nil
}
guard let rawValue = RawValue(KVOValue: KVOValue) else {
return nil
}
self.init(rawValue: rawValue)
}
}

View File

@@ -0,0 +1,28 @@
//
// KVORepresentable.swift
// RxCocoa
//
// Created by Krunoslav Zaher on 11/14/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
/// Type that is KVO representable (KVO mechanism can be used to observe it).
public protocol KVORepresentable {
/// Associated KVO type.
associatedtype KVOType
/// Constructs `Self` using KVO value.
init?(KVOValue: KVOType)
}
extension KVORepresentable {
/// Initializes `KVORepresentable` with optional value.
init?(KVOValue: KVOType?) {
guard let KVOValue = KVOValue else {
return nil
}
self.init(KVOValue: KVOValue)
}
}

View File

@@ -0,0 +1,60 @@
//
// NSObject+Rx+KVORepresentable.swift
// RxCocoa
//
// Created by Krunoslav Zaher on 11/14/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
#if !os(Linux)
import Foundation
import RxSwift
/// Key value observing options
public struct KeyValueObservingOptions: OptionSet {
/// Raw value
public let rawValue: UInt
public init(rawValue: UInt) {
self.rawValue = rawValue
}
/// Whether a sequence element should be sent to the observer immediately, before the subscribe method even returns.
public static let initial = KeyValueObservingOptions(rawValue: 1 << 0)
/// Whether to send updated values.
public static let new = KeyValueObservingOptions(rawValue: 1 << 1)
}
extension Reactive where Base: NSObject {
/**
Specialization of generic `observe` method.
This is a special overload because to observe values of some type (for example `Int`), first values of KVO type
need to be observed (`NSNumber`), and then converted to result type.
For more information take a look at `observe` method.
*/
public func observe<Element: KVORepresentable>(_ type: Element.Type, _ keyPath: String, options: KeyValueObservingOptions = [.new, .initial], retainSelf: Bool = true) -> Observable<Element?> {
return self.observe(Element.KVOType.self, keyPath, options: options, retainSelf: retainSelf)
.map(Element.init)
}
}
#if !DISABLE_SWIZZLING && !os(Linux)
// KVO
extension Reactive where Base: NSObject {
/**
Specialization of generic `observeWeakly` method.
For more information take a look at `observeWeakly` method.
*/
public func observeWeakly<Element: KVORepresentable>(_ type: Element.Type, _ keyPath: String, options: KeyValueObservingOptions = [.new, .initial]) -> Observable<Element?> {
return self.observeWeakly(Element.KVOType.self, keyPath, options: options)
.map(Element.init)
}
}
#endif
#endif

View File

@@ -0,0 +1,52 @@
//
// NSObject+Rx+RawRepresentable.swift
// RxCocoa
//
// Created by Krunoslav Zaher on 11/9/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
#if !os(Linux)
import RxSwift
import Foundation
extension Reactive where Base: NSObject {
/**
Specialization of generic `observe` method.
This specialization first observes `KVORepresentable` value and then converts it to `RawRepresentable` value.
It is useful for observing bridged ObjC enum values.
For more information take a look at `observe` method.
*/
public func observe<Element: RawRepresentable>(_ type: Element.Type, _ keyPath: String, options: KeyValueObservingOptions = [.new, .initial], retainSelf: Bool = true) -> Observable<Element?> where Element.RawValue: KVORepresentable {
return self.observe(Element.RawValue.KVOType.self, keyPath, options: options, retainSelf: retainSelf)
.map(Element.init)
}
}
#if !DISABLE_SWIZZLING
// observeWeakly + RawRepresentable
extension Reactive where Base: NSObject {
/**
Specialization of generic `observeWeakly` method.
This specialization first observes `KVORepresentable` value and then converts it to `RawRepresentable` value.
It is useful for observing bridged ObjC enum values.
For more information take a look at `observeWeakly` method.
*/
public func observeWeakly<Element: RawRepresentable>(_ type: Element.Type, _ keyPath: String, options: KeyValueObservingOptions = [.new, .initial]) -> Observable<Element?> where Element.RawValue: KVORepresentable {
return self.observeWeakly(Element.RawValue.KVOType.self, keyPath, options: options)
.map(Element.init)
}
}
#endif
#endif

View File

@@ -0,0 +1,569 @@
//
// NSObject+Rx.swift
// RxCocoa
//
// Created by Krunoslav Zaher on 2/21/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
#if !os(Linux)
import Foundation
import RxSwift
#if SWIFT_PACKAGE && !DISABLE_SWIZZLING && !os(Linux)
import RxCocoaRuntime
#endif
#if !DISABLE_SWIZZLING && !os(Linux)
private var deallocatingSubjectTriggerContext: UInt8 = 0
private var deallocatingSubjectContext: UInt8 = 0
#endif
private var deallocatedSubjectTriggerContext: UInt8 = 0
private var deallocatedSubjectContext: UInt8 = 0
#if !os(Linux)
/**
KVO is a tricky mechanism.
When observing child in a ownership hierarchy, usually retaining observing target is wanted behavior.
When observing parent in a ownership hierarchy, usually retaining target isn't wanter behavior.
KVO with weak references is especially tricky. For it to work, some kind of swizzling is required.
That can be done by
* replacing object class dynamically (like KVO does)
* by swizzling `dealloc` method on all instances for a class.
* some third method ...
Both approaches can fail in certain scenarios:
* problems arise when swizzlers return original object class (like KVO does when nobody is observing)
* Problems can arise because replacing dealloc method isn't atomic operation (get implementation,
set implementation).
Second approach is chosen. It can fail in case there are multiple libraries dynamically trying
to replace dealloc method. In case that isn't the case, it should be ok.
*/
extension Reactive where Base: NSObject {
/**
Observes values on `keyPath` starting from `self` with `options` and retains `self` if `retainSelf` is set.
`observe` is just a simple and performant wrapper around KVO mechanism.
* it can be used to observe paths starting from `self` or from ancestors in ownership graph (`retainSelf = false`)
* it can be used to observe paths starting from descendants in ownership graph (`retainSelf = true`)
* the paths have to consist only of `strong` properties, otherwise you are risking crashing the system by not unregistering KVO observer before dealloc.
If support for weak properties is needed or observing arbitrary or unknown relationships in the
ownership tree, `observeWeakly` is the preferred option.
- parameter type: Optional type hint of the observed sequence elements.
- parameter keyPath: Key path of property names to observe.
- parameter options: KVO mechanism notification options.
- parameter retainSelf: Retains self during observation if set `true`.
- returns: Observable sequence of objects on `keyPath`.
*/
public func observe<Element>(_ type: Element.Type,
_ keyPath: String,
options: KeyValueObservingOptions = [.new, .initial],
retainSelf: Bool = true) -> Observable<Element?> {
KVOObservable(object: self.base, keyPath: keyPath, options: options, retainTarget: retainSelf).asObservable()
}
/**
Observes values at the provided key path using the provided options.
- parameter keyPath: A key path between the object and one of its properties.
- parameter options: Key-value observation options, defaults to `.new` and `.initial`.
- note: When the object is deallocated, a completion event is emitted.
- returns: An observable emitting value changes at the provided key path.
*/
public func observe<Element>(_ keyPath: KeyPath<Base, Element>,
options: NSKeyValueObservingOptions = [.new, .initial]) -> Observable<Element> {
Observable<Element>.create { [weak base] observer in
let observation = base?.observe(keyPath, options: options) { obj, _ in
observer.on(.next(obj[keyPath: keyPath]))
}
return Disposables.create { observation?.invalidate() }
}
.take(until: base.rx.deallocated)
}
}
#endif
#if !DISABLE_SWIZZLING && !os(Linux)
// KVO
extension Reactive where Base: NSObject {
/**
Observes values on `keyPath` starting from `self` with `options` and doesn't retain `self`.
It can be used in all cases where `observe` can be used and additionally
* because it won't retain observed target, it can be used to observe arbitrary object graph whose ownership relation is unknown
* it can be used to observe `weak` properties
**Since it needs to intercept object deallocation process it needs to perform swizzling of `dealloc` method on observed object.**
- parameter type: Optional type hint of the observed sequence elements.
- parameter keyPath: Key path of property names to observe.
- parameter options: KVO mechanism notification options.
- returns: Observable sequence of objects on `keyPath`.
*/
public func observeWeakly<Element>(_ type: Element.Type, _ keyPath: String, options: KeyValueObservingOptions = [.new, .initial]) -> Observable<Element?> {
return observeWeaklyKeyPathFor(self.base, keyPath: keyPath, options: options)
.map { n in
return n as? Element
}
}
}
#endif
// Dealloc
extension Reactive where Base: AnyObject {
/**
Observable sequence of object deallocated events.
After object is deallocated one `()` element will be produced and sequence will immediately complete.
- returns: Observable sequence of object deallocated events.
*/
public var deallocated: Observable<Void> {
return self.synchronized {
if let deallocObservable = objc_getAssociatedObject(self.base, &deallocatedSubjectContext) as? DeallocObservable {
return deallocObservable.subject
}
let deallocObservable = DeallocObservable()
objc_setAssociatedObject(self.base, &deallocatedSubjectContext, deallocObservable, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return deallocObservable.subject
}
}
#if !DISABLE_SWIZZLING && !os(Linux)
/**
Observable sequence of message arguments that completes when object is deallocated.
Each element is produced before message is invoked on target object. `methodInvoked`
exists in case observing of invoked messages is needed.
In case an error occurs sequence will fail with `RxCocoaObjCRuntimeError`.
In case some argument is `nil`, instance of `NSNull()` will be sent.
- returns: Observable sequence of arguments passed to `selector` method.
*/
public func sentMessage(_ selector: Selector) -> Observable<[Any]> {
return self.synchronized {
// in case of dealloc selector replay subject behavior needs to be used
if selector == deallocSelector {
return self.deallocating.map { _ in [] }
}
do {
let proxy: MessageSentProxy = try self.registerMessageInterceptor(selector)
return proxy.messageSent.asObservable()
}
catch let e {
return Observable.error(e)
}
}
}
/**
Observable sequence of message arguments that completes when object is deallocated.
Each element is produced after message is invoked on target object. `sentMessage`
exists in case interception of sent messages before they were invoked is needed.
In case an error occurs sequence will fail with `RxCocoaObjCRuntimeError`.
In case some argument is `nil`, instance of `NSNull()` will be sent.
- returns: Observable sequence of arguments passed to `selector` method.
*/
public func methodInvoked(_ selector: Selector) -> Observable<[Any]> {
return self.synchronized {
// in case of dealloc selector replay subject behavior needs to be used
if selector == deallocSelector {
return self.deallocated.map { _ in [] }
}
do {
let proxy: MessageSentProxy = try self.registerMessageInterceptor(selector)
return proxy.methodInvoked.asObservable()
}
catch let e {
return Observable.error(e)
}
}
}
/**
Observable sequence of object deallocating events.
When `dealloc` message is sent to `self` one `()` element will be produced and after object is deallocated sequence
will immediately complete.
In case an error occurs sequence will fail with `RxCocoaObjCRuntimeError`.
- returns: Observable sequence of object deallocating events.
*/
public var deallocating: Observable<()> {
return self.synchronized {
do {
let proxy: DeallocatingProxy = try self.registerMessageInterceptor(deallocSelector)
return proxy.messageSent.asObservable()
}
catch let e {
return Observable.error(e)
}
}
}
private func registerMessageInterceptor<T: MessageInterceptorSubject>(_ selector: Selector) throws -> T {
let rxSelector = RX_selector(selector)
let selectorReference = RX_reference_from_selector(rxSelector)
let subject: T
if let existingSubject = objc_getAssociatedObject(self.base, selectorReference) as? T {
subject = existingSubject
}
else {
subject = T()
objc_setAssociatedObject(
self.base,
selectorReference,
subject,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
}
if subject.isActive {
return subject
}
var error: NSError?
let targetImplementation = RX_ensure_observing(self.base, selector, &error)
if targetImplementation == nil {
throw error?.rxCocoaErrorForTarget(self.base) ?? RxCocoaError.unknown
}
subject.targetImplementation = targetImplementation!
return subject
}
#endif
}
// MARK: Message interceptors
#if !DISABLE_SWIZZLING && !os(Linux)
private protocol MessageInterceptorSubject: AnyObject {
init()
var isActive: Bool {
get
}
var targetImplementation: IMP { get set }
}
private final class DeallocatingProxy
: MessageInterceptorSubject
, RXDeallocatingObserver {
typealias Element = ()
let messageSent = ReplaySubject<()>.create(bufferSize: 1)
@objc var targetImplementation: IMP = RX_default_target_implementation()
var isActive: Bool {
return self.targetImplementation != RX_default_target_implementation()
}
init() {
}
@objc func deallocating() {
self.messageSent.on(.next(()))
}
deinit {
self.messageSent.on(.completed)
}
}
private final class MessageSentProxy
: MessageInterceptorSubject
, RXMessageSentObserver {
typealias Element = [AnyObject]
let messageSent = PublishSubject<[Any]>()
let methodInvoked = PublishSubject<[Any]>()
@objc var targetImplementation: IMP = RX_default_target_implementation()
var isActive: Bool {
return self.targetImplementation != RX_default_target_implementation()
}
init() {
}
@objc func messageSent(withArguments arguments: [Any]) {
self.messageSent.on(.next(arguments))
}
@objc func methodInvoked(withArguments arguments: [Any]) {
self.methodInvoked.on(.next(arguments))
}
deinit {
self.messageSent.on(.completed)
self.methodInvoked.on(.completed)
}
}
#endif
private final class DeallocObservable {
let subject = ReplaySubject<Void>.create(bufferSize:1)
init() {
}
deinit {
self.subject.on(.next(()))
self.subject.on(.completed)
}
}
// MARK: KVO
#if !os(Linux)
private protocol KVOObservableProtocol {
var target: AnyObject { get }
var keyPath: String { get }
var retainTarget: Bool { get }
var options: KeyValueObservingOptions { get }
}
private final class KVOObserver
: _RXKVOObserver
, Disposable {
typealias Callback = (Any?) -> Void
var retainSelf: KVOObserver?
init(parent: KVOObservableProtocol, callback: @escaping Callback) {
#if TRACE_RESOURCES
_ = Resources.incrementTotal()
#endif
super.init(target: parent.target, retainTarget: parent.retainTarget, keyPath: parent.keyPath, options: parent.options.nsOptions, callback: callback)
self.retainSelf = self
}
override func dispose() {
super.dispose()
self.retainSelf = nil
}
deinit {
#if TRACE_RESOURCES
_ = Resources.decrementTotal()
#endif
}
}
private final class KVOObservable<Element>
: ObservableType
, KVOObservableProtocol {
typealias Element = Element?
unowned var target: AnyObject
var strongTarget: AnyObject?
var keyPath: String
var options: KeyValueObservingOptions
var retainTarget: Bool
init(object: AnyObject, keyPath: String, options: KeyValueObservingOptions, retainTarget: Bool) {
self.target = object
self.keyPath = keyPath
self.options = options
self.retainTarget = retainTarget
if retainTarget {
self.strongTarget = object
}
}
func subscribe<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element? {
let observer = KVOObserver(parent: self) { value in
if value as? NSNull != nil {
observer.on(.next(nil))
return
}
observer.on(.next(value as? Element))
}
return Disposables.create(with: observer.dispose)
}
}
private extension KeyValueObservingOptions {
var nsOptions: NSKeyValueObservingOptions {
var result: UInt = 0
if self.contains(.new) {
result |= NSKeyValueObservingOptions.new.rawValue
}
if self.contains(.initial) {
result |= NSKeyValueObservingOptions.initial.rawValue
}
return NSKeyValueObservingOptions(rawValue: result)
}
}
#endif
#if !DISABLE_SWIZZLING && !os(Linux)
private func observeWeaklyKeyPathFor(_ target: NSObject, keyPath: String, options: KeyValueObservingOptions) -> Observable<AnyObject?> {
let components = keyPath.components(separatedBy: ".").filter { $0 != "self" }
let observable = observeWeaklyKeyPathFor(target, keyPathSections: components, options: options)
.finishWithNilWhenDealloc(target)
if !options.isDisjoint(with: .initial) {
return observable
}
else {
return observable
.skip(1)
}
}
// This should work correctly
// Identifiers can't contain `,`, so the only place where `,` can appear
// is as a delimiter.
// This means there is `W` as element in an array of property attributes.
private func isWeakProperty(_ properyRuntimeInfo: String) -> Bool {
properyRuntimeInfo.range(of: ",W,") != nil
}
private extension ObservableType where Element == AnyObject? {
func finishWithNilWhenDealloc(_ target: NSObject)
-> Observable<AnyObject?> {
let deallocating = target.rx.deallocating
return deallocating
.map { _ in
return Observable.just(nil)
}
.startWith(self.asObservable())
.switchLatest()
}
}
private func observeWeaklyKeyPathFor(
_ target: NSObject,
keyPathSections: [String],
options: KeyValueObservingOptions
) -> Observable<AnyObject?> {
weak var weakTarget: AnyObject? = target
let propertyName = keyPathSections[0]
let remainingPaths = Array(keyPathSections[1..<keyPathSections.count])
let property = class_getProperty(object_getClass(target), propertyName)
if property == nil {
return Observable.error(RxCocoaError.invalidPropertyName(object: target, propertyName: propertyName))
}
let propertyAttributes = property_getAttributes(property!)
// should dealloc hook be in place if week property, or just create strong reference because it doesn't matter
let isWeak = isWeakProperty(propertyAttributes.map(String.init) ?? "")
let propertyObservable = KVOObservable(object: target, keyPath: propertyName, options: options.union(.initial), retainTarget: false) as KVOObservable<AnyObject>
// KVO recursion for value changes
return propertyObservable
.flatMapLatest { (nextTarget: AnyObject?) -> Observable<AnyObject?> in
if nextTarget == nil {
return Observable.just(nil)
}
let nextObject = nextTarget! as? NSObject
let strongTarget: AnyObject? = weakTarget
if nextObject == nil {
return Observable.error(RxCocoaError.invalidObjectOnKeyPath(object: nextTarget!, sourceObject: strongTarget ?? NSNull(), propertyName: propertyName))
}
// if target is alive, then send change
// if it's deallocated, don't send anything
if strongTarget == nil {
return Observable.empty()
}
let nextElementsObservable = keyPathSections.count == 1
? Observable.just(nextTarget)
: observeWeaklyKeyPathFor(nextObject!, keyPathSections: remainingPaths, options: options)
if isWeak {
return nextElementsObservable
.finishWithNilWhenDealloc(nextObject!)
}
else {
return nextElementsObservable
}
}
}
#endif
// MARK: Constants
private let deallocSelector = NSSelectorFromString("dealloc")
// MARK: AnyObject + Reactive
extension Reactive where Base: AnyObject {
func synchronized<T>( _ action: () -> T) -> T {
objc_sync_enter(self.base)
let result = action()
objc_sync_exit(self.base)
return result
}
}
extension Reactive where Base: AnyObject {
/**
Helper to make sure that `Observable` returned from `createCachedObservable` is only created once.
This is important because there is only one `target` and `action` properties on `NSControl` or `UIBarButtonItem`.
*/
func lazyInstanceObservable<T: AnyObject>(_ key: UnsafeRawPointer, createCachedObservable: () -> T) -> T {
if let value = objc_getAssociatedObject(self.base, key) {
return value as! T
}
let observable = createCachedObservable()
objc_setAssociatedObject(self.base, key, observable, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return observable
}
}
#endif

View File

@@ -0,0 +1,31 @@
//
// NotificationCenter+Rx.swift
// RxCocoa
//
// Created by Krunoslav Zaher on 5/2/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
import RxSwift
extension Reactive where Base: NotificationCenter {
/**
Transforms notifications posted to notification center to observable sequence of notifications.
- parameter name: Optional name used to filter notifications.
- parameter object: Optional object used to filter notifications.
- returns: Observable sequence of posted notifications.
*/
public func notification(_ name: Notification.Name?, object: AnyObject? = nil) -> Observable<Notification> {
return Observable.create { [weak object] observer in
let nsObserver = self.base.addObserver(forName: name, object: object, queue: nil) { notification in
observer.on(.next(notification))
}
return Disposables.create {
self.base.removeObserver(nsObserver)
}
}
}
}

View File

@@ -0,0 +1,240 @@
//
// URLSession+Rx.swift
// RxCocoa
//
// Created by Krunoslav Zaher on 3/23/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
import RxSwift
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
/// RxCocoa URL errors.
public enum RxCocoaURLError
: Swift.Error {
/// Unknown error occurred.
case unknown
/// Response is not NSHTTPURLResponse
case nonHTTPResponse(response: URLResponse)
/// Response is not successful. (not in `200 ..< 300` range)
case httpRequestFailed(response: HTTPURLResponse, data: Data?)
/// Deserialization error.
case deserializationError(error: Swift.Error)
}
extension RxCocoaURLError
: CustomDebugStringConvertible {
/// A textual representation of `self`, suitable for debugging.
public var debugDescription: String {
switch self {
case .unknown:
return "Unknown error has occurred."
case let .nonHTTPResponse(response):
return "Response is not NSHTTPURLResponse `\(response)`."
case let .httpRequestFailed(response, _):
return "HTTP request failed with `\(response.statusCode)`."
case let .deserializationError(error):
return "Error during deserialization of the response: \(error)"
}
}
}
private func escapeTerminalString(_ value: String) -> String {
return value.replacingOccurrences(of: "\"", with: "\\\"", options:[], range: nil)
}
private func convertURLRequestToCurlCommand(_ request: URLRequest) -> String {
let method = request.httpMethod ?? "GET"
var returnValue = "curl -X \(method) "
if let httpBody = request.httpBody {
let maybeBody = String(data: httpBody, encoding: String.Encoding.utf8)
if let body = maybeBody {
returnValue += "-d \"\(escapeTerminalString(body))\" "
}
}
for (key, value) in request.allHTTPHeaderFields ?? [:] {
let escapedKey = escapeTerminalString(key as String)
let escapedValue = escapeTerminalString(value as String)
returnValue += "\n -H \"\(escapedKey): \(escapedValue)\" "
}
let URLString = request.url?.absoluteString ?? "<unknown url>"
returnValue += "\n\"\(escapeTerminalString(URLString))\""
returnValue += " -i -v"
return returnValue
}
private func convertResponseToString(_ response: URLResponse?, _ error: NSError?, _ interval: TimeInterval) -> String {
let ms = Int(interval * 1000)
if let response = response as? HTTPURLResponse {
if 200 ..< 300 ~= response.statusCode {
return "Success (\(ms)ms): Status \(response.statusCode)"
}
else {
return "Failure (\(ms)ms): Status \(response.statusCode)"
}
}
if let error = error {
if error.domain == NSURLErrorDomain && error.code == NSURLErrorCancelled {
return "Canceled (\(ms)ms)"
}
return "Failure (\(ms)ms): NSError > \(error)"
}
return "<Unhandled response from server>"
}
extension Reactive where Base: URLSession {
/**
Observable sequence of responses for URL request.
Performing of request starts after observer is subscribed and not after invoking this method.
**URL requests will be performed per subscribed observer.**
Any error during fetching of the response will cause observed sequence to terminate with error.
- parameter request: URL request.
- returns: Observable sequence of URL responses.
*/
public func response(request: URLRequest) -> Observable<(response: HTTPURLResponse, data: Data)> {
return Observable.create { observer in
// smart compiler should be able to optimize this out
let d: Date?
if URLSession.rx.shouldLogRequest(request) {
d = Date()
}
else {
d = nil
}
let task = self.base.dataTask(with: request) { data, response, error in
if URLSession.rx.shouldLogRequest(request) {
let interval = Date().timeIntervalSince(d ?? Date())
print(convertURLRequestToCurlCommand(request))
#if os(Linux)
print(convertResponseToString(response, error.flatMap { $0 as NSError }, interval))
#else
print(convertResponseToString(response, error.map { $0 as NSError }, interval))
#endif
}
guard let response = response, let data = data else {
observer.on(.error(error ?? RxCocoaURLError.unknown))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
observer.on(.error(RxCocoaURLError.nonHTTPResponse(response: response)))
return
}
observer.on(.next((httpResponse, data)))
observer.on(.completed)
}
task.resume()
return Disposables.create(with: task.cancel)
}
}
/**
Observable sequence of response data for URL request.
Performing of request starts after observer is subscribed and not after invoking this method.
**URL requests will be performed per subscribed observer.**
Any error during fetching of the response will cause observed sequence to terminate with error.
If response is not HTTP response with status code in the range of `200 ..< 300`, sequence
will terminate with `(RxCocoaErrorDomain, RxCocoaError.NetworkError)`.
- parameter request: URL request.
- returns: Observable sequence of response data.
*/
public func data(request: URLRequest) -> Observable<Data> {
return self.response(request: request).map { pair -> Data in
if 200 ..< 300 ~= pair.0.statusCode {
return pair.1
}
else {
throw RxCocoaURLError.httpRequestFailed(response: pair.0, data: pair.1)
}
}
}
/**
Observable sequence of response JSON for URL request.
Performing of request starts after observer is subscribed and not after invoking this method.
**URL requests will be performed per subscribed observer.**
Any error during fetching of the response will cause observed sequence to terminate with error.
If response is not HTTP response with status code in the range of `200 ..< 300`, sequence
will terminate with `(RxCocoaErrorDomain, RxCocoaError.NetworkError)`.
If there is an error during JSON deserialization observable sequence will fail with that error.
- parameter request: URL request.
- returns: Observable sequence of response JSON.
*/
public func json(request: URLRequest, options: JSONSerialization.ReadingOptions = []) -> Observable<Any> {
return self.data(request: request).map { data -> Any in
do {
return try JSONSerialization.jsonObject(with: data, options: options)
} catch let error {
throw RxCocoaURLError.deserializationError(error: error)
}
}
}
/**
Observable sequence of response JSON for GET request with `URL`.
Performing of request starts after observer is subscribed and not after invoking this method.
**URL requests will be performed per subscribed observer.**
Any error during fetching of the response will cause observed sequence to terminate with error.
If response is not HTTP response with status code in the range of `200 ..< 300`, sequence
will terminate with `(RxCocoaErrorDomain, RxCocoaError.NetworkError)`.
If there is an error during JSON deserialization observable sequence will fail with that error.
- parameter url: URL of `NSURLRequest` request.
- returns: Observable sequence of response JSON.
*/
public func json(url: Foundation.URL) -> Observable<Any> {
self.json(request: URLRequest(url: url))
}
}
extension Reactive where Base == URLSession {
/// Log URL requests to standard output in curl format.
public static var shouldLogRequest: (URLRequest) -> Bool = { _ in
#if DEBUG
return true
#else
return false
#endif
}
}