[Swift] Adding Swift3 generator

* This address #3788
* Contains all changes made in #3823
* Also contains many changes made by @ewanmellor, thanks!
This commit is contained in:
Max Stoliar
2016-10-14 17:27:26 +03:00
parent 03ed192b98
commit 0838523a35
581 changed files with 70459 additions and 3 deletions

View File

@@ -0,0 +1,18 @@
use_frameworks!
source 'https://github.com/CocoaPods/Specs.git'
target 'SwaggerClient' do
pod "PetstoreClient", :path => "../"
target 'SwaggerClientTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |configuration|
configuration.build_settings['SWIFT_VERSION'] = "3.0"
end
end
end

View File

@@ -0,0 +1,22 @@
PODS:
- Alamofire (4.0.0)
- PetstoreClient (0.0.1):
- Alamofire (~> 4.0)
- RxSwift (~> 3.0.0-beta.1)
- RxSwift (3.0.0-beta.1)
DEPENDENCIES:
- PetstoreClient (from `../`)
EXTERNAL SOURCES:
PetstoreClient:
:path: ../
SPEC CHECKSUMS:
Alamofire: fef59f00388f267e52d9b432aa5d93dc97190f14
PetstoreClient: a58edc9541bf0e2a0a7f8464907f26c9b7bafe74
RxSwift: 0823e8d7969c23bfa9ddfb2afa4881e424a1a710
PODFILE CHECKSUM: da9f5a7ad6086f2c7abb73cf2c35cefce04a9a30
COCOAPODS: 1.0.1

View File

@@ -0,0 +1,19 @@
Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,450 @@
//
// AFError.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// `AFError` is the error type returned by Alamofire. It encompasses a few different types of errors, each with
/// their own associated reasons.
///
/// - invalidURL: Returned when a `URLConvertible` type fails to create a valid `URL`.
/// - parameterEncodingFailed: Returned when a parameter encoding object throws an error during the encoding process.
/// - multipartEncodingFailed: Returned when some step in the multipart encoding process fails.
/// - responseValidationFailed: Returned when a `validate()` call fails.
/// - responseSerializationFailed: Returned when a response serializer encounters an error in the serialization process.
public enum AFError: Error {
/// The underlying reason the parameter encoding error occurred.
///
/// - missingURL: The URL request did not have a URL to encode.
/// - jsonEncodingFailed: JSON serialization failed with an underlying system error during the
/// encoding process.
/// - propertyListEncodingFailed: Property list serialization failed with an underlying system error during
/// encoding process.
public enum ParameterEncodingFailureReason {
case missingURL
case jsonEncodingFailed(error: Error)
case propertyListEncodingFailed(error: Error)
}
/// The underlying reason the multipart encoding error occurred.
///
/// - bodyPartURLInvalid: The `fileURL` provided for reading an encodable body part isn't a
/// file URL.
/// - bodyPartFilenameInvalid: The filename of the `fileURL` provided has either an empty
/// `lastPathComponent` or `pathExtension.
/// - bodyPartFileNotReachable: The file at the `fileURL` provided was not reachable.
/// - bodyPartFileNotReachableWithError: Attempting to check the reachability of the `fileURL` provided threw
/// an error.
/// - bodyPartFileIsDirectory: The file at the `fileURL` provided is actually a directory.
/// - bodyPartFileSizeNotAvailable: The size of the file at the `fileURL` provided was not returned by
/// the system.
/// - bodyPartFileSizeQueryFailedWithError: The attempt to find the size of the file at the `fileURL` provided
/// threw an error.
/// - bodyPartInputStreamCreationFailed: An `InputStream` could not be created for the provided `fileURL`.
/// - outputStreamCreationFailed: An `OutputStream` could not be created when attempting to write the
/// encoded data to disk.
/// - outputStreamFileAlreadyExists: The encoded body data could not be writtent disk because a file
/// already exists at the provided `fileURL`.
/// - outputStreamURLInvalid: The `fileURL` provided for writing the encoded body data to disk is
/// not a file URL.
/// - outputStreamWriteFailed: The attempt to write the encoded body data to disk failed with an
/// underlying error.
/// - inputStreamReadFailed: The attempt to read an encoded body part `InputStream` failed with
/// underlying system error.
public enum MultipartEncodingFailureReason {
case bodyPartURLInvalid(url: URL)
case bodyPartFilenameInvalid(in: URL)
case bodyPartFileNotReachable(at: URL)
case bodyPartFileNotReachableWithError(atURL: URL, error: Error)
case bodyPartFileIsDirectory(at: URL)
case bodyPartFileSizeNotAvailable(at: URL)
case bodyPartFileSizeQueryFailedWithError(forURL: URL, error: Error)
case bodyPartInputStreamCreationFailed(for: URL)
case outputStreamCreationFailed(for: URL)
case outputStreamFileAlreadyExists(at: URL)
case outputStreamURLInvalid(url: URL)
case outputStreamWriteFailed(error: Error)
case inputStreamReadFailed(error: Error)
}
/// The underlying reason the response validation error occurred.
///
/// - dataFileNil: The data file containing the server response did not exist.
/// - dataFileReadFailed: The data file containing the server response could not be read.
/// - missingContentType: The response did not contain a `Content-Type` and the `acceptableContentTypes`
/// provided did not contain wildcard type.
/// - unacceptableContentType: The response `Content-Type` did not match any type in the provided
/// `acceptableContentTypes`.
/// - unacceptableStatusCode: The response status code was not acceptable.
public enum ResponseValidationFailureReason {
case dataFileNil
case dataFileReadFailed(at: URL)
case missingContentType(acceptableContentTypes: [String])
case unacceptableContentType(acceptableContentTypes: [String], responseContentType: String)
case unacceptableStatusCode(code: Int)
}
/// The underlying reason the response serialization error occurred.
///
/// - inputDataNil: The server response contained no data.
/// - inputDataNilOrZeroLength: The server response contained no data or the data was zero length.
/// - inputFileNil: The file containing the server response did not exist.
/// - inputFileReadFailed: The file containing the server response could not be read.
/// - stringSerializationFailed: String serialization failed using the provided `String.Encoding`.
/// - jsonSerializationFailed: JSON serialization failed with an underlying system error.
/// - propertyListSerializationFailed: Property list serialization failed with an underlying system error.
public enum ResponseSerializationFailureReason {
case inputDataNil
case inputDataNilOrZeroLength
case inputFileNil
case inputFileReadFailed(at: URL)
case stringSerializationFailed(encoding: String.Encoding)
case jsonSerializationFailed(error: Error)
case propertyListSerializationFailed(error: Error)
}
case invalidURL(url: URLConvertible)
case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
case responseValidationFailed(reason: ResponseValidationFailureReason)
case responseSerializationFailed(reason: ResponseSerializationFailureReason)
}
// MARK: - Error Booleans
extension AFError {
/// Returns whether the AFError is an invalid URL error.
public var isInvalidURLError: Bool {
if case .invalidURL = self { return true }
return false
}
/// Returns whether the AFError is a parameter encoding error. When `true`, the `underlyingError` property will
/// contain the associated value.
public var isParameterEncodingError: Bool {
if case .multipartEncodingFailed = self { return true }
return false
}
/// Returns whether the AFError is a multipart encoding error. When `true`, the `url` and `underlyingError` properties
/// will contain the associated values.
public var isMultipartEncodingError: Bool {
if case .multipartEncodingFailed = self { return true }
return false
}
/// Returns whether the `AFError` is a response validation error. When `true`, the `acceptableContentTypes`,
/// `responseContentType`, and `responseCode` properties will contain the associated values.
public var isResponseValidationError: Bool {
if case .responseValidationFailed = self { return true }
return false
}
/// Returns whether the `AFError` is a response serialization error. When `true`, the `failedStringEncoding` and
/// `underlyingError` properties will contain the associated values.
public var isResponseSerializationError: Bool {
if case .responseSerializationFailed = self { return true }
return false
}
}
// MARK: - Convenience Properties
extension AFError {
/// The `URLConvertible` associated with the error.
public var urlConvertible: URLConvertible? {
switch self {
case .invalidURL(let url):
return url
default:
return nil
}
}
/// The `URL` associated with the error.
public var url: URL? {
switch self {
case .multipartEncodingFailed(let reason):
return reason.url
default:
return nil
}
}
/// The `Error` returned by a system framework associated with a `.parameterEncodingFailed`,
/// `.multipartEncodingFailed` or `.responseSerializationFailed` error.
public var underlyingError: Error? {
switch self {
case .parameterEncodingFailed(let reason):
return reason.underlyingError
case .multipartEncodingFailed(let reason):
return reason.underlyingError
case .responseSerializationFailed(let reason):
return reason.underlyingError
default:
return nil
}
}
/// The acceptable `Content-Type`s of a `.responseValidationFailed` error.
public var acceptableContentTypes: [String]? {
switch self {
case .responseValidationFailed(let reason):
return reason.acceptableContentTypes
default:
return nil
}
}
/// The response `Content-Type` of a `.responseValidationFailed` error.
public var responseContentType: String? {
switch self {
case .responseValidationFailed(let reason):
return reason.responseContentType
default:
return nil
}
}
/// The response code of a `.responseValidationFailed` error.
public var responseCode: Int? {
switch self {
case .responseValidationFailed(let reason):
return reason.responseCode
default:
return nil
}
}
/// The `String.Encoding` associated with a failed `.stringResponse()` call.
public var failedStringEncoding: String.Encoding? {
switch self {
case .responseSerializationFailed(let reason):
return reason.failedStringEncoding
default:
return nil
}
}
}
extension AFError.ParameterEncodingFailureReason {
var underlyingError: Error? {
switch self {
case .jsonEncodingFailed(let error), .propertyListEncodingFailed(let error):
return error
default:
return nil
}
}
}
extension AFError.MultipartEncodingFailureReason {
var url: URL? {
switch self {
case .bodyPartURLInvalid(let url), .bodyPartFilenameInvalid(let url), .bodyPartFileNotReachable(let url),
.bodyPartFileIsDirectory(let url), .bodyPartFileSizeNotAvailable(let url),
.bodyPartInputStreamCreationFailed(let url), .outputStreamCreationFailed(let url),
.outputStreamFileAlreadyExists(let url), .outputStreamURLInvalid(let url),
.bodyPartFileNotReachableWithError(let url, _), .bodyPartFileSizeQueryFailedWithError(let url, _):
return url
default:
return nil
}
}
var underlyingError: Error? {
switch self {
case .bodyPartFileNotReachableWithError(_, let error), .bodyPartFileSizeQueryFailedWithError(_, let error),
.outputStreamWriteFailed(let error), .inputStreamReadFailed(let error):
return error
default:
return nil
}
}
}
extension AFError.ResponseValidationFailureReason {
var acceptableContentTypes: [String]? {
switch self {
case .missingContentType(let types), .unacceptableContentType(let types, _):
return types
default:
return nil
}
}
var responseContentType: String? {
switch self {
case .unacceptableContentType(_, let reponseType):
return reponseType
default:
return nil
}
}
var responseCode: Int? {
switch self {
case .unacceptableStatusCode(let code):
return code
default:
return nil
}
}
}
extension AFError.ResponseSerializationFailureReason {
var failedStringEncoding: String.Encoding? {
switch self {
case .stringSerializationFailed(let encoding):
return encoding
default:
return nil
}
}
var underlyingError: Error? {
switch self {
case .jsonSerializationFailed(let error), .propertyListSerializationFailed(let error):
return error
default:
return nil
}
}
}
// MARK: - Error Descriptions
extension AFError: LocalizedError {
public var errorDescription: String? {
switch self {
case .invalidURL(let url):
return "URL is not valid: \(url)"
case .parameterEncodingFailed(let reason):
return reason.localizedDescription
case .multipartEncodingFailed(let reason):
return reason.localizedDescription
case .responseValidationFailed(let reason):
return reason.localizedDescription
case .responseSerializationFailed(let reason):
return reason.localizedDescription
}
}
}
extension AFError.ParameterEncodingFailureReason {
var localizedDescription: String {
switch self {
case .missingURL:
return "URL request to encode was missing a URL"
case .jsonEncodingFailed(let error):
return "JSON could not be encoded because of error:\n\(error.localizedDescription)"
case .propertyListEncodingFailed(let error):
return "PropertyList could not be encoded because of error:\n\(error.localizedDescription)"
}
}
}
extension AFError.MultipartEncodingFailureReason {
var localizedDescription: String {
switch self {
case .bodyPartURLInvalid(let url):
return "The URL provided is not a file URL: \(url)"
case .bodyPartFilenameInvalid(let url):
return "The URL provided does not have a valid filename: \(url)"
case .bodyPartFileNotReachable(let url):
return "The URL provided is not reachable: \(url)"
case .bodyPartFileNotReachableWithError(let url, let error):
return (
"The system returned an error while checking the provided URL for " +
"reachability.\nURL: \(url)\nError: \(error)"
)
case .bodyPartFileIsDirectory(let url):
return "The URL provided is a directory: \(url)"
case .bodyPartFileSizeNotAvailable(let url):
return "Could not fetch the file size from the provided URL: \(url)"
case .bodyPartFileSizeQueryFailedWithError(let url, let error):
return (
"The system returned an error while attempting to fetch the file size from the " +
"provided URL.\nURL: \(url)\nError: \(error)"
)
case .bodyPartInputStreamCreationFailed(let url):
return "Failed to create an InputStream for the provided URL: \(url)"
case .outputStreamCreationFailed(let url):
return "Failed to create an OutputStream for URL: \(url)"
case .outputStreamFileAlreadyExists(let url):
return "A file already exists at the provided URL: \(url)"
case .outputStreamURLInvalid(let url):
return "The provided OutputStream URL is invalid: \(url)"
case .outputStreamWriteFailed(let error):
return "OutputStream write failed with error: \(error)"
case .inputStreamReadFailed(let error):
return "InputStream read failed with error: \(error)"
}
}
}
extension AFError.ResponseSerializationFailureReason {
var localizedDescription: String {
switch self {
case .inputDataNil:
return "Response could not be serialized, input data was nil."
case .inputDataNilOrZeroLength:
return "Response could not be serialized, input data was nil or zero length."
case .inputFileNil:
return "Response could not be serialized, input file was nil."
case .inputFileReadFailed(let url):
return "Response could not be serialized, input file could not be read: \(url)."
case .stringSerializationFailed(let encoding):
return "String could not be serialized with encoding: \(encoding)."
case .jsonSerializationFailed(let error):
return "JSON could not be serialized because of error:\n\(error.localizedDescription)"
case .propertyListSerializationFailed(let error):
return "PropertyList could not be serialized because of error:\n\(error.localizedDescription)"
}
}
}
extension AFError.ResponseValidationFailureReason {
var localizedDescription: String {
switch self {
case .dataFileNil:
return "Response could not be validated, data file was nil."
case .dataFileReadFailed(let url):
return "Response could not be validated, data file could not be read: \(url)."
case .missingContentType(let types):
return (
"Response Content-Type was missing and acceptable content types " +
"(\(types.joined(separator: ","))) do not match \"*/*\"."
)
case .unacceptableContentType(let acceptableTypes, let responseType):
return (
"Response Content-Type \"\(responseType)\" does not match any acceptable types: " +
"\(acceptableTypes.joined(separator: ","))."
)
case .unacceptableStatusCode(let code):
return "Response status code was unacceptable: \(code)."
}
}
}

View File

@@ -0,0 +1,456 @@
//
// Alamofire.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Types adopting the `URLConvertible` protocol can be used to construct URLs, which are then used to construct
/// URL requests.
public protocol URLConvertible {
/// Returns a URL that conforms to RFC 2396 or throws an `Error`.
///
/// - throws: An `Error` if the type cannot be converted to a `URL`.
///
/// - returns: A URL or throws an `Error`.
func asURL() throws -> URL
}
extension String: URLConvertible {
/// Returns a URL if `self` represents a valid URL string that conforms to RFC 2396 or throws an `AFError`.
///
/// - throws: An `AFError.invalidURL` if `self` is not a valid URL string.
///
/// - returns: A URL or throws an `AFError`.
public func asURL() throws -> URL {
guard let url = URL(string: self) else { throw AFError.invalidURL(url: self) }
return url
}
}
extension URL: URLConvertible {
/// Returns self.
public func asURL() throws -> URL { return self }
}
extension URLComponents: URLConvertible {
/// Returns a URL if `url` is not nil, otherise throws an `Error`.
///
/// - throws: An `AFError.invalidURL` if `url` is `nil`.
///
/// - returns: A URL or throws an `AFError`.
public func asURL() throws -> URL {
guard let url = url else { throw AFError.invalidURL(url: self) }
return url
}
}
// MARK: -
/// Types adopting the `URLRequestConvertible` protocol can be used to construct URL requests.
public protocol URLRequestConvertible {
/// Returns a URL request or throws if an `Error` was encountered.
///
/// - throws: An `Error` if the underlying `URLRequest` is `nil`.
///
/// - returns: A URL request.
func asURLRequest() throws -> URLRequest
}
extension URLRequestConvertible {
/// The URL request.
public var urlRequest: URLRequest? { return try? asURLRequest() }
}
extension URLRequest: URLRequestConvertible {
/// Returns a URL request or throws if an `Error` was encountered.
public func asURLRequest() throws -> URLRequest { return self }
}
// MARK: -
extension URLRequest {
/// Creates an instance with the specified `method`, `urlString` and `headers`.
///
/// - parameter url: The URL.
/// - parameter method: The HTTP method.
/// - parameter headers: The HTTP headers. `nil` by default.
///
/// - returns: The new `URLRequest` instance.
public init(url: URLConvertible, method: HTTPMethod, headers: HTTPHeaders? = nil) throws {
let url = try url.asURL()
self.init(url: url)
httpMethod = method.rawValue
if let headers = headers {
for (headerField, headerValue) in headers {
setValue(headerValue, forHTTPHeaderField: headerField)
}
}
}
func adapt(using adapter: RequestAdapter?) throws -> URLRequest {
guard let adapter = adapter else { return self }
return try adapter.adapt(self)
}
}
// MARK: - Data Request
/// Creates a `DataRequest` using the default `SessionManager` to retrieve the contents of the specified `url`,
/// `method`, `parameters`, `encoding` and `headers`.
///
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.get` by default.
/// - parameter parameters: The parameters. `nil` by default.
/// - parameter encoding: The parameter encoding. `URLEncoding.default` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
///
/// - returns: The created `DataRequest`.
@discardableResult
public func request(
_ url: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil)
-> DataRequest
{
return SessionManager.default.request(
url,
method: method,
parameters: parameters,
encoding: encoding,
headers: headers
)
}
/// Creates a `DataRequest` using the default `SessionManager` to retrieve the contents of a URL based on the
/// specified `urlRequest`.
///
/// - parameter urlRequest: The URL request
///
/// - returns: The created `DataRequest`.
@discardableResult
public func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
return SessionManager.default.request(urlRequest)
}
// MARK: - Download Request
// MARK: URL Request
/// Creates a `DownloadRequest` using the default `SessionManager` to retrieve the contents of the specified `url`,
/// `method`, `parameters`, `encoding`, `headers` and save them to the `destination`.
///
/// If `destination` is not specified, the contents will remain in the temporary location determined by the
/// underlying URL session.
///
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.get` by default.
/// - parameter parameters: The parameters. `nil` by default.
/// - parameter encoding: The parameter encoding. `URLEncoding.default` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
/// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.
///
/// - returns: The created `DownloadRequest`.
@discardableResult
public func download(
_ url: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil,
to destination: DownloadRequest.DownloadFileDestination? = nil)
-> DownloadRequest
{
return SessionManager.default.download(
url,
method: method,
parameters: parameters,
encoding: encoding,
headers: headers,
to: destination
)
}
/// Creates a `DownloadRequest` using the default `SessionManager` to retrieve the contents of a URL based on the
/// specified `urlRequest` and save them to the `destination`.
///
/// If `destination` is not specified, the contents will remain in the temporary location determined by the
/// underlying URL session.
///
/// - parameter urlRequest: The URL request.
/// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.
///
/// - returns: The created `DownloadRequest`.
@discardableResult
public func download(
_ urlRequest: URLRequestConvertible,
to destination: DownloadRequest.DownloadFileDestination? = nil)
-> DownloadRequest
{
return SessionManager.default.download(urlRequest, to: destination)
}
// MARK: Resume Data
/// Creates a `DownloadRequest` using the default `SessionManager` from the `resumeData` produced from a
/// previous request cancellation to retrieve the contents of the original request and save them to the `destination`.
///
/// If `destination` is not specified, the contents will remain in the temporary location determined by the
/// underlying URL session.
///
/// - parameter resumeData: The resume data. This is an opaque data blob produced by `URLSessionDownloadTask`
/// when a task is cancelled. See `URLSession -downloadTask(withResumeData:)` for additional
/// information.
/// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.
///
/// - returns: The created `DownloadRequest`.
@discardableResult
public func download(
resumingWith resumeData: Data,
to destination: DownloadRequest.DownloadFileDestination? = nil)
-> DownloadRequest
{
return SessionManager.default.download(resumingWith: resumeData, to: destination)
}
// MARK: - Upload Request
// MARK: File
/// Creates an `UploadRequest` using the default `SessionManager` from the specified `url`, `method` and `headers`
/// for uploading the `file`.
///
/// - parameter file: The file to upload.
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.post` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
///
/// - returns: The created `UploadRequest`.
@discardableResult
public func upload(
_ fileURL: URL,
to url: URLConvertible,
method: HTTPMethod = .post,
headers: HTTPHeaders? = nil)
-> UploadRequest
{
return SessionManager.default.upload(fileURL, to: url, method: method, headers: headers)
}
/// Creates a `UploadRequest` using the default `SessionManager` from the specified `urlRequest` for
/// uploading the `file`.
///
/// - parameter file: The file to upload.
/// - parameter urlRequest: The URL request.
///
/// - returns: The created `UploadRequest`.
@discardableResult
public func upload(_ fileURL: URL, with urlRequest: URLRequestConvertible) -> UploadRequest {
return SessionManager.default.upload(fileURL, with: urlRequest)
}
// MARK: Data
/// Creates an `UploadRequest` using the default `SessionManager` from the specified `url`, `method` and `headers`
/// for uploading the `data`.
///
/// - parameter data: The data to upload.
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.post` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
///
/// - returns: The created `UploadRequest`.
@discardableResult
public func upload(
_ data: Data,
to url: URLConvertible,
method: HTTPMethod = .post,
headers: HTTPHeaders? = nil)
-> UploadRequest
{
return SessionManager.default.upload(data, to: url, method: method, headers: headers)
}
/// Creates an `UploadRequest` using the default `SessionManager` from the specified `urlRequest` for
/// uploading the `data`.
///
/// - parameter data: The data to upload.
/// - parameter urlRequest: The URL request.
///
/// - returns: The created `UploadRequest`.
@discardableResult
public func upload(_ data: Data, with urlRequest: URLRequestConvertible) -> UploadRequest {
return SessionManager.default.upload(data, with: urlRequest)
}
// MARK: InputStream
/// Creates an `UploadRequest` using the default `SessionManager` from the specified `url`, `method` and `headers`
/// for uploading the `stream`.
///
/// - parameter stream: The stream to upload.
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.post` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
///
/// - returns: The created `UploadRequest`.
@discardableResult
public func upload(
_ stream: InputStream,
to url: URLConvertible,
method: HTTPMethod = .post,
headers: HTTPHeaders? = nil)
-> UploadRequest
{
return SessionManager.default.upload(stream, to: url, method: method, headers: headers)
}
/// Creates an `UploadRequest` using the default `SessionManager` from the specified `urlRequest` for
/// uploading the `stream`.
///
/// - parameter urlRequest: The URL request.
/// - parameter stream: The stream to upload.
///
/// - returns: The created `UploadRequest`.
@discardableResult
public func upload(_ stream: InputStream, with urlRequest: URLRequestConvertible) -> UploadRequest {
return SessionManager.default.upload(stream, with: urlRequest)
}
// MARK: MultipartFormData
/// Encodes `multipartFormData` using `encodingMemoryThreshold` with the default `SessionManager` and calls
/// `encodingCompletion` with new `UploadRequest` using the `url`, `method` and `headers`.
///
/// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
/// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
/// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
/// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
/// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
/// used for larger payloads such as video content.
///
/// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
/// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
/// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
/// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
/// technique was used.
///
/// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
/// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
/// `multipartFormDataEncodingMemoryThreshold` by default.
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.post` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
/// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
public func upload(
multipartFormData: @escaping (MultipartFormData) -> Void,
usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
to url: URLConvertible,
method: HTTPMethod = .post,
headers: HTTPHeaders? = nil,
encodingCompletion: ((SessionManager.MultipartFormDataEncodingResult) -> Void)?)
{
return SessionManager.default.upload(
multipartFormData: multipartFormData,
usingThreshold: encodingMemoryThreshold,
to: url,
method: method,
headers: headers,
encodingCompletion: encodingCompletion
)
}
/// Encodes `multipartFormData` using `encodingMemoryThreshold` and the default `SessionManager` and
/// calls `encodingCompletion` with new `UploadRequest` using the `urlRequest`.
///
/// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
/// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
/// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
/// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
/// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
/// used for larger payloads such as video content.
///
/// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
/// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
/// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
/// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
/// technique was used.
///
/// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
/// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
/// `multipartFormDataEncodingMemoryThreshold` by default.
/// - parameter urlRequest: The URL request.
/// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
public func upload(
multipartFormData: @escaping (MultipartFormData) -> Void,
usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
with urlRequest: URLRequestConvertible,
encodingCompletion: ((SessionManager.MultipartFormDataEncodingResult) -> Void)?)
{
return SessionManager.default.upload(
multipartFormData: multipartFormData,
usingThreshold: encodingMemoryThreshold,
with: urlRequest,
encodingCompletion: encodingCompletion
)
}
#if !os(watchOS)
// MARK: - Stream Request
// MARK: Hostname and Port
/// Creates a `StreamRequest` using the default `SessionManager` for bidirectional streaming with the `hostname`
/// and `port`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter hostName: The hostname of the server to connect to.
/// - parameter port: The port of the server to connect to.
///
/// - returns: The created `StreamRequest`.
@discardableResult
public func stream(withHostName hostName: String, port: Int) -> StreamRequest {
return SessionManager.default.stream(withHostName: hostName, port: port)
}
// MARK: NetService
/// Creates a `StreamRequest` using the default `SessionManager` for bidirectional streaming with the `netService`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter netService: The net service used to identify the endpoint.
///
/// - returns: The created `StreamRequest`.
@discardableResult
public func stream(with netService: NetService) -> StreamRequest {
return SessionManager.default.stream(with: netService)
}
#endif

View File

@@ -0,0 +1,42 @@
//
// DispatchQueue+Alamofire.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Dispatch
extension DispatchQueue {
static var userInteractive: DispatchQueue { return DispatchQueue.global(qos: .userInteractive) }
static var userInitiated: DispatchQueue { return DispatchQueue.global(qos: .userInitiated) }
static var utility: DispatchQueue { return DispatchQueue.global(qos: .utility) }
static var background: DispatchQueue { return DispatchQueue.global(qos: .background) }
func after(_ delay: TimeInterval, execute closure: @escaping () -> Void) {
asyncAfter(deadline: .now() + delay, execute: closure)
}
func syncResult<T>(_ closure: () -> T) -> T {
var result: T!
sync { result = closure() }
return result
}
}

View File

@@ -0,0 +1,581 @@
//
// MultipartFormData.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
#if os(iOS) || os(watchOS) || os(tvOS)
import MobileCoreServices
#elseif os(OSX)
import CoreServices
#endif
/// Constructs `multipart/form-data` for uploads within an HTTP or HTTPS body. There are currently two ways to encode
/// multipart form data. The first way is to encode the data directly in memory. This is very efficient, but can lead
/// to memory issues if the dataset is too large. The second way is designed for larger datasets and will write all the
/// data to a single file on disk with all the proper boundary segmentation. The second approach MUST be used for
/// larger datasets such as video content, otherwise your app may run out of memory when trying to encode the dataset.
///
/// For more information on `multipart/form-data` in general, please refer to the RFC-2388 and RFC-2045 specs as well
/// and the w3 form documentation.
///
/// - https://www.ietf.org/rfc/rfc2388.txt
/// - https://www.ietf.org/rfc/rfc2045.txt
/// - https://www.w3.org/TR/html401/interact/forms.html#h-17.13
open class MultipartFormData {
// MARK: - Helper Types
struct EncodingCharacters {
static let crlf = "\r\n"
}
struct BoundaryGenerator {
enum BoundaryType {
case initial, encapsulated, final
}
static func randomBoundary() -> String {
return String(format: "alamofire.boundary.%08x%08x", arc4random(), arc4random())
}
static func boundaryData(forBoundaryType boundaryType: BoundaryType, boundary: String) -> Data {
let boundaryText: String
switch boundaryType {
case .initial:
boundaryText = "--\(boundary)\(EncodingCharacters.crlf)"
case .encapsulated:
boundaryText = "\(EncodingCharacters.crlf)--\(boundary)\(EncodingCharacters.crlf)"
case .final:
boundaryText = "\(EncodingCharacters.crlf)--\(boundary)--\(EncodingCharacters.crlf)"
}
return boundaryText.data(using: String.Encoding.utf8, allowLossyConversion: false)!
}
}
class BodyPart {
let headers: HTTPHeaders
let bodyStream: InputStream
let bodyContentLength: UInt64
var hasInitialBoundary = false
var hasFinalBoundary = false
init(headers: HTTPHeaders, bodyStream: InputStream, bodyContentLength: UInt64) {
self.headers = headers
self.bodyStream = bodyStream
self.bodyContentLength = bodyContentLength
}
}
// MARK: - Properties
/// The `Content-Type` header value containing the boundary used to generate the `multipart/form-data`.
open var contentType: String { return "multipart/form-data; boundary=\(boundary)" }
/// The content length of all body parts used to generate the `multipart/form-data` not including the boundaries.
public var contentLength: UInt64 { return bodyParts.reduce(0) { $0 + $1.bodyContentLength } }
/// The boundary used to separate the body parts in the encoded form data.
public let boundary: String
private var bodyParts: [BodyPart]
private var bodyPartError: AFError?
private let streamBufferSize: Int
// MARK: - Lifecycle
/// Creates a multipart form data object.
///
/// - returns: The multipart form data object.
public init() {
self.boundary = BoundaryGenerator.randomBoundary()
self.bodyParts = []
///
/// The optimal read/write buffer size in bytes for input and output streams is 1024 (1KB). For more
/// information, please refer to the following article:
/// - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Streams/Articles/ReadingInputStreams.html
///
self.streamBufferSize = 1024
}
// MARK: - Body Parts
/// Creates a body part from the data and appends it to the multipart form data object.
///
/// The body part data will be encoded using the following format:
///
/// - `Content-Disposition: form-data; name=#{name}` (HTTP Header)
/// - Encoded data
/// - Multipart form boundary
///
/// - parameter data: The data to encode into the multipart form data.
/// - parameter name: The name to associate with the data in the `Content-Disposition` HTTP header.
public func append(_ data: Data, withName name: String) {
let headers = contentHeaders(withName: name)
let stream = InputStream(data: data)
let length = UInt64(data.count)
append(stream, withLength: length, headers: headers)
}
/// Creates a body part from the data and appends it to the multipart form data object.
///
/// The body part data will be encoded using the following format:
///
/// - `Content-Disposition: form-data; name=#{name}` (HTTP Header)
/// - `Content-Type: #{generated mimeType}` (HTTP Header)
/// - Encoded data
/// - Multipart form boundary
///
/// - parameter data: The data to encode into the multipart form data.
/// - parameter name: The name to associate with the data in the `Content-Disposition` HTTP header.
/// - parameter mimeType: The MIME type to associate with the data content type in the `Content-Type` HTTP header.
public func append(_ data: Data, withName name: String, mimeType: String) {
let headers = contentHeaders(withName: name, mimeType: mimeType)
let stream = InputStream(data: data)
let length = UInt64(data.count)
append(stream, withLength: length, headers: headers)
}
/// Creates a body part from the data and appends it to the multipart form data object.
///
/// The body part data will be encoded using the following format:
///
/// - `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header)
/// - `Content-Type: #{mimeType}` (HTTP Header)
/// - Encoded file data
/// - Multipart form boundary
///
/// - parameter data: The data to encode into the multipart form data.
/// - parameter name: The name to associate with the data in the `Content-Disposition` HTTP header.
/// - parameter fileName: The filename to associate with the data in the `Content-Disposition` HTTP header.
/// - parameter mimeType: The MIME type to associate with the data in the `Content-Type` HTTP header.
public func append(_ data: Data, withName name: String, fileName: String, mimeType: String) {
let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType)
let stream = InputStream(data: data)
let length = UInt64(data.count)
append(stream, withLength: length, headers: headers)
}
/// Creates a body part from the file and appends it to the multipart form data object.
///
/// The body part data will be encoded using the following format:
///
/// - `Content-Disposition: form-data; name=#{name}; filename=#{generated filename}` (HTTP Header)
/// - `Content-Type: #{generated mimeType}` (HTTP Header)
/// - Encoded file data
/// - Multipart form boundary
///
/// The filename in the `Content-Disposition` HTTP header is generated from the last path component of the
/// `fileURL`. The `Content-Type` HTTP header MIME type is generated by mapping the `fileURL` extension to the
/// system associated MIME type.
///
/// - parameter fileURL: The URL of the file whose content will be encoded into the multipart form data.
/// - parameter name: The name to associate with the file content in the `Content-Disposition` HTTP header.
public func append(_ fileURL: URL, withName name: String) {
let fileName = fileURL.lastPathComponent
let pathExtension = fileURL.pathExtension
if !fileName.isEmpty && !pathExtension.isEmpty {
let mime = mimeType(forPathExtension: pathExtension)
append(fileURL, withName: name, fileName: fileName, mimeType: mime)
} else {
setBodyPartError(withReason: .bodyPartFilenameInvalid(in: fileURL))
}
}
/// Creates a body part from the file and appends it to the multipart form data object.
///
/// The body part data will be encoded using the following format:
///
/// - Content-Disposition: form-data; name=#{name}; filename=#{filename} (HTTP Header)
/// - Content-Type: #{mimeType} (HTTP Header)
/// - Encoded file data
/// - Multipart form boundary
///
/// - parameter fileURL: The URL of the file whose content will be encoded into the multipart form data.
/// - parameter name: The name to associate with the file content in the `Content-Disposition` HTTP header.
/// - parameter fileName: The filename to associate with the file content in the `Content-Disposition` HTTP header.
/// - parameter mimeType: The MIME type to associate with the file content in the `Content-Type` HTTP header.
public func append(_ fileURL: URL, withName name: String, fileName: String, mimeType: String) {
let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType)
//============================================================
// Check 1 - is file URL?
//============================================================
guard fileURL.isFileURL else {
setBodyPartError(withReason: .bodyPartURLInvalid(url: fileURL))
return
}
//============================================================
// Check 2 - is file URL reachable?
//============================================================
do {
let isReachable = try fileURL.checkPromisedItemIsReachable()
guard isReachable else {
setBodyPartError(withReason: .bodyPartFileNotReachable(at: fileURL))
return
}
} catch {
setBodyPartError(withReason: .bodyPartFileNotReachableWithError(atURL: fileURL, error: error))
return
}
//============================================================
// Check 3 - is file URL a directory?
//============================================================
var isDirectory: ObjCBool = false
let path = fileURL.path
guard FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) && !isDirectory.boolValue else
{
setBodyPartError(withReason: .bodyPartFileIsDirectory(at: fileURL))
return
}
//============================================================
// Check 4 - can the file size be extracted?
//============================================================
let bodyContentLength: UInt64
do {
guard let fileSize = try FileManager.default.attributesOfItem(atPath: path)[.size] as? NSNumber else {
setBodyPartError(withReason: .bodyPartFileSizeNotAvailable(at: fileURL))
return
}
bodyContentLength = fileSize.uint64Value
}
catch {
setBodyPartError(withReason: .bodyPartFileSizeQueryFailedWithError(forURL: fileURL, error: error))
return
}
//============================================================
// Check 5 - can a stream be created from file URL?
//============================================================
guard let stream = InputStream(url: fileURL) else {
setBodyPartError(withReason: .bodyPartInputStreamCreationFailed(for: fileURL))
return
}
append(stream, withLength: bodyContentLength, headers: headers)
}
/// Creates a body part from the stream and appends it to the multipart form data object.
///
/// The body part data will be encoded using the following format:
///
/// - `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header)
/// - `Content-Type: #{mimeType}` (HTTP Header)
/// - Encoded stream data
/// - Multipart form boundary
///
/// - parameter stream: The input stream to encode in the multipart form data.
/// - parameter length: The content length of the stream.
/// - parameter name: The name to associate with the stream content in the `Content-Disposition` HTTP header.
/// - parameter fileName: The filename to associate with the stream content in the `Content-Disposition` HTTP header.
/// - parameter mimeType: The MIME type to associate with the stream content in the `Content-Type` HTTP header.
public func append(
_ stream: InputStream,
withLength length: UInt64,
name: String,
fileName: String,
mimeType: String)
{
let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType)
append(stream, withLength: length, headers: headers)
}
/// Creates a body part with the headers, stream and length and appends it to the multipart form data object.
///
/// The body part data will be encoded using the following format:
///
/// - HTTP headers
/// - Encoded stream data
/// - Multipart form boundary
///
/// - parameter stream: The input stream to encode in the multipart form data.
/// - parameter length: The content length of the stream.
/// - parameter headers: The HTTP headers for the body part.
public func append(_ stream: InputStream, withLength length: UInt64, headers: HTTPHeaders) {
let bodyPart = BodyPart(headers: headers, bodyStream: stream, bodyContentLength: length)
bodyParts.append(bodyPart)
}
// MARK: - Data Encoding
/// Encodes all the appended body parts into a single `Data` value.
///
/// It is important to note that this method will load all the appended body parts into memory all at the same
/// time. This method should only be used when the encoded data will have a small memory footprint. For large data
/// cases, please use the `writeEncodedDataToDisk(fileURL:completionHandler:)` method.
///
/// - throws: An `AFError` if encoding encounters an error.
///
/// - returns: The encoded `Data` if encoding is successful.
public func encode() throws -> Data {
if let bodyPartError = bodyPartError {
throw bodyPartError
}
var encoded = Data()
bodyParts.first?.hasInitialBoundary = true
bodyParts.last?.hasFinalBoundary = true
for bodyPart in bodyParts {
let encodedData = try encode(bodyPart)
encoded.append(encodedData)
}
return encoded
}
/// Writes the appended body parts into the given file URL.
///
/// This process is facilitated by reading and writing with input and output streams, respectively. Thus,
/// this approach is very memory efficient and should be used for large body part data.
///
/// - parameter fileURL: The file URL to write the multipart form data into.
///
/// - throws: An `AFError` if encoding encounters an error.
public func writeEncodedData(to fileURL: URL) throws {
if let bodyPartError = bodyPartError {
throw bodyPartError
}
if FileManager.default.fileExists(atPath: fileURL.path) {
throw AFError.multipartEncodingFailed(reason: .outputStreamFileAlreadyExists(at: fileURL))
} else if !fileURL.isFileURL {
throw AFError.multipartEncodingFailed(reason: .outputStreamURLInvalid(url: fileURL))
}
guard let outputStream = OutputStream(url: fileURL, append: false) else {
throw AFError.multipartEncodingFailed(reason: .outputStreamCreationFailed(for: fileURL))
}
outputStream.open()
defer { outputStream.close() }
self.bodyParts.first?.hasInitialBoundary = true
self.bodyParts.last?.hasFinalBoundary = true
for bodyPart in self.bodyParts {
try write(bodyPart, to: outputStream)
}
}
// MARK: - Private - Body Part Encoding
private func encode(_ bodyPart: BodyPart) throws -> Data {
var encoded = Data()
let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData()
encoded.append(initialData)
let headerData = encodeHeaders(for: bodyPart)
encoded.append(headerData)
let bodyStreamData = try encodeBodyStream(for: bodyPart)
encoded.append(bodyStreamData)
if bodyPart.hasFinalBoundary {
encoded.append(finalBoundaryData())
}
return encoded
}
private func encodeHeaders(for bodyPart: BodyPart) -> Data {
var headerText = ""
for (key, value) in bodyPart.headers {
headerText += "\(key): \(value)\(EncodingCharacters.crlf)"
}
headerText += EncodingCharacters.crlf
return headerText.data(using: String.Encoding.utf8, allowLossyConversion: false)!
}
private func encodeBodyStream(for bodyPart: BodyPart) throws -> Data {
let inputStream = bodyPart.bodyStream
inputStream.open()
defer { inputStream.close() }
var encoded = Data()
while inputStream.hasBytesAvailable {
var buffer = [UInt8](repeating: 0, count: streamBufferSize)
let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize)
if let error = inputStream.streamError {
throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: error))
}
if bytesRead > 0 {
encoded.append(buffer, count: bytesRead)
} else {
break
}
}
return encoded
}
// MARK: - Private - Writing Body Part to Output Stream
private func write(_ bodyPart: BodyPart, to outputStream: OutputStream) throws {
try writeInitialBoundaryData(for: bodyPart, to: outputStream)
try writeHeaderData(for: bodyPart, to: outputStream)
try writeBodyStream(for: bodyPart, to: outputStream)
try writeFinalBoundaryData(for: bodyPart, to: outputStream)
}
private func writeInitialBoundaryData(for bodyPart: BodyPart, to outputStream: OutputStream) throws {
let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData()
return try write(initialData, to: outputStream)
}
private func writeHeaderData(for bodyPart: BodyPart, to outputStream: OutputStream) throws {
let headerData = encodeHeaders(for: bodyPart)
return try write(headerData, to: outputStream)
}
private func writeBodyStream(for bodyPart: BodyPart, to outputStream: OutputStream) throws {
let inputStream = bodyPart.bodyStream
inputStream.open()
defer { inputStream.close() }
while inputStream.hasBytesAvailable {
var buffer = [UInt8](repeating: 0, count: streamBufferSize)
let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize)
if let streamError = inputStream.streamError {
throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: streamError))
}
if bytesRead > 0 {
if buffer.count != bytesRead {
buffer = Array(buffer[0..<bytesRead])
}
try write(&buffer, to: outputStream)
} else {
break
}
}
}
private func writeFinalBoundaryData(for bodyPart: BodyPart, to outputStream: OutputStream) throws {
if bodyPart.hasFinalBoundary {
return try write(finalBoundaryData(), to: outputStream)
}
}
// MARK: - Private - Writing Buffered Data to Output Stream
private func write(_ data: Data, to outputStream: OutputStream) throws {
var buffer = [UInt8](repeating: 0, count: data.count)
data.copyBytes(to: &buffer, count: data.count)
return try write(&buffer, to: outputStream)
}
private func write(_ buffer: inout [UInt8], to outputStream: OutputStream) throws {
var bytesToWrite = buffer.count
while bytesToWrite > 0, outputStream.hasSpaceAvailable {
let bytesWritten = outputStream.write(buffer, maxLength: bytesToWrite)
if let error = outputStream.streamError {
throw AFError.multipartEncodingFailed(reason: .outputStreamWriteFailed(error: error))
}
bytesToWrite -= bytesWritten
if bytesToWrite > 0 {
buffer = Array(buffer[bytesWritten..<buffer.count])
}
}
}
// MARK: - Private - Mime Type
private func mimeType(forPathExtension pathExtension: String) -> String {
if
let id = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(),
let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?.takeRetainedValue()
{
return contentType as String
}
return "application/octet-stream"
}
// MARK: - Private - Content Headers
private func contentHeaders(withName name: String, fileName: String? = nil, mimeType: String? = nil) -> [String: String] {
var disposition = "form-data; name=\"\(name)\""
if let fileName = fileName { disposition += "; filename=\"\(fileName)\"" }
var headers = ["Content-Disposition": disposition]
if let mimeType = mimeType { headers["Content-Type"] = mimeType }
return headers
}
// MARK: - Private - Boundary Encoding
private func initialBoundaryData() -> Data {
return BoundaryGenerator.boundaryData(forBoundaryType: .initial, boundary: boundary)
}
private func encapsulatedBoundaryData() -> Data {
return BoundaryGenerator.boundaryData(forBoundaryType: .encapsulated, boundary: boundary)
}
private func finalBoundaryData() -> Data {
return BoundaryGenerator.boundaryData(forBoundaryType: .final, boundary: boundary)
}
// MARK: - Private - Errors
private func setBodyPartError(withReason reason: AFError.MultipartEncodingFailureReason) {
guard bodyPartError == nil else { return }
bodyPartError = AFError.multipartEncodingFailed(reason: reason)
}
}

View File

@@ -0,0 +1,240 @@
//
// NetworkReachabilityManager.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#if !os(watchOS)
import Foundation
import SystemConfiguration
/// The `NetworkReachabilityManager` class listens for reachability changes of hosts and addresses for both WWAN and
/// WiFi network interfaces.
///
/// Reachability can be used to determine background information about why a network operation failed, or to retry
/// network requests when a connection is established. It should not be used to prevent a user from initiating a network
/// request, as it's possible that an initial request may be required to establish reachability.
open class NetworkReachabilityManager {
/**
Defines the various states of network reachability.
- Unknown: It is unknown whether the network is reachable.
- NotReachable: The network is not reachable.
- ReachableOnWWAN: The network is reachable over the WWAN connection.
- ReachableOnWiFi: The network is reachable over the WiFi connection.
*/
/// Defines the various states of network reachability.
///
/// - unknown: It is unknown whether the network is reachable.
/// - notReachable: The network is not reachable.
/// - reachable: The network is reachable.
public enum NetworkReachabilityStatus {
case unknown
case notReachable
case reachable(ConnectionType)
}
/// Defines the various connection types detected by reachability flags.
///
/// - ethernetOrWiFi: The connection type is either over Ethernet or WiFi.
/// - wwan: The connection type is a WWAN connection.
public enum ConnectionType {
case ethernetOrWiFi
case wwan
}
/// A closure executed when the network reachability status changes. The closure takes a single argument: the
/// network reachability status.
public typealias Listener = (NetworkReachabilityStatus) -> Void
// MARK: - Properties
/// Whether the network is currently reachable.
open var isReachable: Bool { return isReachableOnWWAN || isReachableOnEthernetOrWiFi }
/// Whether the network is currently reachable over the WWAN interface.
open var isReachableOnWWAN: Bool { return networkReachabilityStatus == .reachable(.wwan) }
/// Whether the network is currently reachable over Ethernet or WiFi interface.
open var isReachableOnEthernetOrWiFi: Bool { return networkReachabilityStatus == .reachable(.ethernetOrWiFi) }
/// The current network reachability status.
open var networkReachabilityStatus: NetworkReachabilityStatus {
guard let flags = self.flags else { return .unknown }
return networkReachabilityStatusForFlags(flags)
}
/// The dispatch queue to execute the `listener` closure on.
open var listenerQueue: DispatchQueue = DispatchQueue.main
/// A closure executed when the network reachability status changes.
open var listener: Listener?
private var flags: SCNetworkReachabilityFlags? {
var flags = SCNetworkReachabilityFlags()
if SCNetworkReachabilityGetFlags(reachability, &flags) {
return flags
}
return nil
}
private let reachability: SCNetworkReachability
private var previousFlags: SCNetworkReachabilityFlags
// MARK: - Initialization
/// Creates a `NetworkReachabilityManager` instance with the specified host.
///
/// - parameter host: The host used to evaluate network reachability.
///
/// - returns: The new `NetworkReachabilityManager` instance.
public convenience init?(host: String) {
guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil }
self.init(reachability: reachability)
}
/// Creates a `NetworkReachabilityManager` instance that monitors the address 0.0.0.0.
///
/// Reachability treats the 0.0.0.0 address as a special token that causes it to monitor the general routing
/// status of the device, both IPv4 and IPv6.
///
/// - returns: The new `NetworkReachabilityManager` instance.
public convenience init?() {
var address = sockaddr_in()
address.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
address.sin_family = sa_family_t(AF_INET)
guard let reachability = withUnsafePointer(to: &address, { pointer in
return pointer.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout<sockaddr>.size) {
return SCNetworkReachabilityCreateWithAddress(nil, $0)
}
}) else { return nil }
self.init(reachability: reachability)
}
private init(reachability: SCNetworkReachability) {
self.reachability = reachability
self.previousFlags = SCNetworkReachabilityFlags()
}
deinit {
stopListening()
}
// MARK: - Listening
/// Starts listening for changes in network reachability status.
///
/// - returns: `true` if listening was started successfully, `false` otherwise.
@discardableResult
open func startListening() -> Bool {
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
context.info = Unmanaged.passUnretained(self).toOpaque()
let callbackEnabled = SCNetworkReachabilitySetCallback(
reachability,
{ (_, flags, info) in
let reachability = Unmanaged<NetworkReachabilityManager>.fromOpaque(info!).takeUnretainedValue()
reachability.notifyListener(flags)
},
&context
)
let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue)
listenerQueue.async {
self.previousFlags = SCNetworkReachabilityFlags()
self.notifyListener(self.flags ?? SCNetworkReachabilityFlags())
}
return callbackEnabled && queueEnabled
}
/// Stops listening for changes in network reachability status.
open func stopListening() {
SCNetworkReachabilitySetCallback(reachability, nil, nil)
SCNetworkReachabilitySetDispatchQueue(reachability, nil)
}
// MARK: - Internal - Listener Notification
func notifyListener(_ flags: SCNetworkReachabilityFlags) {
guard previousFlags != flags else { return }
previousFlags = flags
listener?(networkReachabilityStatusForFlags(flags))
}
// MARK: - Internal - Network Reachability Status
func networkReachabilityStatusForFlags(_ flags: SCNetworkReachabilityFlags) -> NetworkReachabilityStatus {
guard flags.contains(.reachable) else { return .notReachable }
var networkStatus: NetworkReachabilityStatus = .notReachable
if !flags.contains(.connectionRequired) { networkStatus = .reachable(.ethernetOrWiFi) }
if flags.contains(.connectionOnDemand) || flags.contains(.connectionOnTraffic) {
if !flags.contains(.interventionRequired) { networkStatus = .reachable(.ethernetOrWiFi) }
}
#if os(iOS)
if flags.contains(.isWWAN) { networkStatus = .reachable(.wwan) }
#endif
return networkStatus
}
}
// MARK: -
extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable {}
/// Returns whether the two network reachability status values are equal.
///
/// - parameter lhs: The left-hand side value to compare.
/// - parameter rhs: The right-hand side value to compare.
///
/// - returns: `true` if the two values are equal, `false` otherwise.
public func ==(
lhs: NetworkReachabilityManager.NetworkReachabilityStatus,
rhs: NetworkReachabilityManager.NetworkReachabilityStatus)
-> Bool
{
switch (lhs, rhs) {
case (.unknown, .unknown):
return true
case (.notReachable, .notReachable):
return true
case let (.reachable(lhsConnectionType), .reachable(rhsConnectionType)):
return lhsConnectionType == rhsConnectionType
default:
return false
}
}
#endif

View File

@@ -0,0 +1,52 @@
//
// Notifications.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
extension Notification.Name {
/// Used as a namespace for all `URLSessionTask` related notifications.
public struct Task {
/// Posted when a `URLSessionTask` is resumed. The notification `object` contains the resumed `URLSessionTask`.
public static let DidResume = Notification.Name(rawValue: "org.alamofire.notification.name.task.didResume")
/// Posted when a `URLSessionTask` is suspended. The notification `object` contains the suspended `URLSessionTask`.
public static let DidSuspend = Notification.Name(rawValue: "org.alamofire.notification.name.task.didSuspend")
/// Posted when a `URLSessionTask` is cancelled. The notification `object` contains the cancelled `URLSessionTask`.
public static let DidCancel = Notification.Name(rawValue: "org.alamofire.notification.name.task.didCancel")
/// Posted when a `URLSessionTask` is completed. The notification `object` contains the completed `URLSessionTask`.
public static let DidComplete = Notification.Name(rawValue: "org.alamofire.notification.name.task.didComplete")
}
}
// MARK: -
extension Notification {
/// Used as a namespace for all `Notification` user info dictionary keys.
public struct Key {
/// User info dictionary key representing the `URLSessionTask` associated with the notification.
public static let Task = "org.alamofire.notification.key.task"
}
}

View File

@@ -0,0 +1,373 @@
//
// ParameterEncoding.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// HTTP method definitions.
///
/// See https://tools.ietf.org/html/rfc7231#section-4.3
public enum HTTPMethod: String {
case options = "OPTIONS"
case get = "GET"
case head = "HEAD"
case post = "POST"
case put = "PUT"
case patch = "PATCH"
case delete = "DELETE"
case trace = "TRACE"
case connect = "CONNECT"
}
// MARK: -
/// A dictionary of parameters to apply to a `URLRequest`.
public typealias Parameters = [String: Any]
/// A type used to define how a set of parameters are applied to a `URLRequest`.
public protocol ParameterEncoding {
/// Creates a URL request by encoding parameters and applying them onto an existing request.
///
/// - parameter urlRequest: The request to have parameters applied.
/// - parameter parameters: The parameters to apply.
///
/// - throws: An `AFError.parameterEncodingFailed` error if encoding fails.
///
/// - returns: The encoded request.
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
}
// MARK: -
/// Creates a url-encoded query string to be set as or appended to any existing URL query string or set as the HTTP
/// body of the URL request. Whether the query string is set or appended to any existing URL query string or set as
/// the HTTP body depends on the destination of the encoding.
///
/// The `Content-Type` HTTP header field of an encoded request with HTTP body is set to
/// `application/x-www-form-urlencoded; charset=utf-8`. Since there is no published specification for how to encode
/// collection types, the convention of appending `[]` to the key for array values (`foo[]=1&foo[]=2`), and appending
/// the key surrounded by square brackets for nested dictionary values (`foo[bar]=baz`).
public struct URLEncoding: ParameterEncoding {
// MARK: Helper Types
/// Defines whether the url-encoded query string is applied to the existing query string or HTTP body of the
/// resulting URL request.
///
/// - methodDependent: Applies encoded query string result to existing query string for `GET`, `HEAD` and `DELETE`
/// requests and sets as the HTTP body for requests with any other HTTP method.
/// - queryString: Sets or appends encoded query string result to existing query string.
/// - httpBody: Sets encoded query string result as the HTTP body of the URL request.
public enum Destination {
case methodDependent, queryString, httpBody
}
// MARK: Properties
/// Returns a default `URLEncoding` instance.
public static var `default`: URLEncoding { return URLEncoding() }
/// Returns a `URLEncoding` instance with a `.methodDependent` destination.
public static var methodDependent: URLEncoding { return URLEncoding() }
/// Returns a `URLEncoding` instance with a `.queryString` destination.
public static var queryString: URLEncoding { return URLEncoding(destination: .queryString) }
/// Returns a `URLEncoding` instance with an `.httpBody` destination.
public static var httpBody: URLEncoding { return URLEncoding(destination: .httpBody) }
/// The destination defining where the encoded query string is to be applied to the URL request.
public let destination: Destination
// MARK: Initialization
/// Creates a `URLEncoding` instance using the specified destination.
///
/// - parameter destination: The destination defining where the encoded query string is to be applied.
///
/// - returns: The new `URLEncoding` instance.
public init(destination: Destination = .methodDependent) {
self.destination = destination
}
// MARK: Encoding
/// Creates a URL request by encoding parameters and applying them onto an existing request.
///
/// - parameter urlRequest: The request to have parameters applied.
/// - parameter parameters: The parameters to apply.
///
/// - throws: An `Error` if the encoding process encounters an error.
///
/// - returns: The encoded request.
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters else { return urlRequest }
if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
guard let url = urlRequest.url else {
throw AFError.parameterEncodingFailed(reason: .missingURL)
}
if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
urlComponents.percentEncodedQuery = percentEncodedQuery
urlRequest.url = urlComponents.url
}
} else {
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
}
return urlRequest
}
/// Creates percent-escaped, URL encoded query string components from the given key-value pair using recursion.
///
/// - parameter key: The key of the query component.
/// - parameter value: The value of the query component.
///
/// - returns: The percent-escaped, URL encoded query string components.
public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
var components: [(String, String)] = []
if let dictionary = value as? [String: Any] {
for (nestedKey, value) in dictionary {
components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
}
} else if let array = value as? [Any] {
for value in array {
components += queryComponents(fromKey: "\(key)[]", value: value)
}
} else if let value = value as? NSNumber {
if value.isBool {
components.append((escape(key), escape((value.boolValue ? "1" : "0"))))
} else {
components.append((escape(key), escape("\(value)")))
}
} else if let bool = value as? Bool {
components.append((escape(key), escape((bool ? "1" : "0"))))
} else {
components.append((escape(key), escape("\(value)")))
}
return components
}
/// Returns a percent-escaped string following RFC 3986 for a query string key or value.
///
/// RFC 3986 states that the following characters are "reserved" characters.
///
/// - General Delimiters: ":", "#", "[", "]", "@", "?", "/"
/// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
///
/// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
/// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
/// should be percent-escaped in the query string.
///
/// - parameter string: The string to be percent-escaped.
///
/// - returns: The percent-escaped string.
public func escape(_ string: String) -> String {
let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="
var allowedCharacterSet = CharacterSet.urlQueryAllowed
allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
return string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string
}
private func query(_ parameters: [String: Any]) -> String {
var components: [(String, String)] = []
for key in parameters.keys.sorted(by: <) {
let value = parameters[key]!
components += queryComponents(fromKey: key, value: value)
}
return components.map { "\($0)=\($1)" }.joined(separator: "&")
}
private func encodesParametersInURL(with method: HTTPMethod) -> Bool {
switch destination {
case .queryString:
return true
case .httpBody:
return false
default:
break
}
switch method {
case .get, .head, .delete:
return true
default:
return false
}
}
}
// MARK: -
/// Uses `JSONSerialization` to create a JSON representation of the parameters object, which is set as the body of the
/// request. The `Content-Type` HTTP header field of an encoded request is set to `application/json`.
public struct JSONEncoding: ParameterEncoding {
// MARK: Properties
/// Returns a `JSONEncoding` instance with default writing options.
public static var `default`: JSONEncoding { return JSONEncoding() }
/// Returns a `JSONEncoding` instance with `.prettyPrinted` writing options.
public static var prettyPrinted: JSONEncoding { return JSONEncoding(options: .prettyPrinted) }
/// The options for writing the parameters as JSON data.
public let options: JSONSerialization.WritingOptions
// MARK: Initialization
/// Creates a `JSONEncoding` instance using the specified options.
///
/// - parameter options: The options for writing the parameters as JSON data.
///
/// - returns: The new `JSONEncoding` instance.
public init(options: JSONSerialization.WritingOptions = []) {
self.options = options
}
// MARK: Encoding
/// Creates a URL request by encoding parameters and applying them onto an existing request.
///
/// - parameter urlRequest: The request to have parameters applied.
/// - parameter parameters: The parameters to apply.
///
/// - throws: An `Error` if the encoding process encounters an error.
///
/// - returns: The encoded request.
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters else { return urlRequest }
do {
let data = try JSONSerialization.data(withJSONObject: parameters, options: options)
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = data
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
}
// MARK: -
/// Uses `PropertyListSerialization` to create a plist representation of the parameters object, according to the
/// associated format and write options values, which is set as the body of the request. The `Content-Type` HTTP header
/// field of an encoded request is set to `application/x-plist`.
public struct PropertyListEncoding: ParameterEncoding {
// MARK: Properties
/// Returns a default `PropertyListEncoding` instance.
public static var `default`: PropertyListEncoding { return PropertyListEncoding() }
/// Returns a `PropertyListEncoding` instance with xml formatting and default writing options.
public static var xml: PropertyListEncoding { return PropertyListEncoding(format: .xml) }
/// Returns a `PropertyListEncoding` instance with binary formatting and default writing options.
public static var binary: PropertyListEncoding { return PropertyListEncoding(format: .binary) }
/// The property list serialization format.
public let format: PropertyListSerialization.PropertyListFormat
/// The options for writing the parameters as plist data.
public let options: PropertyListSerialization.WriteOptions
// MARK: Initialization
/// Creates a `PropertyListEncoding` instance using the specified format and options.
///
/// - parameter format: The property list serialization format.
/// - parameter options: The options for writing the parameters as plist data.
///
/// - returns: The new `PropertyListEncoding` instance.
public init(
format: PropertyListSerialization.PropertyListFormat = .xml,
options: PropertyListSerialization.WriteOptions = 0)
{
self.format = format
self.options = options
}
// MARK: Encoding
/// Creates a URL request by encoding parameters and applying them onto an existing request.
///
/// - parameter urlRequest: The request to have parameters applied.
/// - parameter parameters: The parameters to apply.
///
/// - throws: An `Error` if the encoding process encounters an error.
///
/// - returns: The encoded request.
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters else { return urlRequest }
do {
let data = try PropertyListSerialization.data(
fromPropertyList: parameters,
format: format,
options: options
)
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/x-plist", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = data
} catch {
throw AFError.parameterEncodingFailed(reason: .propertyListEncodingFailed(error: error))
}
return urlRequest
}
}
// MARK: -
extension NSNumber {
fileprivate var isBool: Bool { return CFBooleanGetTypeID() == CFGetTypeID(self) }
}

View File

@@ -0,0 +1,600 @@
//
// Request.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// A type that can inspect and optionally adapt a `URLRequest` in some manner if necessary.
public protocol RequestAdapter {
/// Inspects and adapts the specified `URLRequest` in some manner if necessary and returns the result.
///
/// - parameter urlRequest: The URL request to adapt.
///
/// - throws: An `Error` if the adaptation encounters an error.
///
/// - returns: The adapted `URLRequest`.
func adapt(_ urlRequest: URLRequest) throws -> URLRequest
}
// MARK: -
/// A closure executed when the `RequestRetrier` determines whether a `Request` should be retried or not.
public typealias RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval) -> Void
/// A type that determines whether a request should be retried after being executed by the specified session manager
/// and encountering an error.
public protocol RequestRetrier {
/// Determines whether the `Request` should be retried by calling the `completion` closure.
///
/// This operation is fully asychronous. Any amount of time can be taken to determine whether the request needs
/// to be retried. The one requirement is that the completion closure is called to ensure the request is properly
/// cleaned up after.
///
/// - parameter manager: The session manager the request was executed on.
/// - parameter request: The request that failed due to the encountered error.
/// - parameter error: The error encountered when executing the request.
/// - parameter completion: The completion closure to be executed when retry decision has been determined.
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion)
}
// MARK: -
protocol TaskConvertible {
func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask
}
/// A dictionary of headers to apply to a `URLRequest`.
public typealias HTTPHeaders = [String: String]
// MARK: -
/// Responsible for sending a request and receiving the response and associated data from the server, as well as
/// managing its underlying `URLSessionTask`.
open class Request {
// MARK: Helper Types
/// A closure executed when monitoring upload or download progress of a request.
public typealias ProgressHandler = (Progress) -> Void
enum RequestTask {
case data(TaskConvertible?, URLSessionTask?)
case download(TaskConvertible?, URLSessionTask?)
case upload(TaskConvertible?, URLSessionTask?)
case stream(TaskConvertible?, URLSessionTask?)
}
// MARK: Properties
/// The delegate for the underlying task.
open internal(set) var delegate: TaskDelegate {
get {
taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
return taskDelegate
}
set {
taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
taskDelegate = newValue
}
}
/// The underlying task.
open var task: URLSessionTask? { return delegate.task }
/// The session belonging to the underlying task.
open let session: URLSession
/// The request sent or to be sent to the server.
open var request: URLRequest? { return task?.originalRequest }
/// The response received from the server, if any.
open var response: HTTPURLResponse? { return task?.response as? HTTPURLResponse }
let originalTask: TaskConvertible?
var startTime: CFAbsoluteTime?
var endTime: CFAbsoluteTime?
var validations: [() -> Void] = []
private var taskDelegate: TaskDelegate
private var taskDelegateLock = NSLock()
// MARK: Lifecycle
init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {
self.session = session
switch requestTask {
case .data(let originalTask, let task):
taskDelegate = DataTaskDelegate(task: task)
self.originalTask = originalTask
case .download(let originalTask, let task):
taskDelegate = DownloadTaskDelegate(task: task)
self.originalTask = originalTask
case .upload(let originalTask, let task):
taskDelegate = UploadTaskDelegate(task: task)
self.originalTask = originalTask
case .stream(let originalTask, let task):
taskDelegate = TaskDelegate(task: task)
self.originalTask = originalTask
}
delegate.error = error
delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
}
// MARK: Authentication
/// Associates an HTTP Basic credential with the request.
///
/// - parameter user: The user.
/// - parameter password: The password.
/// - parameter persistence: The URL credential persistence. `.ForSession` by default.
///
/// - returns: The request.
@discardableResult
open func authenticate(
user: String,
password: String,
persistence: URLCredential.Persistence = .forSession)
-> Self
{
let credential = URLCredential(user: user, password: password, persistence: persistence)
return authenticate(usingCredential: credential)
}
/// Associates a specified credential with the request.
///
/// - parameter credential: The credential.
///
/// - returns: The request.
@discardableResult
open func authenticate(usingCredential credential: URLCredential) -> Self {
delegate.credential = credential
return self
}
/// Returns a base64 encoded basic authentication credential as an authorization header tuple.
///
/// - parameter user: The user.
/// - parameter password: The password.
///
/// - returns: A tuple with Authorization header and credential value if encoding succeeds, `nil` otherwise.
open static func authorizationHeader(user: String, password: String) -> (key: String, value: String)? {
guard let data = "\(user):\(password)".data(using: .utf8) else { return nil }
let credential = data.base64EncodedString(options: [])
return (key: "Authorization", value: "Basic \(credential)")
}
// MARK: State
/// Resumes the request.
open func resume() {
guard let task = task else { delegate.queue.isSuspended = false ; return }
if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
task.resume()
NotificationCenter.default.post(
name: Notification.Name.Task.DidResume,
object: self,
userInfo: [Notification.Key.Task: task]
)
}
/// Suspends the request.
open func suspend() {
guard let task = task else { return }
task.suspend()
NotificationCenter.default.post(
name: Notification.Name.Task.DidSuspend,
object: self,
userInfo: [Notification.Key.Task: task]
)
}
/// Cancels the request.
open func cancel() {
guard let task = task else { return }
task.cancel()
NotificationCenter.default.post(
name: Notification.Name.Task.DidCancel,
object: self,
userInfo: [Notification.Key.Task: task]
)
}
}
// MARK: - CustomStringConvertible
extension Request: CustomStringConvertible {
/// The textual representation used when written to an output stream, which includes the HTTP method and URL, as
/// well as the response status code if a response has been received.
open var description: String {
var components: [String] = []
if let HTTPMethod = request?.httpMethod {
components.append(HTTPMethod)
}
if let urlString = request?.url?.absoluteString {
components.append(urlString)
}
if let response = response {
components.append("(\(response.statusCode))")
}
return components.joined(separator: " ")
}
}
// MARK: - CustomDebugStringConvertible
extension Request: CustomDebugStringConvertible {
/// The textual representation used when written to an output stream, in the form of a cURL command.
open var debugDescription: String {
return cURLRepresentation()
}
func cURLRepresentation() -> String {
var components = ["$ curl -i"]
guard let request = self.request,
let url = request.url,
let host = url.host
else {
return "$ curl command could not be created"
}
if let httpMethod = request.httpMethod, httpMethod != "GET" {
components.append("-X \(httpMethod)")
}
if let credentialStorage = self.session.configuration.urlCredentialStorage {
let protectionSpace = URLProtectionSpace(
host: host,
port: url.port ?? 0,
protocol: url.scheme,
realm: host,
authenticationMethod: NSURLAuthenticationMethodHTTPBasic
)
if let credentials = credentialStorage.credentials(for: protectionSpace)?.values {
for credential in credentials {
components.append("-u \(credential.user!):\(credential.password!)")
}
} else {
if let credential = delegate.credential {
components.append("-u \(credential.user!):\(credential.password!)")
}
}
}
if session.configuration.httpShouldSetCookies {
if
let cookieStorage = session.configuration.httpCookieStorage,
let cookies = cookieStorage.cookies(for: url), !cookies.isEmpty
{
let string = cookies.reduce("") { $0 + "\($1.name)=\($1.value);" }
components.append("-b \"\(string.substring(to: string.characters.index(before: string.endIndex)))\"")
}
}
var headers: [AnyHashable: Any] = [:]
if let additionalHeaders = session.configuration.httpAdditionalHeaders {
for (field, value) in additionalHeaders where field != AnyHashable("Cookie") {
headers[field] = value
}
}
if let headerFields = request.allHTTPHeaderFields {
for (field, value) in headerFields where field != "Cookie" {
headers[field] = value
}
}
for (field, value) in headers {
components.append("-H \"\(field): \(value)\"")
}
if let httpBodyData = request.httpBody, let httpBody = String(data: httpBodyData, encoding: .utf8) {
var escapedBody = httpBody.replacingOccurrences(of: "\\\"", with: "\\\\\"")
escapedBody = escapedBody.replacingOccurrences(of: "\"", with: "\\\"")
components.append("-d \"\(escapedBody)\"")
}
components.append("\"\(url.absoluteString)\"")
return components.joined(separator: " \\\n\t")
}
}
// MARK: -
/// Specific type of `Request` that manages an underlying `URLSessionDataTask`.
open class DataRequest: Request {
// MARK: Helper Types
struct Requestable: TaskConvertible {
let urlRequest: URLRequest
func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
let urlRequest = try self.urlRequest.adapt(using: adapter)
return queue.syncResult { session.dataTask(with: urlRequest) }
}
}
// MARK: Properties
/// The progress of fetching the response data from the server for the request.
open var progress: Progress { return dataDelegate.progress }
var dataDelegate: DataTaskDelegate { return delegate as! DataTaskDelegate }
// MARK: Stream
/// Sets a closure to be called periodically during the lifecycle of the request as data is read from the server.
///
/// This closure returns the bytes most recently received from the server, not including data from previous calls.
/// If this closure is set, data will only be available within this closure, and will not be saved elsewhere. It is
/// also important to note that the server data in any `Response` object will be `nil`.
///
/// - parameter closure: The code to be executed periodically during the lifecycle of the request.
///
/// - returns: The request.
@discardableResult
open func stream(closure: ((Data) -> Void)? = nil) -> Self {
dataDelegate.dataStream = closure
return self
}
// MARK: Progress
/// Sets a closure to be called periodically during the lifecycle of the `Request` as data is read from the server.
///
/// - parameter queue: The dispatch queue to execute the closure on.
/// - parameter closure: The code to be executed periodically as data is read from the server.
///
/// - returns: The request.
@discardableResult
open func downloadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {
dataDelegate.progressHandler = (closure, queue)
return self
}
}
// MARK: -
/// Specific type of `Request` that manages an underlying `URLSessionDownloadTask`.
open class DownloadRequest: Request {
// MARK: Helper Types
/// A collection of options to be executed prior to moving a downloaded file from the temporary URL to the
/// destination URL.
public struct DownloadOptions: OptionSet {
/// Returns the raw bitmask value of the option and satisfies the `RawRepresentable` protocol.
public let rawValue: UInt
/// A `DownloadOptions` flag that creates intermediate directories for the destination URL if specified.
public static let createIntermediateDirectories = DownloadOptions(rawValue: 1 << 0)
/// A `DownloadOptions` flag that removes a previous file from the destination URL if specified.
public static let removePreviousFile = DownloadOptions(rawValue: 1 << 1)
/// Creates a `DownloadFileDestinationOptions` instance with the specified raw value.
///
/// - parameter rawValue: The raw bitmask value for the option.
///
/// - returns: A new log level instance.
public init(rawValue: UInt) {
self.rawValue = rawValue
}
}
/// A closure executed once a download request has successfully completed in order to determine where to move the
/// temporary file written to during the download process. The closure takes two arguments: the temporary file URL
/// and the URL response, and returns a two arguments: the file URL where the temporary file should be moved and
/// the options defining how the file should be moved.
public typealias DownloadFileDestination = (
_ temporaryURL: URL,
_ response: HTTPURLResponse)
-> (destinationURL: URL, options: DownloadOptions)
enum Downloadable: TaskConvertible {
case request(URLRequest)
case resumeData(Data)
func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
let task: URLSessionTask
switch self {
case let .request(urlRequest):
let urlRequest = try urlRequest.adapt(using: adapter)
task = queue.syncResult { session.downloadTask(with: urlRequest) }
case let .resumeData(resumeData):
task = queue.syncResult { session.downloadTask(withResumeData: resumeData) }
}
return task
}
}
// MARK: Properties
/// The resume data of the underlying download task if available after a failure.
open var resumeData: Data? { return downloadDelegate.resumeData }
/// The progress of downloading the response data from the server for the request.
open var progress: Progress { return downloadDelegate.progress }
var downloadDelegate: DownloadTaskDelegate { return delegate as! DownloadTaskDelegate }
// MARK: State
/// Cancels the request.
open override func cancel() {
downloadDelegate.downloadTask.cancel { self.downloadDelegate.resumeData = $0 }
NotificationCenter.default.post(
name: Notification.Name.Task.DidCancel,
object: self,
userInfo: [Notification.Key.Task: task]
)
}
// MARK: Progress
/// Sets a closure to be called periodically during the lifecycle of the `Request` as data is read from the server.
///
/// - parameter queue: The dispatch queue to execute the closure on.
/// - parameter closure: The code to be executed periodically as data is read from the server.
///
/// - returns: The request.
@discardableResult
open func downloadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {
downloadDelegate.progressHandler = (closure, queue)
return self
}
// MARK: Destination
/// Creates a download file destination closure which uses the default file manager to move the temporary file to a
/// file URL in the first available directory with the specified search path directory and search path domain mask.
///
/// - parameter directory: The search path directory. `.DocumentDirectory` by default.
/// - parameter domain: The search path domain mask. `.UserDomainMask` by default.
///
/// - returns: A download file destination closure.
open class func suggestedDownloadDestination(
for directory: FileManager.SearchPathDirectory = .documentDirectory,
in domain: FileManager.SearchPathDomainMask = .userDomainMask)
-> DownloadFileDestination
{
return { temporaryURL, response in
let directoryURLs = FileManager.default.urls(for: directory, in: domain)
if !directoryURLs.isEmpty {
return (directoryURLs[0].appendingPathComponent(response.suggestedFilename!), [])
}
return (temporaryURL, [])
}
}
}
// MARK: -
/// Specific type of `Request` that manages an underlying `URLSessionUploadTask`.
open class UploadRequest: DataRequest {
// MARK: Helper Types
enum Uploadable: TaskConvertible {
case data(Data, URLRequest)
case file(URL, URLRequest)
case stream(InputStream, URLRequest)
func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
let task: URLSessionTask
switch self {
case let .data(data, urlRequest):
let urlRequest = try urlRequest.adapt(using: adapter)
task = queue.syncResult { session.uploadTask(with: urlRequest, from: data) }
case let .file(url, urlRequest):
let urlRequest = try urlRequest.adapt(using: adapter)
task = queue.syncResult { session.uploadTask(with: urlRequest, fromFile: url) }
case let .stream(_, urlRequest):
let urlRequest = try urlRequest.adapt(using: adapter)
task = queue.syncResult { session.uploadTask(withStreamedRequest: urlRequest) }
}
return task
}
}
// MARK: Properties
/// The progress of uploading the payload to the server for the upload request.
open var uploadProgress: Progress { return uploadDelegate.uploadProgress }
var uploadDelegate: UploadTaskDelegate { return delegate as! UploadTaskDelegate }
// MARK: Upload Progress
/// Sets a closure to be called periodically during the lifecycle of the `UploadRequest` as data is sent to
/// the server.
///
/// After the data is sent to the server, the `progress(queue:closure:)` APIs can be used to monitor the progress
/// of data being read from the server.
///
/// - parameter queue: The dispatch queue to execute the closure on.
/// - parameter closure: The code to be executed periodically as data is sent to the server.
///
/// - returns: The request.
@discardableResult
open func uploadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {
uploadDelegate.uploadProgressHandler = (closure, queue)
return self
}
}
// MARK: -
#if !os(watchOS)
/// Specific type of `Request` that manages an underlying `URLSessionStreamTask`.
open class StreamRequest: Request {
enum Streamable: TaskConvertible {
case stream(hostName: String, port: Int)
case netService(NetService)
func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
let task: URLSessionTask
switch self {
case let .stream(hostName, port):
task = queue.syncResult { session.streamTask(withHostName: hostName, port: port) }
case let .netService(netService):
task = queue.syncResult { session.streamTask(with: netService) }
}
return task
}
}
}
#endif

View File

@@ -0,0 +1,296 @@
//
// Response.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Used to store all data associated with an non-serialized response of a data or upload request.
public struct DefaultDataResponse {
/// The URL request sent to the server.
public let request: URLRequest?
/// The server's response to the URL request.
public let response: HTTPURLResponse?
/// The data returned by the server.
public let data: Data?
/// The error encountered while executing or validating the request.
public let error: Error?
var _metrics: AnyObject?
init(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) {
self.request = request
self.response = response
self.data = data
self.error = error
}
}
// MARK: -
/// Used to store all data associated with a serialized response of a data or upload request.
public struct DataResponse<Value> {
/// The URL request sent to the server.
public let request: URLRequest?
/// The server's response to the URL request.
public let response: HTTPURLResponse?
/// The data returned by the server.
public let data: Data?
/// The result of response serialization.
public let result: Result<Value>
/// The timeline of the complete lifecycle of the `Request`.
public let timeline: Timeline
var _metrics: AnyObject?
/// Creates a `DataResponse` instance with the specified parameters derived from response serialization.
///
/// - parameter request: The URL request sent to the server.
/// - parameter response: The server's response to the URL request.
/// - parameter data: The data returned by the server.
/// - parameter result: The result of response serialization.
/// - parameter timeline: The timeline of the complete lifecycle of the `Request`. Defaults to `Timeline()`.
///
/// - returns: The new `DataResponse` instance.
public init(
request: URLRequest?,
response: HTTPURLResponse?,
data: Data?,
result: Result<Value>,
timeline: Timeline = Timeline())
{
self.request = request
self.response = response
self.data = data
self.result = result
self.timeline = timeline
}
}
// MARK: -
extension DataResponse: CustomStringConvertible, CustomDebugStringConvertible {
/// The textual representation used when written to an output stream, which includes whether the result was a
/// success or failure.
public var description: String {
return result.debugDescription
}
/// The debug textual representation used when written to an output stream, which includes the URL request, the URL
/// response, the server data, the response serialization result and the timeline.
public var debugDescription: String {
var output: [String] = []
output.append(request != nil ? "[Request]: \(request!)" : "[Request]: nil")
output.append(response != nil ? "[Response]: \(response!)" : "[Response]: nil")
output.append("[Data]: \(data?.count ?? 0) bytes")
output.append("[Result]: \(result.debugDescription)")
output.append("[Timeline]: \(timeline.debugDescription)")
return output.joined(separator: "\n")
}
}
// MARK: -
/// Used to store all data associated with an non-serialized response of a download request.
public struct DefaultDownloadResponse {
/// The URL request sent to the server.
public let request: URLRequest?
/// The server's response to the URL request.
public let response: HTTPURLResponse?
/// The temporary destination URL of the data returned from the server.
public let temporaryURL: URL?
/// The final destination URL of the data returned from the server if it was moved.
public let destinationURL: URL?
/// The resume data generated if the request was cancelled.
public let resumeData: Data?
/// The error encountered while executing or validating the request.
public let error: Error?
var _metrics: AnyObject?
init(
request: URLRequest?,
response: HTTPURLResponse?,
temporaryURL: URL?,
destinationURL: URL?,
resumeData: Data?,
error: Error?)
{
self.request = request
self.response = response
self.temporaryURL = temporaryURL
self.destinationURL = destinationURL
self.resumeData = resumeData
self.error = error
}
}
// MARK: -
/// Used to store all data associated with a serialized response of a download request.
public struct DownloadResponse<Value> {
/// The URL request sent to the server.
public let request: URLRequest?
/// The server's response to the URL request.
public let response: HTTPURLResponse?
/// The temporary destination URL of the data returned from the server.
public let temporaryURL: URL?
/// The final destination URL of the data returned from the server if it was moved.
public let destinationURL: URL?
/// The resume data generated if the request was cancelled.
public let resumeData: Data?
/// The result of response serialization.
public let result: Result<Value>
/// The timeline of the complete lifecycle of the request.
public let timeline: Timeline
var _metrics: AnyObject?
/// Creates a `DownloadResponse` instance with the specified parameters derived from response serialization.
///
/// - parameter request: The URL request sent to the server.
/// - parameter response: The server's response to the URL request.
/// - parameter temporaryURL: The temporary destination URL of the data returned from the server.
/// - parameter destinationURL: The final destination URL of the data returned from the server if it was moved.
/// - parameter resumeData: The resume data generated if the request was cancelled.
/// - parameter result: The result of response serialization.
/// - parameter timeline: The timeline of the complete lifecycle of the `Request`. Defaults to `Timeline()`.
///
/// - returns: The new `DownloadResponse` instance.
public init(
request: URLRequest?,
response: HTTPURLResponse?,
temporaryURL: URL?,
destinationURL: URL?,
resumeData: Data?,
result: Result<Value>,
timeline: Timeline = Timeline())
{
self.request = request
self.response = response
self.temporaryURL = temporaryURL
self.destinationURL = destinationURL
self.resumeData = resumeData
self.result = result
self.timeline = timeline
}
}
// MARK: -
extension DownloadResponse: CustomStringConvertible, CustomDebugStringConvertible {
/// The textual representation used when written to an output stream, which includes whether the result was a
/// success or failure.
public var description: String {
return result.debugDescription
}
/// The debug textual representation used when written to an output stream, which includes the URL request, the URL
/// response, the temporary and destination URLs, the resume data, the response serialization result and the
/// timeline.
public var debugDescription: String {
var output: [String] = []
output.append(request != nil ? "[Request]: \(request!)" : "[Request]: nil")
output.append(response != nil ? "[Response]: \(response!)" : "[Response]: nil")
output.append("[TemporaryURL]: \(temporaryURL?.path ?? "nil")")
output.append("[DestinationURL]: \(destinationURL?.path ?? "nil")")
output.append("[ResumeData]: \(resumeData?.count ?? 0) bytes")
output.append("[Result]: \(result.debugDescription)")
output.append("[Timeline]: \(timeline.debugDescription)")
return output.joined(separator: "\n")
}
}
// MARK: -
protocol Response {
/// The task metrics containing the request / response statistics.
var _metrics: AnyObject? { get set }
mutating func add(_ metrics: AnyObject?)
}
extension Response {
mutating func add(_ metrics: AnyObject?) {
#if !os(watchOS)
guard #available(iOS 10.0, macOS 10.12, tvOS 10.0, *) else { return }
guard let metrics = metrics as? URLSessionTaskMetrics else { return }
_metrics = metrics
#endif
}
}
// MARK: -
@available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
extension DefaultDataResponse: Response {
#if !os(watchOS)
/// The task metrics containing the request / response statistics.
public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics }
#endif
}
@available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
extension DataResponse: Response {
#if !os(watchOS)
/// The task metrics containing the request / response statistics.
public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics }
#endif
}
@available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
extension DefaultDownloadResponse: Response {
#if !os(watchOS)
/// The task metrics containing the request / response statistics.
public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics }
#endif
}
@available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
extension DownloadResponse: Response {
#if !os(watchOS)
/// The task metrics containing the request / response statistics.
public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics }
#endif
}

View File

@@ -0,0 +1,716 @@
//
// ResponseSerialization.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// The type in which all data response serializers must conform to in order to serialize a response.
public protocol DataResponseSerializerProtocol {
/// The type of serialized object to be created by this `DataResponseSerializerType`.
associatedtype SerializedObject
/// A closure used by response handlers that takes a request, response, data and error and returns a result.
var serializeResponse: (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result<SerializedObject> { get }
}
// MARK: -
/// A generic `DataResponseSerializerType` used to serialize a request, response, and data into a serialized object.
public struct DataResponseSerializer<Value>: DataResponseSerializerProtocol {
/// The type of serialized object to be created by this `DataResponseSerializer`.
public typealias SerializedObject = Value
/// A closure used by response handlers that takes a request, response, data and error and returns a result.
public var serializeResponse: (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result<Value>
/// Initializes the `ResponseSerializer` instance with the given serialize response closure.
///
/// - parameter serializeResponse: The closure used to serialize the response.
///
/// - returns: The new generic response serializer instance.
public init(serializeResponse: @escaping (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result<Value>) {
self.serializeResponse = serializeResponse
}
}
// MARK: -
/// The type in which all download response serializers must conform to in order to serialize a response.
public protocol DownloadResponseSerializerProtocol {
/// The type of serialized object to be created by this `DownloadResponseSerializerType`.
associatedtype SerializedObject
/// A closure used by response handlers that takes a request, response, url and error and returns a result.
var serializeResponse: (URLRequest?, HTTPURLResponse?, URL?, Error?) -> Result<SerializedObject> { get }
}
// MARK: -
/// A generic `DownloadResponseSerializerType` used to serialize a request, response, and data into a serialized object.
public struct DownloadResponseSerializer<Value>: DownloadResponseSerializerProtocol {
/// The type of serialized object to be created by this `DownloadResponseSerializer`.
public typealias SerializedObject = Value
/// A closure used by response handlers that takes a request, response, url and error and returns a result.
public var serializeResponse: (URLRequest?, HTTPURLResponse?, URL?, Error?) -> Result<Value>
/// Initializes the `ResponseSerializer` instance with the given serialize response closure.
///
/// - parameter serializeResponse: The closure used to serialize the response.
///
/// - returns: The new generic response serializer instance.
public init(serializeResponse: @escaping (URLRequest?, HTTPURLResponse?, URL?, Error?) -> Result<Value>) {
self.serializeResponse = serializeResponse
}
}
// MARK: - Default
extension DataRequest {
/// Adds a handler to be called once the request has finished.
///
/// - parameter queue: The queue on which the completion handler is dispatched.
/// - parameter completionHandler: The code to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
public func response(queue: DispatchQueue? = nil, completionHandler: @escaping (DefaultDataResponse) -> Void) -> Self {
delegate.queue.addOperation {
(queue ?? DispatchQueue.main).async {
var dataResponse = DefaultDataResponse(
request: self.request,
response: self.response,
data: self.delegate.data,
error: self.delegate.error
)
dataResponse.add(self.delegate.metrics)
completionHandler(dataResponse)
}
}
return self
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter queue: The queue on which the completion handler is dispatched.
/// - parameter responseSerializer: The response serializer responsible for serializing the request, response,
/// and data.
/// - parameter completionHandler: The code to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
public func response<T: DataResponseSerializerProtocol>(
queue: DispatchQueue? = nil,
responseSerializer: T,
completionHandler: @escaping (DataResponse<T.SerializedObject>) -> Void)
-> Self
{
delegate.queue.addOperation {
let result = responseSerializer.serializeResponse(
self.request,
self.response,
self.delegate.data,
self.delegate.error
)
let requestCompletedTime = self.endTime ?? CFAbsoluteTimeGetCurrent()
let initialResponseTime = self.delegate.initialResponseTime ?? requestCompletedTime
let timeline = Timeline(
requestStartTime: self.startTime ?? CFAbsoluteTimeGetCurrent(),
initialResponseTime: initialResponseTime,
requestCompletedTime: requestCompletedTime,
serializationCompletedTime: CFAbsoluteTimeGetCurrent()
)
var dataResponse = DataResponse<T.SerializedObject>(
request: self.request,
response: self.response,
data: self.delegate.data,
result: result,
timeline: timeline
)
dataResponse.add(self.delegate.metrics)
(queue ?? DispatchQueue.main).async { completionHandler(dataResponse) }
}
return self
}
}
extension DownloadRequest {
/// Adds a handler to be called once the request has finished.
///
/// - parameter queue: The queue on which the completion handler is dispatched.
/// - parameter completionHandler: The code to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
public func response(
queue: DispatchQueue? = nil,
completionHandler: @escaping (DefaultDownloadResponse) -> Void)
-> Self
{
delegate.queue.addOperation {
(queue ?? DispatchQueue.main).async {
var downloadResponse = DefaultDownloadResponse(
request: self.request,
response: self.response,
temporaryURL: self.downloadDelegate.temporaryURL,
destinationURL: self.downloadDelegate.destinationURL,
resumeData: self.downloadDelegate.resumeData,
error: self.downloadDelegate.error
)
downloadResponse.add(self.delegate.metrics)
completionHandler(downloadResponse)
}
}
return self
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter queue: The queue on which the completion handler is dispatched.
/// - parameter responseSerializer: The response serializer responsible for serializing the request, response,
/// and data contained in the destination url.
/// - parameter completionHandler: The code to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
public func response<T: DownloadResponseSerializerProtocol>(
queue: DispatchQueue? = nil,
responseSerializer: T,
completionHandler: @escaping (DownloadResponse<T.SerializedObject>) -> Void)
-> Self
{
delegate.queue.addOperation {
let result = responseSerializer.serializeResponse(
self.request,
self.response,
self.downloadDelegate.fileURL,
self.downloadDelegate.error
)
let requestCompletedTime = self.endTime ?? CFAbsoluteTimeGetCurrent()
let initialResponseTime = self.delegate.initialResponseTime ?? requestCompletedTime
let timeline = Timeline(
requestStartTime: self.startTime ?? CFAbsoluteTimeGetCurrent(),
initialResponseTime: initialResponseTime,
requestCompletedTime: requestCompletedTime,
serializationCompletedTime: CFAbsoluteTimeGetCurrent()
)
var downloadResponse = DownloadResponse<T.SerializedObject>(
request: self.request,
response: self.response,
temporaryURL: self.downloadDelegate.temporaryURL,
destinationURL: self.downloadDelegate.destinationURL,
resumeData: self.downloadDelegate.resumeData,
result: result,
timeline: timeline
)
downloadResponse.add(self.delegate.metrics)
(queue ?? DispatchQueue.main).async { completionHandler(downloadResponse) }
}
return self
}
}
// MARK: - Data
extension Request {
/// Returns a result data type that contains the response data as-is.
///
/// - parameter response: The response from the server.
/// - parameter data: The data returned from the server.
/// - parameter error: The error already encountered if it exists.
///
/// - returns: The result data type.
public static func serializeResponseData(response: HTTPURLResponse?, data: Data?, error: Error?) -> Result<Data> {
guard error == nil else { return .failure(error!) }
if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(Data()) }
guard let validData = data else {
return .failure(AFError.responseSerializationFailed(reason: .inputDataNil))
}
return .success(validData)
}
}
extension DataRequest {
/// Creates a response serializer that returns the associated data as-is.
///
/// - returns: A data response serializer.
public static func dataResponseSerializer() -> DataResponseSerializer<Data> {
return DataResponseSerializer { _, response, data, error in
return Request.serializeResponseData(response: response, data: data, error: error)
}
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter completionHandler: The code to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
public func responseData(
queue: DispatchQueue? = nil,
completionHandler: @escaping (DataResponse<Data>) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.dataResponseSerializer(),
completionHandler: completionHandler
)
}
}
extension DownloadRequest {
/// Creates a response serializer that returns the associated data as-is.
///
/// - returns: A data response serializer.
public static func dataResponseSerializer() -> DownloadResponseSerializer<Data> {
return DownloadResponseSerializer { _, response, fileURL, error in
guard error == nil else { return .failure(error!) }
guard let fileURL = fileURL else {
return .failure(AFError.responseSerializationFailed(reason: .inputFileNil))
}
do {
let data = try Data(contentsOf: fileURL)
return Request.serializeResponseData(response: response, data: data, error: error)
} catch {
return .failure(AFError.responseSerializationFailed(reason: .inputFileReadFailed(at: fileURL)))
}
}
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter completionHandler: The code to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
public func responseData(
queue: DispatchQueue? = nil,
completionHandler: @escaping (DownloadResponse<Data>) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DownloadRequest.dataResponseSerializer(),
completionHandler: completionHandler
)
}
}
// MARK: - String
extension Request {
/// Returns a result string type initialized from the response data with the specified string encoding.
///
/// - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the server
/// response, falling back to the default HTTP default character set, ISO-8859-1.
/// - parameter response: The response from the server.
/// - parameter data: The data returned from the server.
/// - parameter error: The error already encountered if it exists.
///
/// - returns: The result data type.
public static func serializeResponseString(
encoding: String.Encoding?,
response: HTTPURLResponse?,
data: Data?,
error: Error?)
-> Result<String>
{
guard error == nil else { return .failure(error!) }
if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success("") }
guard let validData = data else {
return .failure(AFError.responseSerializationFailed(reason: .inputDataNil))
}
var convertedEncoding = encoding
if let encodingName = response?.textEncodingName as CFString!, convertedEncoding == nil {
convertedEncoding = String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(
CFStringConvertIANACharSetNameToEncoding(encodingName))
)
}
let actualEncoding = convertedEncoding ?? String.Encoding.isoLatin1
if let string = String(data: validData, encoding: actualEncoding) {
return .success(string)
} else {
return .failure(AFError.responseSerializationFailed(reason: .stringSerializationFailed(encoding: actualEncoding)))
}
}
}
extension DataRequest {
/// Creates a response serializer that returns a result string type initialized from the response data with
/// the specified string encoding.
///
/// - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the server
/// response, falling back to the default HTTP default character set, ISO-8859-1.
///
/// - returns: A string response serializer.
public static func stringResponseSerializer(encoding: String.Encoding? = nil) -> DataResponseSerializer<String> {
return DataResponseSerializer { _, response, data, error in
return Request.serializeResponseString(encoding: encoding, response: response, data: data, error: error)
}
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the
/// server response, falling back to the default HTTP default character set,
/// ISO-8859-1.
/// - parameter completionHandler: A closure to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
public func responseString(
queue: DispatchQueue? = nil,
encoding: String.Encoding? = nil,
completionHandler: @escaping (DataResponse<String>) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.stringResponseSerializer(encoding: encoding),
completionHandler: completionHandler
)
}
}
extension DownloadRequest {
/// Creates a response serializer that returns a result string type initialized from the response data with
/// the specified string encoding.
///
/// - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the server
/// response, falling back to the default HTTP default character set, ISO-8859-1.
///
/// - returns: A string response serializer.
public static func stringResponseSerializer(encoding: String.Encoding? = nil) -> DownloadResponseSerializer<String> {
return DownloadResponseSerializer { _, response, fileURL, error in
guard error == nil else { return .failure(error!) }
guard let fileURL = fileURL else {
return .failure(AFError.responseSerializationFailed(reason: .inputFileNil))
}
do {
let data = try Data(contentsOf: fileURL)
return Request.serializeResponseString(encoding: encoding, response: response, data: data, error: error)
} catch {
return .failure(AFError.responseSerializationFailed(reason: .inputFileReadFailed(at: fileURL)))
}
}
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the
/// server response, falling back to the default HTTP default character set,
/// ISO-8859-1.
/// - parameter completionHandler: A closure to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
public func responseString(
queue: DispatchQueue? = nil,
encoding: String.Encoding? = nil,
completionHandler: @escaping (DownloadResponse<String>) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DownloadRequest.stringResponseSerializer(encoding: encoding),
completionHandler: completionHandler
)
}
}
// MARK: - JSON
extension Request {
/// Returns a JSON object contained in a result type constructed from the response data using `JSONSerialization`
/// with the specified reading options.
///
/// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`.
/// - parameter response: The response from the server.
/// - parameter data: The data returned from the server.
/// - parameter error: The error already encountered if it exists.
///
/// - returns: The result data type.
public static func serializeResponseJSON(
options: JSONSerialization.ReadingOptions,
response: HTTPURLResponse?,
data: Data?,
error: Error?)
-> Result<Any>
{
guard error == nil else { return .failure(error!) }
if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(NSNull()) }
guard let validData = data, validData.count > 0 else {
return .failure(AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength))
}
do {
let json = try JSONSerialization.jsonObject(with: validData, options: options)
return .success(json)
} catch {
return .failure(AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error)))
}
}
}
extension DataRequest {
/// Creates a response serializer that returns a JSON object result type constructed from the response data using
/// `JSONSerialization` with the specified reading options.
///
/// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`.
///
/// - returns: A JSON object response serializer.
public static func jsonResponseSerializer(
options: JSONSerialization.ReadingOptions = .allowFragments)
-> DataResponseSerializer<Any>
{
return DataResponseSerializer { _, response, data, error in
return Request.serializeResponseJSON(options: options, response: response, data: data, error: error)
}
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`.
/// - parameter completionHandler: A closure to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
public func responseJSON(
queue: DispatchQueue? = nil,
options: JSONSerialization.ReadingOptions = .allowFragments,
completionHandler: @escaping (DataResponse<Any>) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.jsonResponseSerializer(options: options),
completionHandler: completionHandler
)
}
}
extension DownloadRequest {
/// Creates a response serializer that returns a JSON object result type constructed from the response data using
/// `JSONSerialization` with the specified reading options.
///
/// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`.
///
/// - returns: A JSON object response serializer.
public static func jsonResponseSerializer(
options: JSONSerialization.ReadingOptions = .allowFragments)
-> DownloadResponseSerializer<Any>
{
return DownloadResponseSerializer { _, response, fileURL, error in
guard error == nil else { return .failure(error!) }
guard let fileURL = fileURL else {
return .failure(AFError.responseSerializationFailed(reason: .inputFileNil))
}
do {
let data = try Data(contentsOf: fileURL)
return Request.serializeResponseJSON(options: options, response: response, data: data, error: error)
} catch {
return .failure(AFError.responseSerializationFailed(reason: .inputFileReadFailed(at: fileURL)))
}
}
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`.
/// - parameter completionHandler: A closure to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
public func responseJSON(
queue: DispatchQueue? = nil,
options: JSONSerialization.ReadingOptions = .allowFragments,
completionHandler: @escaping (DownloadResponse<Any>) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DownloadRequest.jsonResponseSerializer(options: options),
completionHandler: completionHandler
)
}
}
// MARK: - Property List
extension Request {
/// Returns a plist object contained in a result type constructed from the response data using
/// `PropertyListSerialization` with the specified reading options.
///
/// - parameter options: The property list reading options. Defaults to `[]`.
/// - parameter response: The response from the server.
/// - parameter data: The data returned from the server.
/// - parameter error: The error already encountered if it exists.
///
/// - returns: The result data type.
public static func serializeResponsePropertyList(
options: PropertyListSerialization.ReadOptions,
response: HTTPURLResponse?,
data: Data?,
error: Error?)
-> Result<Any>
{
guard error == nil else { return .failure(error!) }
if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(NSNull()) }
guard let validData = data, validData.count > 0 else {
return .failure(AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength))
}
do {
let plist = try PropertyListSerialization.propertyList(from: validData, options: options, format: nil)
return .success(plist)
} catch {
return .failure(AFError.responseSerializationFailed(reason: .propertyListSerializationFailed(error: error)))
}
}
}
extension DataRequest {
/// Creates a response serializer that returns an object constructed from the response data using
/// `PropertyListSerialization` with the specified reading options.
///
/// - parameter options: The property list reading options. Defaults to `[]`.
///
/// - returns: A property list object response serializer.
public static func propertyListResponseSerializer(
options: PropertyListSerialization.ReadOptions = [])
-> DataResponseSerializer<Any>
{
return DataResponseSerializer { _, response, data, error in
return Request.serializeResponsePropertyList(options: options, response: response, data: data, error: error)
}
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter options: The property list reading options. Defaults to `[]`.
/// - parameter completionHandler: A closure to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
public func responsePropertyList(
queue: DispatchQueue? = nil,
options: PropertyListSerialization.ReadOptions = [],
completionHandler: @escaping (DataResponse<Any>) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.propertyListResponseSerializer(options: options),
completionHandler: completionHandler
)
}
}
extension DownloadRequest {
/// Creates a response serializer that returns an object constructed from the response data using
/// `PropertyListSerialization` with the specified reading options.
///
/// - parameter options: The property list reading options. Defaults to `[]`.
///
/// - returns: A property list object response serializer.
public static func propertyListResponseSerializer(
options: PropertyListSerialization.ReadOptions = [])
-> DownloadResponseSerializer<Any>
{
return DownloadResponseSerializer { _, response, fileURL, error in
guard error == nil else { return .failure(error!) }
guard let fileURL = fileURL else {
return .failure(AFError.responseSerializationFailed(reason: .inputFileNil))
}
do {
let data = try Data(contentsOf: fileURL)
return Request.serializeResponsePropertyList(options: options, response: response, data: data, error: error)
} catch {
return .failure(AFError.responseSerializationFailed(reason: .inputFileReadFailed(at: fileURL)))
}
}
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter options: The property list reading options. Defaults to `[]`.
/// - parameter completionHandler: A closure to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
public func responsePropertyList(
queue: DispatchQueue? = nil,
options: PropertyListSerialization.ReadOptions = [],
completionHandler: @escaping (DownloadResponse<Any>) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DownloadRequest.propertyListResponseSerializer(options: options),
completionHandler: completionHandler
)
}
}
/// A set of HTTP response status code that do not contain response data.
private let emptyDataStatusCodes: Set<Int> = [204, 205]

View File

@@ -0,0 +1,102 @@
//
// Result.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Used to represent whether a request was successful or encountered an error.
///
/// - success: The request and all post processing operations were successful resulting in the serialization of the
/// provided associated value.
///
/// - failure: The request encountered an error resulting in a failure. The associated values are the original data
/// provided by the server as well as the error that caused the failure.
public enum Result<Value> {
case success(Value)
case failure(Error)
/// Returns `true` if the result is a success, `false` otherwise.
public var isSuccess: Bool {
switch self {
case .success:
return true
case .failure:
return false
}
}
/// Returns `true` if the result is a failure, `false` otherwise.
public var isFailure: Bool {
return !isSuccess
}
/// Returns the associated value if the result is a success, `nil` otherwise.
public var value: Value? {
switch self {
case .success(let value):
return value
case .failure:
return nil
}
}
/// Returns the associated error value if the result is a failure, `nil` otherwise.
public var error: Error? {
switch self {
case .success:
return nil
case .failure(let error):
return error
}
}
}
// MARK: - CustomStringConvertible
extension Result: CustomStringConvertible {
/// The textual representation used when written to an output stream, which includes whether the result was a
/// success or failure.
public var description: String {
switch self {
case .success:
return "SUCCESS"
case .failure:
return "FAILURE"
}
}
}
// MARK: - CustomDebugStringConvertible
extension Result: CustomDebugStringConvertible {
/// The debug textual representation used when written to an output stream, which includes whether the result was a
/// success or failure in addition to the value or error.
public var debugDescription: String {
switch self {
case .success(let value):
return "SUCCESS: \(value)"
case .failure(let error):
return "FAILURE: \(error)"
}
}
}

View File

@@ -0,0 +1,293 @@
//
// ServerTrustPolicy.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Responsible for managing the mapping of `ServerTrustPolicy` objects to a given host.
open class ServerTrustPolicyManager {
/// The dictionary of policies mapped to a particular host.
open let policies: [String: ServerTrustPolicy]
/// Initializes the `ServerTrustPolicyManager` instance with the given policies.
///
/// Since different servers and web services can have different leaf certificates, intermediate and even root
/// certficates, it is important to have the flexibility to specify evaluation policies on a per host basis. This
/// allows for scenarios such as using default evaluation for host1, certificate pinning for host2, public key
/// pinning for host3 and disabling evaluation for host4.
///
/// - parameter policies: A dictionary of all policies mapped to a particular host.
///
/// - returns: The new `ServerTrustPolicyManager` instance.
public init(policies: [String: ServerTrustPolicy]) {
self.policies = policies
}
/// Returns the `ServerTrustPolicy` for the given host if applicable.
///
/// By default, this method will return the policy that perfectly matches the given host. Subclasses could override
/// this method and implement more complex mapping implementations such as wildcards.
///
/// - parameter host: The host to use when searching for a matching policy.
///
/// - returns: The server trust policy for the given host if found.
open func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
return policies[host]
}
}
// MARK: -
extension URLSession {
private struct AssociatedKeys {
static var managerKey = "URLSession.ServerTrustPolicyManager"
}
var serverTrustPolicyManager: ServerTrustPolicyManager? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.managerKey) as? ServerTrustPolicyManager
}
set (manager) {
objc_setAssociatedObject(self, &AssociatedKeys.managerKey, manager, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
// MARK: - ServerTrustPolicy
/// The `ServerTrustPolicy` evaluates the server trust generally provided by an `NSURLAuthenticationChallenge` when
/// connecting to a server over a secure HTTPS connection. The policy configuration then evaluates the server trust
/// with a given set of criteria to determine whether the server trust is valid and the connection should be made.
///
/// Using pinned certificates or public keys for evaluation helps prevent man-in-the-middle (MITM) attacks and other
/// vulnerabilities. Applications dealing with sensitive customer data or financial information are strongly encouraged
/// to route all communication over an HTTPS connection with pinning enabled.
///
/// - performDefaultEvaluation: Uses the default server trust evaluation while allowing you to control whether to
/// validate the host provided by the challenge. Applications are encouraged to always
/// validate the host in production environments to guarantee the validity of the server's
/// certificate chain.
///
/// - pinCertificates: Uses the pinned certificates to validate the server trust. The server trust is
/// considered valid if one of the pinned certificates match one of the server certificates.
/// By validating both the certificate chain and host, certificate pinning provides a very
/// secure form of server trust validation mitigating most, if not all, MITM attacks.
/// Applications are encouraged to always validate the host and require a valid certificate
/// chain in production environments.
///
/// - pinPublicKeys: Uses the pinned public keys to validate the server trust. The server trust is considered
/// valid if one of the pinned public keys match one of the server certificate public keys.
/// By validating both the certificate chain and host, public key pinning provides a very
/// secure form of server trust validation mitigating most, if not all, MITM attacks.
/// Applications are encouraged to always validate the host and require a valid certificate
/// chain in production environments.
///
/// - disableEvaluation: Disables all evaluation which in turn will always consider any server trust as valid.
///
/// - customEvaluation: Uses the associated closure to evaluate the validity of the server trust.
public enum ServerTrustPolicy {
case performDefaultEvaluation(validateHost: Bool)
case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)
case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)
case disableEvaluation
case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool)
// MARK: - Bundle Location
/// Returns all certificates within the given bundle with a `.cer` file extension.
///
/// - parameter bundle: The bundle to search for all `.cer` files.
///
/// - returns: All certificates within the given bundle.
public static func certificates(in bundle: Bundle = Bundle.main) -> [SecCertificate] {
var certificates: [SecCertificate] = []
let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in
bundle.paths(forResourcesOfType: fileExtension, inDirectory: nil)
}.joined())
for path in paths {
if
let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData,
let certificate = SecCertificateCreateWithData(nil, certificateData)
{
certificates.append(certificate)
}
}
return certificates
}
/// Returns all public keys within the given bundle with a `.cer` file extension.
///
/// - parameter bundle: The bundle to search for all `*.cer` files.
///
/// - returns: All public keys within the given bundle.
public static func publicKeys(in bundle: Bundle = Bundle.main) -> [SecKey] {
var publicKeys: [SecKey] = []
for certificate in certificates(in: bundle) {
if let publicKey = publicKey(for: certificate) {
publicKeys.append(publicKey)
}
}
return publicKeys
}
// MARK: - Evaluation
/// Evaluates whether the server trust is valid for the given host.
///
/// - parameter serverTrust: The server trust to evaluate.
/// - parameter host: The host of the challenge protection space.
///
/// - returns: Whether the server trust is valid.
public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool {
var serverTrustIsValid = false
switch self {
case let .performDefaultEvaluation(validateHost):
let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
SecTrustSetPolicies(serverTrust, policy)
serverTrustIsValid = trustIsValid(serverTrust)
case let .pinCertificates(pinnedCertificates, validateCertificateChain, validateHost):
if validateCertificateChain {
let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
SecTrustSetPolicies(serverTrust, policy)
SecTrustSetAnchorCertificates(serverTrust, pinnedCertificates as CFArray)
SecTrustSetAnchorCertificatesOnly(serverTrust, true)
serverTrustIsValid = trustIsValid(serverTrust)
} else {
let serverCertificatesDataArray = certificateData(for: serverTrust)
let pinnedCertificatesDataArray = certificateData(for: pinnedCertificates)
outerLoop: for serverCertificateData in serverCertificatesDataArray {
for pinnedCertificateData in pinnedCertificatesDataArray {
if serverCertificateData == pinnedCertificateData {
serverTrustIsValid = true
break outerLoop
}
}
}
}
case let .pinPublicKeys(pinnedPublicKeys, validateCertificateChain, validateHost):
var certificateChainEvaluationPassed = true
if validateCertificateChain {
let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
SecTrustSetPolicies(serverTrust, policy)
certificateChainEvaluationPassed = trustIsValid(serverTrust)
}
if certificateChainEvaluationPassed {
outerLoop: for serverPublicKey in ServerTrustPolicy.publicKeys(for: serverTrust) as [AnyObject] {
for pinnedPublicKey in pinnedPublicKeys as [AnyObject] {
if serverPublicKey.isEqual(pinnedPublicKey) {
serverTrustIsValid = true
break outerLoop
}
}
}
}
case .disableEvaluation:
serverTrustIsValid = true
case let .customEvaluation(closure):
serverTrustIsValid = closure(serverTrust, host)
}
return serverTrustIsValid
}
// MARK: - Private - Trust Validation
private func trustIsValid(_ trust: SecTrust) -> Bool {
var isValid = false
var result = SecTrustResultType.invalid
let status = SecTrustEvaluate(trust, &result)
if status == errSecSuccess {
let unspecified = SecTrustResultType.unspecified
let proceed = SecTrustResultType.proceed
isValid = result == unspecified || result == proceed
}
return isValid
}
// MARK: - Private - Certificate Data
private func certificateData(for trust: SecTrust) -> [Data] {
var certificates: [SecCertificate] = []
for index in 0..<SecTrustGetCertificateCount(trust) {
if let certificate = SecTrustGetCertificateAtIndex(trust, index) {
certificates.append(certificate)
}
}
return certificateData(for: certificates)
}
private func certificateData(for certificates: [SecCertificate]) -> [Data] {
return certificates.map { SecCertificateCopyData($0) as Data }
}
// MARK: - Private - Public Key Extraction
private static func publicKeys(for trust: SecTrust) -> [SecKey] {
var publicKeys: [SecKey] = []
for index in 0..<SecTrustGetCertificateCount(trust) {
if
let certificate = SecTrustGetCertificateAtIndex(trust, index),
let publicKey = publicKey(for: certificate)
{
publicKeys.append(publicKey)
}
}
return publicKeys
}
private static func publicKey(for certificate: SecCertificate) -> SecKey? {
var publicKey: SecKey?
let policy = SecPolicyCreateBasicX509()
var trust: SecTrust?
let trustCreationStatus = SecTrustCreateWithCertificates(certificate, policy, &trust)
if let trust = trust, trustCreationStatus == errSecSuccess {
publicKey = SecTrustCopyPublicKey(trust)
}
return publicKey
}
}

View File

@@ -0,0 +1,681 @@
//
// SessionDelegate.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Responsible for handling all delegate callbacks for the underlying session.
open class SessionDelegate: NSObject {
// MARK: URLSessionDelegate Overrides
/// Overrides default behavior for URLSessionDelegate method `urlSession(_:didBecomeInvalidWithError:)`.
open var sessionDidBecomeInvalidWithError: ((URLSession, Error?) -> Void)?
/// Overrides default behavior for URLSessionDelegate method `urlSession(_:didReceive:completionHandler:)`.
open var sessionDidReceiveChallenge: ((URLSession, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
/// Overrides all behavior for URLSessionDelegate method `urlSession(_:didReceive:completionHandler:)` and requires the caller to call the `completionHandler`.
open var sessionDidReceiveChallengeWithCompletion: ((URLSession, URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)?
/// Overrides default behavior for URLSessionDelegate method `urlSessionDidFinishEvents(forBackgroundURLSession:)`.
open var sessionDidFinishEventsForBackgroundURLSession: ((URLSession) -> Void)?
// MARK: URLSessionTaskDelegate Overrides
/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)`.
open var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)?
/// Overrides all behavior for URLSessionTaskDelegate method `urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)` and
/// requires the caller to call the `completionHandler`.
open var taskWillPerformHTTPRedirectionWithCompletion: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest, (URLRequest?) -> Void) -> Void)?
/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:didReceive:completionHandler:)`.
open var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
/// Overrides all behavior for URLSessionTaskDelegate method `urlSession(_:task:didReceive:completionHandler:)` and
/// requires the caller to call the `completionHandler`.
open var taskDidReceiveChallengeWithCompletion: ((URLSession, URLSessionTask, URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)?
/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:needNewBodyStream:)`.
open var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> InputStream?)?
/// Overrides all behavior for URLSessionTaskDelegate method `urlSession(_:task:needNewBodyStream:)` and
/// requires the caller to call the `completionHandler`.
open var taskNeedNewBodyStreamWithCompletion: ((URLSession, URLSessionTask, (InputStream?) -> Void) -> Void)?
/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)`.
open var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)?
/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:didCompleteWithError:)`.
open var taskDidComplete: ((URLSession, URLSessionTask, Error?) -> Void)?
// MARK: URLSessionDataDelegate Overrides
/// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:didReceive:completionHandler:)`.
open var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)?
/// Overrides all behavior for URLSessionDataDelegate method `urlSession(_:dataTask:didReceive:completionHandler:)` and
/// requires caller to call the `completionHandler`.
open var dataTaskDidReceiveResponseWithCompletion: ((URLSession, URLSessionDataTask, URLResponse, (URLSession.ResponseDisposition) -> Void) -> Void)?
/// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:didBecome:)`.
open var dataTaskDidBecomeDownloadTask: ((URLSession, URLSessionDataTask, URLSessionDownloadTask) -> Void)?
/// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:didReceive:)`.
open var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)?
/// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:willCacheResponse:completionHandler:)`.
open var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)?
/// Overrides all behavior for URLSessionDataDelegate method `urlSession(_:dataTask:willCacheResponse:completionHandler:)` and
/// requires caller to call the `completionHandler`.
open var dataTaskWillCacheResponseWithCompletion: ((URLSession, URLSessionDataTask, CachedURLResponse, (CachedURLResponse?) -> Void) -> Void)?
// MARK: URLSessionDownloadDelegate Overrides
/// Overrides default behavior for URLSessionDownloadDelegate method `urlSession(_:downloadTask:didFinishDownloadingTo:)`.
open var downloadTaskDidFinishDownloadingToURL: ((URLSession, URLSessionDownloadTask, URL) -> Void)?
/// Overrides default behavior for URLSessionDownloadDelegate method `urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)`.
open var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)?
/// Overrides default behavior for URLSessionDownloadDelegate method `urlSession(_:downloadTask:didResumeAtOffset:expectedTotalBytes:)`.
open var downloadTaskDidResumeAtOffset: ((URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)?
// MARK: URLSessionStreamDelegate Overrides
#if !os(watchOS)
/// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:readClosedFor:)`.
open var streamTaskReadClosed: ((URLSession, URLSessionStreamTask) -> Void)?
/// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:writeClosedFor:)`.
open var streamTaskWriteClosed: ((URLSession, URLSessionStreamTask) -> Void)?
/// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:betterRouteDiscoveredFor:)`.
open var streamTaskBetterRouteDiscovered: ((URLSession, URLSessionStreamTask) -> Void)?
/// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:streamTask:didBecome:outputStream:)`.
open var streamTaskDidBecomeInputAndOutputStreams: ((URLSession, URLSessionStreamTask, InputStream, OutputStream) -> Void)?
#endif
// MARK: Properties
var retrier: RequestRetrier?
weak var sessionManager: SessionManager?
private var requests: [Int: Request] = [:]
private let lock = NSLock()
/// Access the task delegate for the specified task in a thread-safe manner.
open subscript(task: URLSessionTask) -> Request? {
get {
lock.lock() ; defer { lock.unlock() }
return requests[task.taskIdentifier]
}
set {
lock.lock() ; defer { lock.unlock() }
requests[task.taskIdentifier] = newValue
}
}
// MARK: Lifecycle
/// Initializes the `SessionDelegate` instance.
///
/// - returns: The new `SessionDelegate` instance.
public override init() {
super.init()
}
// MARK: NSObject Overrides
/// Returns a `Bool` indicating whether the `SessionDelegate` implements or inherits a method that can respond
/// to a specified message.
///
/// - parameter selector: A selector that identifies a message.
///
/// - returns: `true` if the receiver implements or inherits a method that can respond to selector, otherwise `false`.
open override func responds(to selector: Selector) -> Bool {
#if !os(OSX)
if selector == #selector(URLSessionDelegate.urlSessionDidFinishEvents(forBackgroundURLSession:)) {
return sessionDidFinishEventsForBackgroundURLSession != nil
}
#endif
#if !os(watchOS)
switch selector {
case #selector(URLSessionStreamDelegate.urlSession(_:readClosedFor:)):
return streamTaskReadClosed != nil
case #selector(URLSessionStreamDelegate.urlSession(_:writeClosedFor:)):
return streamTaskWriteClosed != nil
case #selector(URLSessionStreamDelegate.urlSession(_:betterRouteDiscoveredFor:)):
return streamTaskBetterRouteDiscovered != nil
case #selector(URLSessionStreamDelegate.urlSession(_:streamTask:didBecome:outputStream:)):
return streamTaskDidBecomeInputAndOutputStreams != nil
default:
break
}
#endif
switch selector {
case #selector(URLSessionDelegate.urlSession(_:didBecomeInvalidWithError:)):
return sessionDidBecomeInvalidWithError != nil
case #selector(URLSessionDelegate.urlSession(_:didReceive:completionHandler:)):
return (sessionDidReceiveChallenge != nil || sessionDidReceiveChallengeWithCompletion != nil)
case #selector(URLSessionTaskDelegate.urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)):
return (taskWillPerformHTTPRedirection != nil || taskWillPerformHTTPRedirectionWithCompletion != nil)
case #selector(URLSessionDataDelegate.urlSession(_:dataTask:didReceive:completionHandler:)):
return (dataTaskDidReceiveResponse != nil || dataTaskDidReceiveResponseWithCompletion != nil)
default:
return type(of: self).instancesRespond(to: selector)
}
}
}
// MARK: - URLSessionDelegate
extension SessionDelegate: URLSessionDelegate {
/// Tells the delegate that the session has been invalidated.
///
/// - parameter session: The session object that was invalidated.
/// - parameter error: The error that caused invalidation, or nil if the invalidation was explicit.
open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
sessionDidBecomeInvalidWithError?(session, error)
}
/// Requests credentials from the delegate in response to a session-level authentication request from the
/// remote server.
///
/// - parameter session: The session containing the task that requested authentication.
/// - parameter challenge: An object that contains the request for authentication.
/// - parameter completionHandler: A handler that your delegate method must call providing the disposition
/// and credential.
open func urlSession(
_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
guard sessionDidReceiveChallengeWithCompletion == nil else {
sessionDidReceiveChallengeWithCompletion?(session, challenge, completionHandler)
return
}
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?
if let sessionDidReceiveChallenge = sessionDidReceiveChallenge {
(disposition, credential) = sessionDidReceiveChallenge(session, challenge)
} else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let host = challenge.protectionSpace.host
if
let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host),
let serverTrust = challenge.protectionSpace.serverTrust
{
if serverTrustPolicy.evaluate(serverTrust, forHost: host) {
disposition = .useCredential
credential = URLCredential(trust: serverTrust)
} else {
disposition = .cancelAuthenticationChallenge
}
}
}
completionHandler(disposition, credential)
}
#if !os(OSX)
/// Tells the delegate that all messages enqueued for a session have been delivered.
///
/// - parameter session: The session that no longer has any outstanding requests.
open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
sessionDidFinishEventsForBackgroundURLSession?(session)
}
#endif
}
// MARK: - URLSessionTaskDelegate
extension SessionDelegate: URLSessionTaskDelegate {
/// Tells the delegate that the remote server requested an HTTP redirect.
///
/// - parameter session: The session containing the task whose request resulted in a redirect.
/// - parameter task: The task whose request resulted in a redirect.
/// - parameter response: An object containing the servers response to the original request.
/// - parameter request: A URL request object filled out with the new location.
/// - parameter completionHandler: A closure that your handler should call with either the value of the request
/// parameter, a modified URL request object, or NULL to refuse the redirect and
/// return the body of the redirect response.
open func urlSession(
_ session: URLSession,
task: URLSessionTask,
willPerformHTTPRedirection response: HTTPURLResponse,
newRequest request: URLRequest,
completionHandler: @escaping (URLRequest?) -> Void)
{
guard taskWillPerformHTTPRedirectionWithCompletion == nil else {
taskWillPerformHTTPRedirectionWithCompletion?(session, task, response, request, completionHandler)
return
}
var redirectRequest: URLRequest? = request
if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection {
redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request)
}
completionHandler(redirectRequest)
}
/// Requests credentials from the delegate in response to an authentication request from the remote server.
///
/// - parameter session: The session containing the task whose request requires authentication.
/// - parameter task: The task whose request requires authentication.
/// - parameter challenge: An object that contains the request for authentication.
/// - parameter completionHandler: A handler that your delegate method must call providing the disposition
/// and credential.
open func urlSession(
_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
guard taskDidReceiveChallengeWithCompletion == nil else {
taskDidReceiveChallengeWithCompletion?(session, task, challenge, completionHandler)
return
}
if let taskDidReceiveChallenge = taskDidReceiveChallenge {
let result = taskDidReceiveChallenge(session, task, challenge)
completionHandler(result.0, result.1)
} else if let delegate = self[task]?.delegate {
delegate.urlSession(
session,
task: task,
didReceive: challenge,
completionHandler: completionHandler
)
} else {
urlSession(session, didReceive: challenge, completionHandler: completionHandler)
}
}
/// Tells the delegate when a task requires a new request body stream to send to the remote server.
///
/// - parameter session: The session containing the task that needs a new body stream.
/// - parameter task: The task that needs a new body stream.
/// - parameter completionHandler: A completion handler that your delegate method should call with the new body stream.
open func urlSession(
_ session: URLSession,
task: URLSessionTask,
needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
{
guard taskNeedNewBodyStreamWithCompletion == nil else {
taskNeedNewBodyStreamWithCompletion?(session, task, completionHandler)
return
}
if let taskNeedNewBodyStream = taskNeedNewBodyStream {
completionHandler(taskNeedNewBodyStream(session, task))
} else if let delegate = self[task]?.delegate {
delegate.urlSession(session, task: task, needNewBodyStream: completionHandler)
}
}
/// Periodically informs the delegate of the progress of sending body content to the server.
///
/// - parameter session: The session containing the data task.
/// - parameter task: The data task.
/// - parameter bytesSent: The number of bytes sent since the last time this delegate method was called.
/// - parameter totalBytesSent: The total number of bytes sent so far.
/// - parameter totalBytesExpectedToSend: The expected length of the body data.
open func urlSession(
_ session: URLSession,
task: URLSessionTask,
didSendBodyData bytesSent: Int64,
totalBytesSent: Int64,
totalBytesExpectedToSend: Int64)
{
if let taskDidSendBodyData = taskDidSendBodyData {
taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend)
} else if let delegate = self[task]?.delegate as? UploadTaskDelegate {
delegate.URLSession(
session,
task: task,
didSendBodyData: bytesSent,
totalBytesSent: totalBytesSent,
totalBytesExpectedToSend: totalBytesExpectedToSend
)
}
}
#if !os(watchOS)
/// Tells the delegate that the session finished collecting metrics for the task.
///
/// - parameter session: The session collecting the metrics.
/// - parameter task: The task whose metrics have been collected.
/// - parameter metrics: The collected metrics.
@available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
@objc(URLSession:task:didFinishCollectingMetrics:)
open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
self[task]?.delegate.metrics = metrics
}
#endif
/// Tells the delegate that the task finished transferring data.
///
/// - parameter session: The session containing the task whose request finished transferring data.
/// - parameter task: The task whose request finished transferring data.
/// - parameter error: If an error occurred, an error object indicating how the transfer failed, otherwise nil.
open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
/// Executed after it is determined that the request is not going to be retried
let completeTask: (URLSession, URLSessionTask, Error?) -> Void = { [weak self] session, task, error in
guard let strongSelf = self else { return }
if let taskDidComplete = strongSelf.taskDidComplete {
taskDidComplete(session, task, error)
} else if let delegate = strongSelf[task]?.delegate {
delegate.urlSession(session, task: task, didCompleteWithError: error)
}
NotificationCenter.default.post(
name: Notification.Name.Task.DidComplete,
object: strongSelf,
userInfo: [Notification.Key.Task: task]
)
strongSelf[task] = nil
}
guard let request = self[task], let sessionManager = sessionManager else {
completeTask(session, task, error)
return
}
// Run all validations on the request before checking if an error occurred
request.validations.forEach { $0() }
// Determine whether an error has occurred
var error: Error? = error
if let taskDelegate = self[task]?.delegate, taskDelegate.error != nil {
error = taskDelegate.error
}
/// If an error occurred and the retrier is set, asynchronously ask the retrier if the request
/// should be retried. Otherwise, complete the task by notifying the task delegate.
if let retrier = retrier, let error = error {
retrier.should(sessionManager, retry: request, with: error) { [weak self] shouldRetry, delay in
guard shouldRetry else { completeTask(session, task, error) ; return }
DispatchQueue.utility.after(delay) { [weak self] in
guard let strongSelf = self else { return }
let retrySucceeded = strongSelf.sessionManager?.retry(request) ?? false
if retrySucceeded, let task = request.task {
strongSelf[task] = request
return
} else {
completeTask(session, task, error)
}
}
}
} else {
completeTask(session, task, error)
}
}
}
// MARK: - URLSessionDataDelegate
extension SessionDelegate: URLSessionDataDelegate {
/// Tells the delegate that the data task received the initial reply (headers) from the server.
///
/// - parameter session: The session containing the data task that received an initial reply.
/// - parameter dataTask: The data task that received an initial reply.
/// - parameter response: A URL response object populated with headers.
/// - parameter completionHandler: A completion handler that your code calls to continue the transfer, passing a
/// constant to indicate whether the transfer should continue as a data task or
/// should become a download task.
open func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive response: URLResponse,
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void)
{
guard dataTaskDidReceiveResponseWithCompletion == nil else {
dataTaskDidReceiveResponseWithCompletion?(session, dataTask, response, completionHandler)
return
}
var disposition: URLSession.ResponseDisposition = .allow
if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse {
disposition = dataTaskDidReceiveResponse(session, dataTask, response)
}
completionHandler(disposition)
}
/// Tells the delegate that the data task was changed to a download task.
///
/// - parameter session: The session containing the task that was replaced by a download task.
/// - parameter dataTask: The data task that was replaced by a download task.
/// - parameter downloadTask: The new download task that replaced the data task.
open func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
didBecome downloadTask: URLSessionDownloadTask)
{
if let dataTaskDidBecomeDownloadTask = dataTaskDidBecomeDownloadTask {
dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask)
} else {
self[downloadTask]?.delegate = DownloadTaskDelegate(task: downloadTask)
}
}
/// Tells the delegate that the data task has received some of the expected data.
///
/// - parameter session: The session containing the data task that provided data.
/// - parameter dataTask: The data task that provided data.
/// - parameter data: A data object containing the transferred data.
open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if let dataTaskDidReceiveData = dataTaskDidReceiveData {
dataTaskDidReceiveData(session, dataTask, data)
} else if let delegate = self[dataTask]?.delegate as? DataTaskDelegate {
delegate.urlSession(session, dataTask: dataTask, didReceive: data)
}
}
/// Asks the delegate whether the data (or upload) task should store the response in the cache.
///
/// - parameter session: The session containing the data (or upload) task.
/// - parameter dataTask: The data (or upload) task.
/// - parameter proposedResponse: The default caching behavior. This behavior is determined based on the current
/// caching policy and the values of certain received headers, such as the Pragma
/// and Cache-Control headers.
/// - parameter completionHandler: A block that your handler must call, providing either the original proposed
/// response, a modified version of that response, or NULL to prevent caching the
/// response. If your delegate implements this method, it must call this completion
/// handler; otherwise, your app leaks memory.
open func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
willCacheResponse proposedResponse: CachedURLResponse,
completionHandler: @escaping (CachedURLResponse?) -> Void)
{
guard dataTaskWillCacheResponseWithCompletion == nil else {
dataTaskWillCacheResponseWithCompletion?(session, dataTask, proposedResponse, completionHandler)
return
}
if let dataTaskWillCacheResponse = dataTaskWillCacheResponse {
completionHandler(dataTaskWillCacheResponse(session, dataTask, proposedResponse))
} else if let delegate = self[dataTask]?.delegate as? DataTaskDelegate {
delegate.urlSession(
session,
dataTask: dataTask,
willCacheResponse: proposedResponse,
completionHandler: completionHandler
)
} else {
completionHandler(proposedResponse)
}
}
}
// MARK: - URLSessionDownloadDelegate
extension SessionDelegate: URLSessionDownloadDelegate {
/// Tells the delegate that a download task has finished downloading.
///
/// - parameter session: The session containing the download task that finished.
/// - parameter downloadTask: The download task that finished.
/// - parameter location: A file URL for the temporary file. Because the file is temporary, you must either
/// open the file for reading or move it to a permanent location in your apps sandbox
/// container directory before returning from this delegate method.
open func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL)
{
if let downloadTaskDidFinishDownloadingToURL = downloadTaskDidFinishDownloadingToURL {
downloadTaskDidFinishDownloadingToURL(session, downloadTask, location)
} else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate {
delegate.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
}
}
/// Periodically informs the delegate about the downloads progress.
///
/// - parameter session: The session containing the download task.
/// - parameter downloadTask: The download task.
/// - parameter bytesWritten: The number of bytes transferred since the last time this delegate
/// method was called.
/// - parameter totalBytesWritten: The total number of bytes transferred so far.
/// - parameter totalBytesExpectedToWrite: The expected length of the file, as provided by the Content-Length
/// header. If this header was not provided, the value is
/// `NSURLSessionTransferSizeUnknown`.
open func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64)
{
if let downloadTaskDidWriteData = downloadTaskDidWriteData {
downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
} else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate {
delegate.urlSession(
session,
downloadTask: downloadTask,
didWriteData: bytesWritten,
totalBytesWritten: totalBytesWritten,
totalBytesExpectedToWrite: totalBytesExpectedToWrite
)
}
}
/// Tells the delegate that the download task has resumed downloading.
///
/// - parameter session: The session containing the download task that finished.
/// - parameter downloadTask: The download task that resumed. See explanation in the discussion.
/// - parameter fileOffset: If the file's cache policy or last modified date prevents reuse of the
/// existing content, then this value is zero. Otherwise, this value is an
/// integer representing the number of bytes on disk that do not need to be
/// retrieved again.
/// - parameter expectedTotalBytes: The expected length of the file, as provided by the Content-Length header.
/// If this header was not provided, the value is NSURLSessionTransferSizeUnknown.
open func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didResumeAtOffset fileOffset: Int64,
expectedTotalBytes: Int64)
{
if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset {
downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes)
} else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate {
delegate.urlSession(
session,
downloadTask: downloadTask,
didResumeAtOffset: fileOffset,
expectedTotalBytes: expectedTotalBytes
)
}
}
}
// MARK: - URLSessionStreamDelegate
#if !os(watchOS)
extension SessionDelegate: URLSessionStreamDelegate {
/// Tells the delegate that the read side of the connection has been closed.
///
/// - parameter session: The session.
/// - parameter streamTask: The stream task.
open func urlSession(_ session: URLSession, readClosedFor streamTask: URLSessionStreamTask) {
streamTaskReadClosed?(session, streamTask)
}
/// Tells the delegate that the write side of the connection has been closed.
///
/// - parameter session: The session.
/// - parameter streamTask: The stream task.
open func urlSession(_ session: URLSession, writeClosedFor streamTask: URLSessionStreamTask) {
streamTaskWriteClosed?(session, streamTask)
}
/// Tells the delegate that the system has determined that a better route to the host is available.
///
/// - parameter session: The session.
/// - parameter streamTask: The stream task.
open func urlSession(_ session: URLSession, betterRouteDiscoveredFor streamTask: URLSessionStreamTask) {
streamTaskBetterRouteDiscovered?(session, streamTask)
}
/// Tells the delegate that the stream task has been completed and provides the unopened stream objects.
///
/// - parameter session: The session.
/// - parameter streamTask: The stream task.
/// - parameter inputStream: The new input stream.
/// - parameter outputStream: The new output stream.
open func urlSession(
_ session: URLSession,
streamTask: URLSessionStreamTask,
didBecome inputStream: InputStream,
outputStream: OutputStream)
{
streamTaskDidBecomeInputAndOutputStreams?(session, streamTask, inputStream, outputStream)
}
}
#endif

View File

@@ -0,0 +1,776 @@
//
// SessionManager.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Responsible for creating and managing `Request` objects, as well as their underlying `NSURLSession`.
open class SessionManager {
// MARK: - Helper Types
/// Defines whether the `MultipartFormData` encoding was successful and contains result of the encoding as
/// associated values.
///
/// - Success: Represents a successful `MultipartFormData` encoding and contains the new `UploadRequest` along with
/// streaming information.
/// - Failure: Used to represent a failure in the `MultipartFormData` encoding and also contains the encoding
/// error.
public enum MultipartFormDataEncodingResult {
case success(request: UploadRequest, streamingFromDisk: Bool, streamFileURL: URL?)
case failure(Error)
}
// MARK: - Properties
/// A default instance of `SessionManager`, used by top-level Alamofire request methods, and suitable for use
/// directly for any ad hoc requests.
open static let `default`: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
/// Creates default values for the "Accept-Encoding", "Accept-Language" and "User-Agent" headers.
open static let defaultHTTPHeaders: HTTPHeaders = {
// Accept-Encoding HTTP Header; see https://tools.ietf.org/html/rfc7230#section-4.2.3
let acceptEncoding: String = "gzip;q=1.0, compress;q=0.5"
// Accept-Language HTTP Header; see https://tools.ietf.org/html/rfc7231#section-5.3.5
let acceptLanguage = Locale.preferredLanguages.prefix(6).enumerated().map { index, languageCode in
let quality = 1.0 - (Double(index) * 0.1)
return "\(languageCode);q=\(quality)"
}.joined(separator: ", ")
// User-Agent Header; see https://tools.ietf.org/html/rfc7231#section-5.5.3
// Example: `iOS Example/1.0 (com.alamofire.iOS-Example; build:1; iOS 9.3.0) Alamofire/3.4.2`
let userAgent: String = {
if let info = Bundle.main.infoDictionary {
let executable = info[kCFBundleExecutableKey as String] as? String ?? "Unknown"
let bundle = info[kCFBundleIdentifierKey as String] as? String ?? "Unknown"
let appVersion = info["CFBundleShortVersionString"] as? String ?? "Unknown"
let appBuild = info[kCFBundleVersionKey as String] as? String ?? "Unknown"
let osNameVersion: String = {
let version = ProcessInfo.processInfo.operatingSystemVersion
let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"
let osName: String = {
#if os(iOS)
return "iOS"
#elseif os(watchOS)
return "watchOS"
#elseif os(tvOS)
return "tvOS"
#elseif os(OSX)
return "OS X"
#elseif os(Linux)
return "Linux"
#else
return "Unknown"
#endif
}()
return "\(osName) \(versionString)"
}()
let alamofireVersion: String = {
guard
let afInfo = Bundle(for: SessionManager.self).infoDictionary,
let build = afInfo["CFBundleShortVersionString"]
else { return "Unknown" }
return "Alamofire/\(build)"
}()
return "\(executable)/\(appVersion) (\(bundle); build:\(appBuild); \(osNameVersion)) \(alamofireVersion)"
}
return "Alamofire"
}()
return [
"Accept-Encoding": acceptEncoding,
"Accept-Language": acceptLanguage,
"User-Agent": userAgent
]
}()
/// Default memory threshold used when encoding `MultipartFormData` in bytes.
open static let multipartFormDataEncodingMemoryThreshold: UInt64 = 10_000_000
/// The underlying session.
open let session: URLSession
/// The session delegate handling all the task and session delegate callbacks.
open let delegate: SessionDelegate
/// Whether to start requests immediately after being constructed. `true` by default.
open var startRequestsImmediately: Bool = true
/// The request adapter called each time a new request is created.
open var adapter: RequestAdapter?
/// The request retrier called each time a request encounters an error to determine whether to retry the request.
open var retrier: RequestRetrier? {
get { return delegate.retrier }
set { delegate.retrier = newValue }
}
/// The background completion handler closure provided by the UIApplicationDelegate
/// `application:handleEventsForBackgroundURLSession:completionHandler:` method. By setting the background
/// completion handler, the SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` closure implementation
/// will automatically call the handler.
///
/// If you need to handle your own events before the handler is called, then you need to override the
/// SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` and manually call the handler when finished.
///
/// `nil` by default.
open var backgroundCompletionHandler: (() -> Void)?
let queue = DispatchQueue(label: "org.alamofire.session-manager." + UUID().uuidString)
// MARK: - Lifecycle
/// Creates an instance with the specified `configuration`, `delegate` and `serverTrustPolicyManager`.
///
/// - parameter configuration: The configuration used to construct the managed session.
/// `URLSessionConfiguration.default` by default.
/// - parameter delegate: The delegate used when initializing the session. `SessionDelegate()` by
/// default.
/// - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust
/// challenges. `nil` by default.
///
/// - returns: The new `SessionManager` instance.
public init(
configuration: URLSessionConfiguration = URLSessionConfiguration.default,
delegate: SessionDelegate = SessionDelegate(),
serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
{
self.delegate = delegate
self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)
commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
}
/// Creates an instance with the specified `session`, `delegate` and `serverTrustPolicyManager`.
///
/// - parameter session: The URL session.
/// - parameter delegate: The delegate of the URL session. Must equal the URL session's delegate.
/// - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust
/// challenges. `nil` by default.
///
/// - returns: The new `SessionManager` instance if the URL session's delegate matches; `nil` otherwise.
public init?(
session: URLSession,
delegate: SessionDelegate,
serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
{
guard delegate === session.delegate else { return nil }
self.delegate = delegate
self.session = session
commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
}
private func commonInit(serverTrustPolicyManager: ServerTrustPolicyManager?) {
session.serverTrustPolicyManager = serverTrustPolicyManager
delegate.sessionManager = self
delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
guard let strongSelf = self else { return }
DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }
}
}
deinit {
session.invalidateAndCancel()
}
// MARK: - Data Request
/// Creates a `DataRequest` to retrieve the contents of the specified `url`, `method`, `parameters`, `encoding`
/// and `headers`.
///
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.get` by default.
/// - parameter parameters: The parameters. `nil` by default.
/// - parameter encoding: The parameter encoding. `URLEncoding.default` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
///
/// - returns: The created `DataRequest`.
@discardableResult
open func request(
_ url: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil)
-> DataRequest
{
do {
let urlRequest = try URLRequest(url: url, method: method, headers: headers)
let encodedURLRequest = try encoding.encode(urlRequest, with: parameters)
return request(encodedURLRequest)
} catch {
return request(failedWith: error)
}
}
/// Creates a `DataRequest` to retrieve the contents of a URL based on the specified `urlRequest`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter urlRequest: The URL request.
///
/// - returns: The created `DataRequest`.
open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
do {
let originalRequest = try urlRequest.asURLRequest()
let originalTask = DataRequest.Requestable(urlRequest: originalRequest)
let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
let request = DataRequest(session: session, requestTask: .data(originalTask, task))
delegate[task] = request
if startRequestsImmediately { request.resume() }
return request
} catch {
return request(failedWith: error)
}
}
// MARK: Private - Request Implementation
private func request(failedWith error: Error) -> DataRequest {
let request = DataRequest(session: session, requestTask: .data(nil, nil), error: error)
if startRequestsImmediately { request.resume() }
return request
}
// MARK: - Download Request
// MARK: URL Request
/// Creates a `DownloadRequest` to retrieve the contents the specified `url`, `method`, `parameters`, `encoding`,
/// `headers` and save them to the `destination`.
///
/// If `destination` is not specified, the contents will remain in the temporary location determined by the
/// underlying URL session.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.get` by default.
/// - parameter parameters: The parameters. `nil` by default.
/// - parameter encoding: The parameter encoding. `URLEncoding.default` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
/// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.
///
/// - returns: The created `DownloadRequest`.
@discardableResult
open func download(
_ url: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil,
to destination: DownloadRequest.DownloadFileDestination? = nil)
-> DownloadRequest
{
do {
let urlRequest = try URLRequest(url: url, method: method, headers: headers)
let encodedURLRequest = try encoding.encode(urlRequest, with: parameters)
return download(encodedURLRequest, to: destination)
} catch {
return download(failedWith: error)
}
}
/// Creates a `DownloadRequest` to retrieve the contents of a URL based on the specified `urlRequest` and save
/// them to the `destination`.
///
/// If `destination` is not specified, the contents will remain in the temporary location determined by the
/// underlying URL session.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter urlRequest: The URL request
/// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.
///
/// - returns: The created `DownloadRequest`.
@discardableResult
open func download(
_ urlRequest: URLRequestConvertible,
to destination: DownloadRequest.DownloadFileDestination? = nil)
-> DownloadRequest
{
do {
let urlRequest = try urlRequest.asURLRequest()
return download(.request(urlRequest), to: destination)
} catch {
return download(failedWith: error)
}
}
// MARK: Resume Data
/// Creates a `DownloadRequest` from the `resumeData` produced from a previous request cancellation to retrieve
/// the contents of the original request and save them to the `destination`.
///
/// If `destination` is not specified, the contents will remain in the temporary location determined by the
/// underlying URL session.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter resumeData: The resume data. This is an opaque data blob produced by `URLSessionDownloadTask`
/// when a task is cancelled. See `URLSession -downloadTask(withResumeData:)` for
/// additional information.
/// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.
///
/// - returns: The created `DownloadRequest`.
@discardableResult
open func download(
resumingWith resumeData: Data,
to destination: DownloadRequest.DownloadFileDestination? = nil)
-> DownloadRequest
{
return download(.resumeData(resumeData), to: destination)
}
// MARK: Private - Download Implementation
private func download(
_ downloadable: DownloadRequest.Downloadable,
to destination: DownloadRequest.DownloadFileDestination?)
-> DownloadRequest
{
do {
let task = try downloadable.task(session: session, adapter: adapter, queue: queue)
let request = DownloadRequest(session: session, requestTask: .download(downloadable, task))
request.downloadDelegate.destination = destination
delegate[task] = request
if startRequestsImmediately { request.resume() }
return request
} catch {
return download(failedWith: error)
}
}
private func download(failedWith error: Error) -> DownloadRequest {
let download = DownloadRequest(session: session, requestTask: .download(nil, nil), error: error)
if startRequestsImmediately { download.resume() }
return download
}
// MARK: - Upload Request
// MARK: File
/// Creates an `UploadRequest` from the specified `url`, `method` and `headers` for uploading the `file`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter file: The file to upload.
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.post` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
///
/// - returns: The created `UploadRequest`.
@discardableResult
open func upload(
_ fileURL: URL,
to url: URLConvertible,
method: HTTPMethod = .post,
headers: HTTPHeaders? = nil)
-> UploadRequest
{
do {
let urlRequest = try URLRequest(url: url, method: method, headers: headers)
return upload(fileURL, with: urlRequest)
} catch {
return upload(failedWith: error)
}
}
/// Creates a `UploadRequest` from the specified `urlRequest` for uploading the `file`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter file: The file to upload.
/// - parameter urlRequest: The URL request.
///
/// - returns: The created `UploadRequest`.
@discardableResult
open func upload(_ fileURL: URL, with urlRequest: URLRequestConvertible) -> UploadRequest {
do {
let urlRequest = try urlRequest.asURLRequest()
return upload(.file(fileURL, urlRequest))
} catch {
return upload(failedWith: error)
}
}
// MARK: Data
/// Creates an `UploadRequest` from the specified `url`, `method` and `headers` for uploading the `data`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter data: The data to upload.
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.post` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
///
/// - returns: The created `UploadRequest`.
@discardableResult
open func upload(
_ data: Data,
to url: URLConvertible,
method: HTTPMethod = .post,
headers: HTTPHeaders? = nil)
-> UploadRequest
{
do {
let urlRequest = try URLRequest(url: url, method: method, headers: headers)
return upload(data, with: urlRequest)
} catch {
return upload(failedWith: error)
}
}
/// Creates an `UploadRequest` from the specified `urlRequest` for uploading the `data`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter data: The data to upload.
/// - parameter urlRequest: The URL request.
///
/// - returns: The created `UploadRequest`.
@discardableResult
open func upload(_ data: Data, with urlRequest: URLRequestConvertible) -> UploadRequest {
do {
let urlRequest = try urlRequest.asURLRequest()
return upload(.data(data, urlRequest))
} catch {
return upload(failedWith: error)
}
}
// MARK: InputStream
/// Creates an `UploadRequest` from the specified `url`, `method` and `headers` for uploading the `stream`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter stream: The stream to upload.
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.post` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
///
/// - returns: The created `UploadRequest`.
@discardableResult
open func upload(
_ stream: InputStream,
to url: URLConvertible,
method: HTTPMethod = .post,
headers: HTTPHeaders? = nil)
-> UploadRequest
{
do {
let urlRequest = try URLRequest(url: url, method: method, headers: headers)
return upload(stream, with: urlRequest)
} catch {
return upload(failedWith: error)
}
}
/// Creates an `UploadRequest` from the specified `urlRequest` for uploading the `stream`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter stream: The stream to upload.
/// - parameter urlRequest: The URL request.
///
/// - returns: The created `UploadRequest`.
@discardableResult
open func upload(_ stream: InputStream, with urlRequest: URLRequestConvertible) -> UploadRequest {
do {
let urlRequest = try urlRequest.asURLRequest()
return upload(.stream(stream, urlRequest))
} catch {
return upload(failedWith: error)
}
}
// MARK: MultipartFormData
/// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new
/// `UploadRequest` using the `url`, `method` and `headers`.
///
/// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
/// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
/// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
/// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
/// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
/// used for larger payloads such as video content.
///
/// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
/// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
/// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
/// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
/// technique was used.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
/// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
/// `multipartFormDataEncodingMemoryThreshold` by default.
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.post` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
/// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
open func upload(
multipartFormData: @escaping (MultipartFormData) -> Void,
usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
to url: URLConvertible,
method: HTTPMethod = .post,
headers: HTTPHeaders? = nil,
encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
{
do {
let urlRequest = try URLRequest(url: url, method: method, headers: headers)
return upload(
multipartFormData: multipartFormData,
usingThreshold: encodingMemoryThreshold,
with: urlRequest,
encodingCompletion: encodingCompletion
)
} catch {
DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
}
}
/// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new
/// `UploadRequest` using the `urlRequest`.
///
/// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
/// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
/// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
/// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
/// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
/// used for larger payloads such as video content.
///
/// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
/// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
/// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
/// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
/// technique was used.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
/// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
/// `multipartFormDataEncodingMemoryThreshold` by default.
/// - parameter urlRequest: The URL request.
/// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
open func upload(
multipartFormData: @escaping (MultipartFormData) -> Void,
usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
with urlRequest: URLRequestConvertible,
encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
{
DispatchQueue.global(qos: .utility).async {
let formData = MultipartFormData()
multipartFormData(formData)
do {
var urlRequestWithContentType = try urlRequest.asURLRequest()
urlRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type")
let isBackgroundSession = self.session.configuration.identifier != nil
if formData.contentLength < encodingMemoryThreshold && !isBackgroundSession {
let data = try formData.encode()
let encodingResult = MultipartFormDataEncodingResult.success(
request: self.upload(data, with: urlRequestWithContentType),
streamingFromDisk: false,
streamFileURL: nil
)
DispatchQueue.main.async { encodingCompletion?(encodingResult) }
} else {
let fileManager = FileManager.default
let tempDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory())
let directoryURL = tempDirectoryURL.appendingPathComponent("org.alamofire.manager/multipart.form.data")
let fileName = UUID().uuidString
let fileURL = directoryURL.appendingPathComponent(fileName)
var directoryError: Error?
// Create directory inside serial queue to ensure two threads don't do this in parallel
self.queue.sync {
do {
try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
} catch {
directoryError = error
}
}
if let directoryError = directoryError { throw directoryError }
try formData.writeEncodedData(to: fileURL)
DispatchQueue.main.async {
let encodingResult = MultipartFormDataEncodingResult.success(
request: self.upload(fileURL, with: urlRequestWithContentType),
streamingFromDisk: true,
streamFileURL: fileURL
)
encodingCompletion?(encodingResult)
}
}
} catch {
DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
}
}
}
// MARK: Private - Upload Implementation
private func upload(_ uploadable: UploadRequest.Uploadable) -> UploadRequest {
do {
let task = try uploadable.task(session: session, adapter: adapter, queue: queue)
let upload = UploadRequest(session: session, requestTask: .upload(uploadable, task))
if case let .stream(inputStream, _) = uploadable {
upload.delegate.taskNeedNewBodyStream = { _, _ in inputStream }
}
delegate[task] = upload
if startRequestsImmediately { upload.resume() }
return upload
} catch {
return upload(failedWith: error)
}
}
private func upload(failedWith error: Error) -> UploadRequest {
let upload = UploadRequest(session: session, requestTask: .upload(nil, nil), error: error)
if startRequestsImmediately { upload.resume() }
return upload
}
#if !os(watchOS)
// MARK: - Stream Request
// MARK: Hostname and Port
/// Creates a `StreamRequest` for bidirectional streaming using the `hostname` and `port`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter hostName: The hostname of the server to connect to.
/// - parameter port: The port of the server to connect to.
///
/// - returns: The created `StreamRequest`.
@discardableResult
open func stream(withHostName hostName: String, port: Int) -> StreamRequest {
return stream(.stream(hostName: hostName, port: port))
}
// MARK: NetService
/// Creates a `StreamRequest` for bidirectional streaming using the `netService`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter netService: The net service used to identify the endpoint.
///
/// - returns: The created `StreamRequest`.
@discardableResult
open func stream(with netService: NetService) -> StreamRequest {
return stream(.netService(netService))
}
// MARK: Private - Stream Implementation
private func stream(_ streamable: StreamRequest.Streamable) -> StreamRequest {
do {
let task = try streamable.task(session: session, adapter: adapter, queue: queue)
let request = StreamRequest(session: session, requestTask: .stream(streamable, task))
delegate[task] = request
if startRequestsImmediately { request.resume() }
return request
} catch {
return stream(failedWith: error)
}
}
private func stream(failedWith error: Error) -> StreamRequest {
let stream = StreamRequest(session: session, requestTask: .stream(nil, nil), error: error)
if startRequestsImmediately { stream.resume() }
return stream
}
#endif
// MARK: - Internal - Retry Request
func retry(_ request: Request) -> Bool {
guard let originalTask = request.originalTask else { return false }
do {
let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
request.delegate.task = task // resets all task delegate data
request.startTime = CFAbsoluteTimeGetCurrent()
request.endTime = nil
task.resume()
return true
} catch {
request.delegate.error = error
return false
}
}
}

View File

@@ -0,0 +1,448 @@
//
// TaskDelegate.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// The task delegate is responsible for handling all delegate callbacks for the underlying task as well as
/// executing all operations attached to the serial operation queue upon task completion.
open class TaskDelegate: NSObject {
// MARK: Properties
/// The serial operation queue used to execute all operations after the task completes.
open let queue: OperationQueue
var task: URLSessionTask? {
didSet { reset() }
}
var data: Data? { return nil }
var error: Error?
var initialResponseTime: CFAbsoluteTime?
var credential: URLCredential?
var metrics: AnyObject? // URLSessionTaskMetrics
// MARK: Lifecycle
init(task: URLSessionTask?) {
self.task = task
self.queue = {
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 1
operationQueue.isSuspended = true
operationQueue.qualityOfService = .utility
return operationQueue
}()
}
func reset() {
error = nil
initialResponseTime = nil
}
// MARK: URLSessionTaskDelegate
var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)?
var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> InputStream?)?
var taskDidCompleteWithError: ((URLSession, URLSessionTask, Error?) -> Void)?
@objc(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)
func urlSession(
_ session: URLSession,
task: URLSessionTask,
willPerformHTTPRedirection response: HTTPURLResponse,
newRequest request: URLRequest,
completionHandler: @escaping (URLRequest?) -> Void)
{
var redirectRequest: URLRequest? = request
if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection {
redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request)
}
completionHandler(redirectRequest)
}
@objc(URLSession:task:didReceiveChallenge:completionHandler:)
func urlSession(
_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?
if let taskDidReceiveChallenge = taskDidReceiveChallenge {
(disposition, credential) = taskDidReceiveChallenge(session, task, challenge)
} else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let host = challenge.protectionSpace.host
if let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host),
let serverTrust = challenge.protectionSpace.serverTrust
{
if serverTrustPolicy.evaluate(serverTrust, forHost: host) {
disposition = .useCredential
credential = URLCredential(trust: serverTrust)
} else {
disposition = .cancelAuthenticationChallenge
}
}
} else {
if challenge.previousFailureCount > 0 {
disposition = .rejectProtectionSpace
} else {
credential = self.credential ?? session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)
if credential != nil {
disposition = .useCredential
}
}
}
completionHandler(disposition, credential)
}
@objc(URLSession:task:needNewBodyStream:)
func urlSession(
_ session: URLSession,
task: URLSessionTask,
needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
{
var bodyStream: InputStream?
if let taskNeedNewBodyStream = taskNeedNewBodyStream {
bodyStream = taskNeedNewBodyStream(session, task)
}
completionHandler(bodyStream)
}
@objc(URLSession:task:didCompleteWithError:)
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let taskDidCompleteWithError = taskDidCompleteWithError {
taskDidCompleteWithError(session, task, error)
} else {
if let error = error {
if self.error == nil { self.error = error }
if
let downloadDelegate = self as? DownloadTaskDelegate,
let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data
{
downloadDelegate.resumeData = resumeData
}
}
queue.isSuspended = false
}
}
}
// MARK: -
class DataTaskDelegate: TaskDelegate, URLSessionDataDelegate {
// MARK: Properties
var dataTask: URLSessionDataTask { return task as! URLSessionDataTask }
override var data: Data? {
if dataStream != nil {
return nil
} else {
return mutableData
}
}
var progress: Progress
var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
var dataStream: ((_ data: Data) -> Void)?
private var totalBytesReceived: Int64 = 0
private var mutableData: Data
private var expectedContentLength: Int64?
// MARK: Lifecycle
override init(task: URLSessionTask?) {
mutableData = Data()
progress = Progress(totalUnitCount: 0)
super.init(task: task)
}
override func reset() {
super.reset()
progress = Progress(totalUnitCount: 0)
totalBytesReceived = 0
mutableData = Data()
expectedContentLength = nil
}
// MARK: URLSessionDataDelegate
var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)?
var dataTaskDidBecomeDownloadTask: ((URLSession, URLSessionDataTask, URLSessionDownloadTask) -> Void)?
var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)?
var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)?
func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive response: URLResponse,
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void)
{
var disposition: URLSession.ResponseDisposition = .allow
expectedContentLength = response.expectedContentLength
if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse {
disposition = dataTaskDidReceiveResponse(session, dataTask, response)
}
completionHandler(disposition)
}
func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
didBecome downloadTask: URLSessionDownloadTask)
{
dataTaskDidBecomeDownloadTask?(session, dataTask, downloadTask)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
if let dataTaskDidReceiveData = dataTaskDidReceiveData {
dataTaskDidReceiveData(session, dataTask, data)
} else {
if let dataStream = dataStream {
dataStream(data)
} else {
mutableData.append(data)
}
let bytesReceived = Int64(data.count)
totalBytesReceived += bytesReceived
let totalBytesExpected = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
progress.totalUnitCount = totalBytesExpected
progress.completedUnitCount = totalBytesReceived
if let progressHandler = progressHandler {
progressHandler.queue.async { progressHandler.closure(self.progress) }
}
}
}
func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
willCacheResponse proposedResponse: CachedURLResponse,
completionHandler: @escaping (CachedURLResponse?) -> Void)
{
var cachedResponse: CachedURLResponse? = proposedResponse
if let dataTaskWillCacheResponse = dataTaskWillCacheResponse {
cachedResponse = dataTaskWillCacheResponse(session, dataTask, proposedResponse)
}
completionHandler(cachedResponse)
}
}
// MARK: -
class DownloadTaskDelegate: TaskDelegate, URLSessionDownloadDelegate {
// MARK: Properties
var downloadTask: URLSessionDownloadTask { return task as! URLSessionDownloadTask }
var progress: Progress
var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
var resumeData: Data?
override var data: Data? { return resumeData }
var destination: DownloadRequest.DownloadFileDestination?
var temporaryURL: URL?
var destinationURL: URL?
var fileURL: URL? { return destination != nil ? destinationURL : temporaryURL }
// MARK: Lifecycle
override init(task: URLSessionTask?) {
progress = Progress(totalUnitCount: 0)
super.init(task: task)
}
override func reset() {
super.reset()
progress = Progress(totalUnitCount: 0)
resumeData = nil
}
// MARK: URLSessionDownloadDelegate
var downloadTaskDidFinishDownloadingToURL: ((URLSession, URLSessionDownloadTask, URL) -> URL)?
var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)?
var downloadTaskDidResumeAtOffset: ((URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)?
func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL)
{
temporaryURL = location
if let destination = destination {
let result = destination(location, downloadTask.response as! HTTPURLResponse)
let destination = result.destinationURL
let options = result.options
do {
destinationURL = destination
if options.contains(.removePreviousFile) {
if FileManager.default.fileExists(atPath: destination.path) {
try FileManager.default.removeItem(at: destination)
}
}
if options.contains(.createIntermediateDirectories) {
let directory = destination.deletingLastPathComponent()
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil)
}
try FileManager.default.moveItem(at: location, to: destination)
} catch {
self.error = error
}
}
}
func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64)
{
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
if let downloadTaskDidWriteData = downloadTaskDidWriteData {
downloadTaskDidWriteData(
session,
downloadTask,
bytesWritten,
totalBytesWritten,
totalBytesExpectedToWrite
)
} else {
progress.totalUnitCount = totalBytesExpectedToWrite
progress.completedUnitCount = totalBytesWritten
if let progressHandler = progressHandler {
progressHandler.queue.async { progressHandler.closure(self.progress) }
}
}
}
func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didResumeAtOffset fileOffset: Int64,
expectedTotalBytes: Int64)
{
if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset {
downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes)
} else {
progress.totalUnitCount = expectedTotalBytes
progress.completedUnitCount = fileOffset
}
}
}
// MARK: -
class UploadTaskDelegate: DataTaskDelegate {
// MARK: Properties
var uploadTask: URLSessionUploadTask { return task as! URLSessionUploadTask }
var uploadProgress: Progress
var uploadProgressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
// MARK: Lifecycle
override init(task: URLSessionTask?) {
uploadProgress = Progress(totalUnitCount: 0)
super.init(task: task)
}
override func reset() {
super.reset()
uploadProgress = Progress(totalUnitCount: 0)
}
// MARK: URLSessionTaskDelegate
var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)?
func URLSession(
_ session: URLSession,
task: URLSessionTask,
didSendBodyData bytesSent: Int64,
totalBytesSent: Int64,
totalBytesExpectedToSend: Int64)
{
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
if let taskDidSendBodyData = taskDidSendBodyData {
taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend)
} else {
uploadProgress.totalUnitCount = totalBytesExpectedToSend
uploadProgress.completedUnitCount = totalBytesSent
if let uploadProgressHandler = uploadProgressHandler {
uploadProgressHandler.queue.async { uploadProgressHandler.closure(self.uploadProgress) }
}
}
}
}

View File

@@ -0,0 +1,136 @@
//
// Timeline.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Responsible for computing the timing metrics for the complete lifecycle of a `Request`.
public struct Timeline {
/// The time the request was initialized.
public let requestStartTime: CFAbsoluteTime
/// The time the first bytes were received from or sent to the server.
public let initialResponseTime: CFAbsoluteTime
/// The time when the request was completed.
public let requestCompletedTime: CFAbsoluteTime
/// The time when the response serialization was completed.
public let serializationCompletedTime: CFAbsoluteTime
/// The time interval in seconds from the time the request started to the initial response from the server.
public let latency: TimeInterval
/// The time interval in seconds from the time the request started to the time the request completed.
public let requestDuration: TimeInterval
/// The time interval in seconds from the time the request completed to the time response serialization completed.
public let serializationDuration: TimeInterval
/// The time interval in seconds from the time the request started to the time response serialization completed.
public let totalDuration: TimeInterval
/// Creates a new `Timeline` instance with the specified request times.
///
/// - parameter requestStartTime: The time the request was initialized. Defaults to `0.0`.
/// - parameter initialResponseTime: The time the first bytes were received from or sent to the server.
/// Defaults to `0.0`.
/// - parameter requestCompletedTime: The time when the request was completed. Defaults to `0.0`.
/// - parameter serializationCompletedTime: The time when the response serialization was completed. Defaults
/// to `0.0`.
///
/// - returns: The new `Timeline` instance.
public init(
requestStartTime: CFAbsoluteTime = 0.0,
initialResponseTime: CFAbsoluteTime = 0.0,
requestCompletedTime: CFAbsoluteTime = 0.0,
serializationCompletedTime: CFAbsoluteTime = 0.0)
{
self.requestStartTime = requestStartTime
self.initialResponseTime = initialResponseTime
self.requestCompletedTime = requestCompletedTime
self.serializationCompletedTime = serializationCompletedTime
self.latency = initialResponseTime - requestStartTime
self.requestDuration = requestCompletedTime - requestStartTime
self.serializationDuration = serializationCompletedTime - requestCompletedTime
self.totalDuration = serializationCompletedTime - requestStartTime
}
}
// MARK: - CustomStringConvertible
extension Timeline: CustomStringConvertible {
/// The textual representation used when written to an output stream, which includes the latency, the request
/// duration and the total duration.
public var description: String {
let latency = String(format: "%.3f", self.latency)
let requestDuration = String(format: "%.3f", self.requestDuration)
let serializationDuration = String(format: "%.3f", self.serializationDuration)
let totalDuration = String(format: "%.3f", self.totalDuration)
// NOTE: Had to move to string concatenation due to memory leak filed as rdar://26761490. Once memory leak is
// fixed, we should move back to string interpolation by reverting commit 7d4a43b1.
let timings = [
"\"Latency\": " + latency + " secs",
"\"Request Duration\": " + requestDuration + " secs",
"\"Serialization Duration\": " + serializationDuration + " secs",
"\"Total Duration\": " + totalDuration + " secs"
]
return "Timeline: { " + timings.joined(separator: ", ") + " }"
}
}
// MARK: - CustomDebugStringConvertible
extension Timeline: CustomDebugStringConvertible {
/// The textual representation used when written to an output stream, which includes the request start time, the
/// initial response time, the request completed time, the serialization completed time, the latency, the request
/// duration and the total duration.
public var debugDescription: String {
let requestStartTime = String(format: "%.3f", self.requestStartTime)
let initialResponseTime = String(format: "%.3f", self.initialResponseTime)
let requestCompletedTime = String(format: "%.3f", self.requestCompletedTime)
let serializationCompletedTime = String(format: "%.3f", self.serializationCompletedTime)
let latency = String(format: "%.3f", self.latency)
let requestDuration = String(format: "%.3f", self.requestDuration)
let serializationDuration = String(format: "%.3f", self.serializationDuration)
let totalDuration = String(format: "%.3f", self.totalDuration)
// NOTE: Had to move to string concatenation due to memory leak filed as rdar://26761490. Once memory leak is
// fixed, we should move back to string interpolation by reverting commit 7d4a43b1.
let timings = [
"\"Request Start Time\": " + requestStartTime,
"\"Initial Response Time\": " + initialResponseTime,
"\"Request Completed Time\": " + requestCompletedTime,
"\"Serialization Completed Time\": " + serializationCompletedTime,
"\"Latency\": " + latency + " secs",
"\"Request Duration\": " + requestDuration + " secs",
"\"Serialization Duration\": " + serializationDuration + " secs",
"\"Total Duration\": " + totalDuration + " secs"
]
return "Timeline: { " + timings.joined(separator: ", ") + " }"
}
}

View File

@@ -0,0 +1,309 @@
//
// Validation.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
extension Request {
// MARK: Helper Types
fileprivate typealias ErrorReason = AFError.ResponseValidationFailureReason
/// Used to represent whether validation was successful or encountered an error resulting in a failure.
///
/// - success: The validation was successful.
/// - failure: The validation failed encountering the provided error.
public enum ValidationResult {
case success
case failure(Error)
}
fileprivate struct MIMEType {
let type: String
let subtype: String
var isWildcard: Bool { return type == "*" && subtype == "*" }
init?(_ string: String) {
let components: [String] = {
let stripped = string.trimmingCharacters(in: .whitespacesAndNewlines)
let split = stripped.substring(to: stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)
return split.components(separatedBy: "/")
}()
if let type = components.first, let subtype = components.last {
self.type = type
self.subtype = subtype
} else {
return nil
}
}
func matches(_ mime: MIMEType) -> Bool {
switch (type, subtype) {
case (mime.type, mime.subtype), (mime.type, "*"), ("*", mime.subtype), ("*", "*"):
return true
default:
return false
}
}
}
// MARK: Properties
fileprivate var acceptableStatusCodes: [Int] { return Array(200..<300) }
fileprivate var acceptableContentTypes: [String] {
if let accept = request?.value(forHTTPHeaderField: "Accept") {
return accept.components(separatedBy: ",")
}
return ["*/*"]
}
// MARK: Status Code
fileprivate func validate<S: Sequence>(
statusCode acceptableStatusCodes: S,
response: HTTPURLResponse)
-> ValidationResult
where S.Iterator.Element == Int
{
if acceptableStatusCodes.contains(response.statusCode) {
return .success
} else {
let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode)
return .failure(AFError.responseValidationFailed(reason: reason))
}
}
// MARK: Content Type
fileprivate func validate<S: Sequence>(
contentType acceptableContentTypes: S,
response: HTTPURLResponse,
data: Data?)
-> ValidationResult
where S.Iterator.Element == String
{
guard let data = data, data.count > 0 else { return .success }
guard
let responseContentType = response.mimeType,
let responseMIMEType = MIMEType(responseContentType)
else {
for contentType in acceptableContentTypes {
if let mimeType = MIMEType(contentType), mimeType.isWildcard {
return .success
}
}
let error: AFError = {
let reason: ErrorReason = .missingContentType(acceptableContentTypes: Array(acceptableContentTypes))
return AFError.responseValidationFailed(reason: reason)
}()
return .failure(error)
}
for contentType in acceptableContentTypes {
if let acceptableMIMEType = MIMEType(contentType), acceptableMIMEType.matches(responseMIMEType) {
return .success
}
}
let error: AFError = {
let reason: ErrorReason = .unacceptableContentType(
acceptableContentTypes: Array(acceptableContentTypes),
responseContentType: responseContentType
)
return AFError.responseValidationFailed(reason: reason)
}()
return .failure(error)
}
}
// MARK: -
extension DataRequest {
/// A closure used to validate a request that takes a URL request, a URL response and data, and returns whether the
/// request was valid.
public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult
/// Validates the request, using the specified closure.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - parameter validation: A closure to validate the request.
///
/// - returns: The request.
@discardableResult
public func validate(_ validation: @escaping Validation) -> Self {
let validationExecution: () -> Void = {
if
let response = self.response,
self.delegate.error == nil,
case let .failure(error) = validation(self.request, response, self.delegate.data)
{
self.delegate.error = error
}
}
validations.append(validationExecution)
return self
}
/// Validates that the response has a status code in the specified sequence.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - parameter range: The range of acceptable status codes.
///
/// - returns: The request.
@discardableResult
public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
return validate { _, response, _ in
return self.validate(statusCode: acceptableStatusCodes, response: response)
}
}
/// Validates that the response has a content type in the specified sequence.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes.
///
/// - returns: The request.
@discardableResult
public func validate<S: Sequence>(contentType acceptableContentTypes: S) -> Self where S.Iterator.Element == String {
return validate { _, response, data in
return self.validate(contentType: acceptableContentTypes, response: response, data: data)
}
}
/// Validates that the response has a status code in the default acceptable range of 200...299, and that the content
/// type matches any specified in the Accept HTTP header field.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - returns: The request.
@discardableResult
public func validate() -> Self {
return validate(statusCode: self.acceptableStatusCodes).validate(contentType: self.acceptableContentTypes)
}
}
// MARK: -
extension DownloadRequest {
/// A closure used to validate a request that takes a URL request, a URL response, a temporary URL and a
/// destination URL, and returns whether the request was valid.
public typealias Validation = (
_ request: URLRequest?,
_ response: HTTPURLResponse,
_ temporaryURL: URL?,
_ destinationURL: URL?)
-> ValidationResult
/// Validates the request, using the specified closure.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - parameter validation: A closure to validate the request.
///
/// - returns: The request.
@discardableResult
public func validate(_ validation: @escaping Validation) -> Self {
let validationExecution: () -> Void = {
let request = self.request
let temporaryURL = self.downloadDelegate.temporaryURL
let destinationURL = self.downloadDelegate.destinationURL
if
let response = self.response,
self.delegate.error == nil,
case let .failure(error) = validation(request, response, temporaryURL, destinationURL)
{
self.delegate.error = error
}
}
validations.append(validationExecution)
return self
}
/// Validates that the response has a status code in the specified sequence.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - parameter range: The range of acceptable status codes.
///
/// - returns: The request.
@discardableResult
public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
return validate { _, response, _, _ in
return self.validate(statusCode: acceptableStatusCodes, response: response)
}
}
/// Validates that the response has a content type in the specified sequence.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes.
///
/// - returns: The request.
@discardableResult
public func validate<S: Sequence>(contentType acceptableContentTypes: S) -> Self where S.Iterator.Element == String {
return validate { _, response, _, _ in
let fileURL = self.downloadDelegate.fileURL
guard let validFileURL = fileURL else {
return .failure(AFError.responseValidationFailed(reason: .dataFileNil))
}
do {
let data = try Data(contentsOf: validFileURL)
return self.validate(contentType: acceptableContentTypes, response: response, data: data)
} catch {
return .failure(AFError.responseValidationFailed(reason: .dataFileReadFailed(at: validFileURL)))
}
}
}
/// Validates that the response has a status code in the default acceptable range of 200...299, and that the content
/// type matches any specified in the Accept HTTP header field.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - returns: The request.
@discardableResult
public func validate() -> Self {
return validate(statusCode: self.acceptableStatusCodes).validate(contentType: self.acceptableContentTypes)
}
}

View File

@@ -0,0 +1,25 @@
{
"name": "PetstoreClient",
"platforms": {
"ios": "9.0",
"osx": "10.11"
},
"version": "0.0.1",
"source": {
"git": "git@github.com:swagger-api/swagger-mustache.git",
"tag": "v1.0.0"
},
"authors": "",
"license": "Apache License, Version 2.0",
"homepage": "https://github.com/swagger-api/swagger-codegen",
"summary": "PetstoreClient",
"source_files": "PetstoreClient/Classes/Swaggers/**/*.swift",
"dependencies": {
"RxSwift": [
"~> 3.0.0-beta.1"
],
"Alamofire": [
"~> 4.0"
]
}
}

View File

@@ -0,0 +1,22 @@
PODS:
- Alamofire (4.0.0)
- PetstoreClient (0.0.1):
- Alamofire (~> 4.0)
- RxSwift (~> 3.0.0-beta.1)
- RxSwift (3.0.0-beta.1)
DEPENDENCIES:
- PetstoreClient (from `../`)
EXTERNAL SOURCES:
PetstoreClient:
:path: ../
SPEC CHECKSUMS:
Alamofire: fef59f00388f267e52d9b432aa5d93dc97190f14
PetstoreClient: a58edc9541bf0e2a0a7f8464907f26c9b7bafe74
RxSwift: 0823e8d7969c23bfa9ddfb2afa4881e424a1a710
PODFILE CHECKSUM: da9f5a7ad6086f2c7abb73cf2c35cefce04a9a30
COCOAPODS: 1.0.1

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
**The MIT License**
**Copyright © 2015 Krunoslav Zaher**
**All rights reserved.**
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,203 @@
<img src="assets/Rx_Logo_M.png" alt="Miss Electric Eel 2016" width="36" height="36"> RxSwift: ReactiveX for Swift
======================================
[![Travis CI](https://travis-ci.org/ReactiveX/RxSwift.svg?branch=master)](https://travis-ci.org/ReactiveX/RxSwift) ![platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20OSX%20%7C%20tvOS%20%7C%20watchOS%20%7C%20Linux%28experimental%29-333333.svg) ![pod](https://img.shields.io/cocoapods/v/RxSwift.svg) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
## About Rx
**:warning: This readme describes RxSwift 3.0 version that requires Swift 3.0:warning:**
**:warning: If you are looking for Swift 2.3 compatible version, please take a look at RxSwift ~> 2.0 versions and [swift-2.3](https://github.com/ReactiveX/RxSwift/tree/rxswift-2.0) branch :warning:**
Rx is a [generic abstraction of computation](https://youtu.be/looJcaeboBY) expressed through `Observable<Element>` interface.
This is a Swift version of [Rx](https://github.com/Reactive-Extensions/Rx.NET).
It tries to port as many concepts from the original version as possible, but some concepts were adapted for more pleasant and performant integration with iOS/OSX environment.
Cross platform documentation can be found on [ReactiveX.io](http://reactivex.io/).
Like the original Rx, its intention is to enable easy composition of asynchronous operations and event/data streams.
KVO observing, async operations and streams are all unified under [abstraction of sequence](Documentation/GettingStarted.md#observables-aka-sequences). This is the reason why Rx is so simple, elegant and powerful.
## I came here because I want to ...
###### ... understand
* [why use rx?](Documentation/Why.md)
* [the basics, getting started with RxSwift](Documentation/GettingStarted.md)
* [units](Documentation/Units.md) - what is `Driver`, `ControlProperty`, and `Variable` ... and why do they exist?
* [testing](Documentation/UnitTests.md)
* [tips and common errors](Documentation/Tips.md)
* [debugging](Documentation/GettingStarted.md#debugging)
* [the math behind Rx](Documentation/MathBehindRx.md)
* [what are hot and cold observable sequences?](Documentation/HotAndColdObservables.md)
* [what does the the public API look like?](Documentation/API.md)
###### ... install
* Integrate RxSwift/RxCocoa with my app. [Installation Guide](Documentation/Installation.md)
###### ... hack around
* with the example app. [Running Example App](Documentation/ExampleApp.md)
* with operators in playgrounds. [Playgrounds](Documentation/Playgrounds.md)
###### ... interact
* All of this is great, but it would be nice to talk with other people using RxSwift and exchange experiences. <br />[![Slack channel](http://rxswift-slack.herokuapp.com/badge.svg)](http://slack.rxswift.org) [Join Slack Channel](http://rxswift-slack.herokuapp.com)
* Report a problem using the library. [Open an Issue With Bug Template](ISSUE_TEMPLATE.md)
* Request a new feature. [Open an Issue With Feature Request Template](Documentation/NewFeatureRequestTemplate.md)
###### ... compare
* [with other libraries](Documentation/ComparisonWithOtherLibraries.md).
###### ... find compatible
* libraries from [RxSwiftCommunity](https://github.com/RxSwiftCommunity).
* [Pods using RxSwift](https://cocoapods.org/?q=uses%3Arxswift).
###### ... see the broader vision
* Does this exist for Android? [RxJava](https://github.com/ReactiveX/RxJava)
* Where is all of this going, what is the future, what about reactive architectures, how do you design entire apps this way? [Cycle.js](https://github.com/cyclejs/cycle-core) - this is javascript, but [RxJS](https://github.com/Reactive-Extensions/RxJS) is javascript version of Rx.
## Usage
<table>
<tr>
<th width="30%">Here's an example</th>
<th width="30%">In Action</th>
</tr>
<tr>
<td>Define search for GitHub repositories ...</td>
<th rowspan="9"><img src="https://raw.githubusercontent.com/kzaher/rxswiftcontent/master/GithubSearch.gif"></th>
</tr>
<tr>
<td><div class="highlight highlight-source-swift"><pre>
let searchResults = searchBar.rx.text
.throttle(0.3, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.flatMapLatest { query -> Observable<[Repository]> in
if query.isEmpty {
return Observable.just([])
}
return searchGitHub(query)
.catchErrorJustReturn([])
}
.observeOn(MainScheduler.instance)</pre></div></td>
</tr>
<tr>
<td>... then bind the results to your tableview</td>
</tr>
<tr>
<td width="30%"><div class="highlight highlight-source-swift"><pre>
searchResults
.bindTo(tableView.rx.items(cellIdentifier: "Cell")) {
(index, repository: Repository, cell) in
cell.textLabel?.text = repository.name
cell.detailTextLabel?.text = repository.url
}
.addDisposableTo(disposeBag)</pre></div></td>
</tr>
</table>
## Requirements
* Xcode 8.0 GM (8A218a)
* Swift 3.0
* iOS 8.0+
* Mac OS X 10.10+
* tvOS 9.0+
* watchOS 2.0+
## Installation
Rx doesn't contain any external dependencies.
These are currently the supported options:
### Manual
Open Rx.xcworkspace, choose `RxExample` and hit run. This method will build everything and run the sample app
### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html)
```
# Podfile
use_frameworks!
target 'YOUR_TARGET_NAME' do
pod 'RxSwift', '~> 3.0.0-beta.1'
pod 'RxCocoa', '~> 3.0.0-beta.1'
end
# RxTests and RxBlocking make the most sense in the context of unit/integration tests
target 'YOUR_TESTING_TARGET' do
pod 'RxBlocking', '~> 3.0.0-beta.1'
pod 'RxTests', '~> 3.0.0-beta.1'
end
```
Replace `YOUR_TARGET_NAME` and then, in the `Podfile` directory, type:
**:warning: If you want to use CocoaPods with Xcode 8.0 beta and Swift 3.0, you might need to add the following
lines to your podfile: :warning:**
```
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['SWIFT_VERSION'] = '3.0'
config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '10.10'
end
end
end
```
```
$ pod install
```
### [Carthage](https://github.com/Carthage/Carthage)
Add this to `Cartfile`
```
github "ReactiveX/RxSwift" "3.0.0-beta.1"
```
```
$ carthage update
```
### Manually using git submodules
* Add RxSwift as a submodule
```
$ git submodule add git@github.com:ReactiveX/RxSwift.git
```
* Drag `Rx.xcodeproj` into Project Navigator
* Go to `Project > Targets > Build Phases > Link Binary With Libraries`, click `+` and select `RxSwift-[Platform]` and `RxCocoa-[Platform]` targets
## References
* [http://reactivex.io/](http://reactivex.io/)
* [Reactive Extensions GitHub (GitHub)](https://github.com/Reactive-Extensions)
* [Erik Meijer (Wikipedia)](http://en.wikipedia.org/wiki/Erik_Meijer_%28computer_scientist%29)
* [Expert to Expert: Brian Beckman and Erik Meijer - Inside the .NET Reactive Framework (Rx) (video)](https://youtu.be/looJcaeboBY)
* [Reactive Programming Overview (Jafar Husain from Netflix)](https://www.youtube.com/watch?v=dwP1TNXE6fc)
* [Subject/Observer is Dual to Iterator (paper)](http://csl.stanford.edu/~christos/pldi2010.fit/meijer.duality.pdf)
* [Rx standard sequence operators visualized (visualization tool)](http://rxmarbles.com/)
* [Haskell](https://www.haskell.org/)

View File

@@ -0,0 +1,75 @@
//
// AnyObserver.swift
// Rx
//
// Created by Krunoslav Zaher on 2/28/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
A type-erased `ObserverType`.
Forwards operations to an arbitrary underlying observer with the same `Element` type, hiding the specifics of the underlying observer type.
*/
public struct AnyObserver<Element> : ObserverType {
/**
The type of elements in sequence that observer can observe.
*/
public typealias E = Element
/**
Anonymous event handler type.
*/
public typealias EventHandler = (Event<Element>) -> Void
public let observer: EventHandler
/**
Construct an instance whose `on(event)` calls `eventHandler(event)`
- parameter eventHandler: Event handler that observes sequences events.
*/
public init(eventHandler: @escaping EventHandler) {
self.observer = eventHandler
}
/**
Construct an instance whose `on(event)` calls `observer.on(event)`
- parameter observer: Observer that receives sequence events.
*/
public init<O : ObserverType>(_ observer: O) where O.E == Element {
self.observer = observer.on
}
/**
Send `event` to this observer.
- parameter event: Event instance.
*/
public func on(_ event: Event<Element>) {
return self.observer(event)
}
/**
Erases type of observer and returns canonical observer.
- returns: type erased observer.
*/
public func asObserver() -> AnyObserver<E> {
return self
}
}
extension ObserverType {
/**
Erases type of observer and returns canonical observer.
- returns: type erased observer.
*/
public func asObserver() -> AnyObserver<E> {
return AnyObserver(self)
}
}

View File

@@ -0,0 +1,28 @@
//
// Cancelable.swift
// Rx
//
// Created by Krunoslav Zaher on 3/12/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Represents disposable resource with state tracking.
*/
public protocol Cancelable : Disposable {
/**
- returns: Was resource disposed.
*/
var isDisposed: Bool { get }
}
public extension Cancelable {
@available(*, deprecated, renamed: "isDisposed")
var disposed: Bool {
return isDisposed
}
}

View File

@@ -0,0 +1,104 @@
//
// AsyncLock.swift
// Rx
//
// Created by Krunoslav Zaher on 3/21/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
In case nobody holds this lock, the work will be queued and executed immediately
on thread that is requesting lock.
In case there is somebody currently holding that lock, action will be enqueued.
When owned of the lock finishes with it's processing, it will also execute
and pending work.
That means that enqueued work could possibly be executed later on a different thread.
*/
class AsyncLock<I: InvocableType>
: Disposable
, Lock
, SynchronizedDisposeType {
typealias Action = () -> Void
var _lock = SpinLock()
private var _queue: Queue<I> = Queue(capacity: 0)
private var _isExecuting: Bool = false
private var _hasFaulted: Bool = false
// lock {
func lock() {
_lock.lock()
}
func unlock() {
_lock.unlock()
}
// }
private func enqueue(_ action: I) -> I? {
_lock.lock(); defer { _lock.unlock() } // {
if _hasFaulted {
return nil
}
if _isExecuting {
_queue.enqueue(action)
return nil
}
_isExecuting = true
return action
// }
}
private func dequeue() -> I? {
_lock.lock(); defer { _lock.unlock() } // {
if _queue.count > 0 {
return _queue.dequeue()
}
else {
_isExecuting = false
return nil
}
// }
}
func invoke(_ action: I) {
let firstEnqueuedAction = enqueue(action)
if let firstEnqueuedAction = firstEnqueuedAction {
firstEnqueuedAction.invoke()
}
else {
// action is enqueued, it's somebody else's concern now
return
}
while true {
let nextAction = dequeue()
if let nextAction = nextAction {
nextAction.invoke()
}
else {
return
}
}
}
func dispose() {
synchronizedDispose()
}
func _synchronized_dispose() {
_queue = Queue(capacity: 0)
_hasFaulted = true
}
}

View File

@@ -0,0 +1,107 @@
//
// Lock.swift
// Rx
//
// Created by Krunoslav Zaher on 3/31/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
protocol Lock {
func lock()
func unlock()
}
#if os(Linux)
import Glibc
/**
Simple wrapper for spin lock.
*/
class SpinLock {
private var _lock: pthread_spinlock_t = 0
init() {
if (pthread_spin_init(&_lock, 0) != 0) {
fatalError("Spin lock initialization failed")
}
}
func lock() {
pthread_spin_lock(&_lock)
}
func unlock() {
pthread_spin_unlock(&_lock)
}
func performLocked(@noescape action: () -> Void) {
lock(); defer { unlock() }
action()
}
func calculateLocked<T>(@noescape action: () -> T) -> T {
lock(); defer { unlock() }
return action()
}
func calculateLockedOrFail<T>(@noescape action: () throws -> T) throws -> T {
lock(); defer { unlock() }
let result = try action()
return result
}
deinit {
pthread_spin_destroy(&_lock)
}
}
#else
// https://lists.swift.org/pipermail/swift-dev/Week-of-Mon-20151214/000321.html
typealias SpinLock = NSRecursiveLock
#endif
extension NSRecursiveLock : Lock {
func performLocked(_ action: () -> Void) {
lock(); defer { unlock() }
action()
}
func calculateLocked<T>(_ action: () -> T) -> T {
lock(); defer { unlock() }
return action()
}
func calculateLockedOrFail<T>(_ action: () throws -> T) throws -> T {
lock(); defer { unlock() }
let result = try action()
return result
}
}
/*
let RECURSIVE_MUTEX = _initializeRecursiveMutex()
func _initializeRecursiveMutex() -> pthread_mutex_t {
var mutex: pthread_mutex_t = pthread_mutex_t()
var mta: pthread_mutexattr_t = pthread_mutexattr_t()
pthread_mutex_init(&mutex, nil)
pthread_mutexattr_init(&mta)
pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_RECURSIVE)
pthread_mutex_init(&mutex, &mta)
return mutex
}
extension pthread_mutex_t {
mutating func lock() {
pthread_mutex_lock(&self)
}
mutating func unlock() {
pthread_mutex_unlock(&self)
}
}
*/

View File

@@ -0,0 +1,23 @@
//
// LockOwnerType.swift
// Rx
//
// Created by Krunoslav Zaher on 10/25/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
protocol LockOwnerType : class, Lock {
var _lock: NSRecursiveLock { get }
}
extension LockOwnerType {
func lock() {
_lock.lock()
}
func unlock() {
_lock.unlock()
}
}

View File

@@ -0,0 +1,20 @@
//
// SynchronizedDisposeType.swift
// Rx
//
// Created by Krunoslav Zaher on 10/25/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
protocol SynchronizedDisposeType : class, Disposable, Lock {
func _synchronized_dispose()
}
extension SynchronizedDisposeType {
func synchronizedDispose() {
lock(); defer { unlock() }
_synchronized_dispose()
}
}

View File

@@ -0,0 +1,20 @@
//
// SynchronizedOnType.swift
// Rx
//
// Created by Krunoslav Zaher on 10/25/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
protocol SynchronizedOnType : class, ObserverType, Lock {
func _synchronized_on(_ event: Event<E>)
}
extension SynchronizedOnType {
func synchronizedOn(_ event: Event<E>) {
lock(); defer { unlock() }
_synchronized_on(event)
}
}

View File

@@ -0,0 +1,20 @@
//
// SynchronizedSubscribeType.swift
// Rx
//
// Created by Krunoslav Zaher on 10/25/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
protocol SynchronizedSubscribeType : class, ObservableType, Lock {
func _synchronized_subscribe<O: ObserverType>(_ observer: O) -> Disposable where O.E == E
}
extension SynchronizedSubscribeType {
func synchronizedSubscribe<O: ObserverType>(_ observer: O) -> Disposable where O.E == E {
lock(); defer { unlock() }
return _synchronized_subscribe(observer)
}
}

View File

@@ -0,0 +1,15 @@
//
// SynchronizedUnsubscribeType.swift
// Rx
//
// Created by Krunoslav Zaher on 10/25/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
protocol SynchronizedUnsubscribeType : class {
associatedtype DisposeKey
func synchronizedUnsubscribe(_ disposeKey: DisposeKey)
}

View File

@@ -0,0 +1,21 @@
//
// ConnectableObservableType.swift
// Rx
//
// Created by Krunoslav Zaher on 3/1/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Represents an observable sequence wrapper that can be connected and disconnected from its underlying observable sequence.
*/
public protocol ConnectableObservableType : ObservableType {
/**
Connects the observable wrapper to its source. All subscribed observers will receive values from the underlying observable sequence as long as the connection is established.
- returns: Disposable used to disconnect the observable wrapper from its source, causing subscribed observer to stop receiving values from the underlying observable sequence.
*/
func connect() -> Disposable
}

View File

@@ -0,0 +1,336 @@
//
// Bag.swift
// Rx
//
// Created by Krunoslav Zaher on 2/28/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
import Swift
let arrayDictionaryMaxSize = 30
/**
Class that enables using memory allocations as a means to uniquely identify objects.
*/
class Identity {
// weird things have known to happen with Swift
var _forceAllocation: Int32 = 0
}
func hash(_ _x: Int) -> Int {
var x = _x
x = ((x >> 16) ^ x) &* 0x45d9f3b
x = ((x >> 16) ^ x) &* 0x45d9f3b
x = ((x >> 16) ^ x)
return x;
}
/**
Unique identifier for object added to `Bag`.
*/
public struct BagKey : Hashable {
let uniqueIdentity: Identity?
let key: Int
public var hashValue: Int {
if let uniqueIdentity = uniqueIdentity {
return hash(key) ^ (ObjectIdentifier(uniqueIdentity).hashValue)
}
else {
return hash(key)
}
}
}
/**
Compares two `BagKey`s.
*/
public func == (lhs: BagKey, rhs: BagKey) -> Bool {
return lhs.key == rhs.key && lhs.uniqueIdentity === rhs.uniqueIdentity
}
/**
Data structure that represents a bag of elements typed `T`.
Single element can be stored multiple times.
Time and space complexity of insertion an deletion is O(n).
It is suitable for storing small number of elements.
*/
public struct Bag<T> : CustomDebugStringConvertible {
/**
Type of identifier for inserted elements.
*/
public typealias KeyType = BagKey
fileprivate typealias ScopeUniqueTokenType = Int
typealias Entry = (key: BagKey, value: T)
fileprivate var _uniqueIdentity: Identity?
fileprivate var _nextKey: ScopeUniqueTokenType = 0
// data
// first fill inline variables
fileprivate var _key0: BagKey? = nil
fileprivate var _value0: T? = nil
fileprivate var _key1: BagKey? = nil
fileprivate var _value1: T? = nil
// then fill "array dictionary"
fileprivate var _pairs = ContiguousArray<Entry>()
// last is sparse dictionary
fileprivate var _dictionary: [BagKey : T]? = nil
fileprivate var _onlyFastPath = true
/**
Creates new empty `Bag`.
*/
public init() {
}
/**
Inserts `value` into bag.
- parameter element: Element to insert.
- returns: Key that can be used to remove element from bag.
*/
public mutating func insert(_ element: T) -> BagKey {
_nextKey = _nextKey &+ 1
#if DEBUG
_nextKey = _nextKey % 20
#endif
if _nextKey == 0 {
_uniqueIdentity = Identity()
}
let key = BagKey(uniqueIdentity: _uniqueIdentity, key: _nextKey)
if _key0 == nil {
_key0 = key
_value0 = element
return key
}
_onlyFastPath = false
if _key1 == nil {
_key1 = key
_value1 = element
return key
}
if _dictionary != nil {
_dictionary![key] = element
return key
}
if _pairs.count < arrayDictionaryMaxSize {
_pairs.append(key: key, value: element)
return key
}
if _dictionary == nil {
_dictionary = [:]
}
_dictionary![key] = element
return key
}
/**
- returns: Number of elements in bag.
*/
public var count: Int {
let dictionaryCount: Int = _dictionary?.count ?? 0
return _pairs.count + (_value0 != nil ? 1 : 0) + (_value1 != nil ? 1 : 0) + dictionaryCount
}
/**
Removes all elements from bag and clears capacity.
*/
public mutating func removeAll() {
_key0 = nil
_value0 = nil
_key1 = nil
_value1 = nil
_pairs.removeAll(keepingCapacity: false)
_dictionary?.removeAll(keepingCapacity: false)
}
/**
Removes element with a specific `key` from bag.
- parameter key: Key that identifies element to remove from bag.
- returns: Element that bag contained, or nil in case element was already removed.
*/
public mutating func removeKey(_ key: BagKey) -> T? {
if _key0 == key {
_key0 = nil
let value = _value0!
_value0 = nil
return value
}
if _key1 == key {
_key1 = nil
let value = _value1!
_value1 = nil
return value
}
if let existingObject = _dictionary?.removeValue(forKey: key) {
return existingObject
}
for i in 0 ..< _pairs.count {
if _pairs[i].key == key {
let value = _pairs[i].value
_pairs.remove(at: i)
return value
}
}
return nil
}
}
extension Bag {
/**
A textual representation of `self`, suitable for debugging.
*/
public var debugDescription : String {
return "\(self.count) elements in Bag"
}
}
// MARK: forEach
extension Bag {
/**
Enumerates elements inside the bag.
- parameter action: Enumeration closure.
*/
public func forEach(_ action: (T) -> Void) {
if _onlyFastPath {
if let value0 = _value0 {
action(value0)
}
return
}
let pairs = _pairs
let value0 = _value0
let value1 = _value1
let dictionary = _dictionary
if let value0 = value0 {
action(value0)
}
if let value1 = value1 {
action(value1)
}
for i in 0 ..< pairs.count {
action(pairs[i].value)
}
if dictionary?.count ?? 0 > 0 {
for element in dictionary!.values {
action(element)
}
}
}
}
extension Bag where T: ObserverType {
/**
Dispatches `event` to app observers contained inside bag.
- parameter action: Enumeration closure.
*/
public func on(_ event: Event<T.E>) {
if _onlyFastPath {
_value0?.on(event)
return
}
let pairs = _pairs
let value0 = _value0
let value1 = _value1
let dictionary = _dictionary
if let value0 = value0 {
value0.on(event)
}
if let value1 = value1 {
value1.on(event)
}
for i in 0 ..< pairs.count {
pairs[i].value.on(event)
}
if dictionary?.count ?? 0 > 0 {
for element in dictionary!.values {
element.on(event)
}
}
}
}
/**
Dispatches `dispose` to all disposables contained inside bag.
*/
@available(*, deprecated, renamed: "disposeAll(in:)")
public func disposeAllIn(_ bag: Bag<Disposable>) {
disposeAll(in: bag)
}
/**
Dispatches `dispose` to all disposables contained inside bag.
*/
public func disposeAll(in bag: Bag<Disposable>) {
if bag._onlyFastPath {
bag._value0?.dispose()
return
}
let pairs = bag._pairs
let value0 = bag._value0
let value1 = bag._value1
let dictionary = bag._dictionary
if let value0 = value0 {
value0.dispose()
}
if let value1 = value1 {
value1.dispose()
}
for i in 0 ..< pairs.count {
pairs[i].value.dispose()
}
if dictionary?.count ?? 0 > 0 {
for element in dictionary!.values {
element.dispose()
}
}
}

View File

@@ -0,0 +1,30 @@
//
// InfiniteSequence.swift
// RxSwift
//
// Created by Krunoslav Zaher on 6/13/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Sequence that repeats `repeatedValue` infinite number of times.
*/
struct InfiniteSequence<E> : Sequence {
typealias Element = E
typealias Iterator = AnyIterator<E>
private let _repeatedValue: E
init(repeatedValue: E) {
_repeatedValue = repeatedValue
}
func makeIterator() -> Iterator {
let repeatedValue = _repeatedValue
return AnyIterator {
return repeatedValue
}
}
}

View File

@@ -0,0 +1,120 @@
//
// PriorityQueue.swift
// Rx
//
// Created by Krunoslav Zaher on 12/27/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
struct PriorityQueue<Element: AnyObject> {
private let _hasHigherPriority: (Element, Element) -> Bool
fileprivate var _elements = [Element]()
init(hasHigherPriority: @escaping (Element, Element) -> Bool) {
_hasHigherPriority = hasHigherPriority
}
mutating func enqueue(_ element: Element) {
_elements.append(element)
bubbleToHigherPriority(_elements.count - 1)
}
func peek() -> Element? {
return _elements.first
}
var isEmpty: Bool {
return _elements.count == 0
}
mutating func dequeue() -> Element? {
guard let front = peek() else {
return nil
}
removeAt(0)
return front
}
mutating func remove(_ element: Element) {
for i in 0 ..< _elements.count {
if _elements[i] === element {
removeAt(i)
return
}
}
}
private mutating func removeAt(_ index: Int) {
let removingLast = index == _elements.count - 1
if !removingLast {
swap(&_elements[index], &_elements[_elements.count - 1])
}
_ = _elements.popLast()
if !removingLast {
bubbleToHigherPriority(index)
bubbleToLowerPriority(index)
}
}
private mutating func bubbleToHigherPriority(_ initialUnbalancedIndex: Int) {
precondition(initialUnbalancedIndex >= 0)
precondition(initialUnbalancedIndex < _elements.count)
var unbalancedIndex = initialUnbalancedIndex
while unbalancedIndex > 0 {
let parentIndex = (unbalancedIndex - 1) / 2
if _hasHigherPriority(_elements[unbalancedIndex], _elements[parentIndex]) {
swap(&_elements[unbalancedIndex], &_elements[parentIndex])
unbalancedIndex = parentIndex
}
else {
break
}
}
}
private mutating func bubbleToLowerPriority(_ initialUnbalancedIndex: Int) {
precondition(initialUnbalancedIndex >= 0)
precondition(initialUnbalancedIndex < _elements.count)
var unbalancedIndex = initialUnbalancedIndex
repeat {
let leftChildIndex = unbalancedIndex * 2 + 1
let rightChildIndex = unbalancedIndex * 2 + 2
var highestPriorityIndex = unbalancedIndex
if leftChildIndex < _elements.count && _hasHigherPriority(_elements[leftChildIndex], _elements[highestPriorityIndex]) {
highestPriorityIndex = leftChildIndex
}
if rightChildIndex < _elements.count && _hasHigherPriority(_elements[rightChildIndex], _elements[highestPriorityIndex]) {
highestPriorityIndex = rightChildIndex
}
if highestPriorityIndex != unbalancedIndex {
swap(&_elements[highestPriorityIndex], &_elements[unbalancedIndex])
unbalancedIndex = highestPriorityIndex
}
else {
break
}
} while true
}
}
extension PriorityQueue : CustomDebugStringConvertible {
var debugDescription: String {
return _elements.debugDescription
}
}

View File

@@ -0,0 +1,168 @@
//
// Queue.swift
// Rx
//
// Created by Krunoslav Zaher on 3/21/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Data structure that represents queue.
Complexity of `enqueue`, `dequeue` is O(1) when number of operations is
averaged over N operations.
Complexity of `peek` is O(1).
*/
public struct Queue<T>: Sequence {
/**
Type of generator.
*/
public typealias Generator = AnyIterator<T>
private let _resizeFactor = 2
private var _storage: ContiguousArray<T?>
private var _count = 0
private var _pushNextIndex = 0
private var _initialCapacity: Int
/**
Creates new queue.
- parameter capacity: Capacity of newly created queue.
*/
public init(capacity: Int) {
_initialCapacity = capacity
_storage = ContiguousArray<T?>(repeating: nil, count: capacity)
}
private var dequeueIndex: Int {
let index = _pushNextIndex - count
return index < 0 ? index + _storage.count : index
}
/**
- returns: Is queue empty.
*/
public var isEmpty: Bool {
return count == 0
}
/**
- returns: Number of elements inside queue.
*/
public var count: Int {
return _count
}
/**
- returns: Element in front of a list of elements to `dequeue`.
*/
public func peek() -> T {
precondition(count > 0)
return _storage[dequeueIndex]!
}
mutating private func resizeTo(_ size: Int) {
var newStorage = ContiguousArray<T?>(repeating: nil, count: size)
let count = _count
let dequeueIndex = self.dequeueIndex
let spaceToEndOfQueue = _storage.count - dequeueIndex
// first batch is from dequeue index to end of array
let countElementsInFirstBatch = Swift.min(count, spaceToEndOfQueue)
// second batch is wrapped from start of array to end of queue
let numberOfElementsInSecondBatch = count - countElementsInFirstBatch
newStorage[0 ..< countElementsInFirstBatch] = _storage[dequeueIndex ..< (dequeueIndex + countElementsInFirstBatch)]
newStorage[countElementsInFirstBatch ..< (countElementsInFirstBatch + numberOfElementsInSecondBatch)] = _storage[0 ..< numberOfElementsInSecondBatch]
_count = count
_pushNextIndex = count
_storage = newStorage
}
/**
Enqueues `element`.
- parameter element: Element to enqueue.
*/
public mutating func enqueue(_ element: T) {
if count == _storage.count {
resizeTo(Swift.max(_storage.count, 1) * _resizeFactor)
}
_storage[_pushNextIndex] = element
_pushNextIndex += 1
_count += 1
if _pushNextIndex >= _storage.count {
_pushNextIndex -= _storage.count
}
}
private mutating func dequeueElementOnly() -> T {
precondition(count > 0)
let index = dequeueIndex
defer {
_storage[index] = nil
_count -= 1
}
return _storage[index]!
}
/**
Dequeues element or throws an exception in case queue is empty.
- returns: Dequeued element.
*/
public mutating func dequeue() -> T? {
if self.count == 0 {
return nil
}
defer {
let downsizeLimit = _storage.count / (_resizeFactor * _resizeFactor)
if _count < downsizeLimit && downsizeLimit >= _initialCapacity {
resizeTo(_storage.count / _resizeFactor)
}
}
return dequeueElementOnly()
}
/**
- returns: Generator of contained elements.
*/
public func makeIterator() -> AnyIterator<T> {
var i = dequeueIndex
var count = _count
return AnyIterator {
if count == 0 {
return nil
}
defer {
count -= 1
i += 1
}
if i >= self._storage.count {
i -= self._storage.count
}
return self._storage[i]
}
}
}

View File

@@ -0,0 +1,15 @@
//
// Disposable.swift
// Rx
//
// Created by Krunoslav Zaher on 2/8/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/// Respresents a disposable resource.
public protocol Disposable {
/// Dispose resource.
func dispose()
}

View File

@@ -0,0 +1,74 @@
//
// AnonymousDisposable.swift
// Rx
//
// Created by Krunoslav Zaher on 2/15/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Represents an Action-based disposable.
When dispose method is called, disposal action will be dereferenced.
*/
public final class AnonymousDisposable : DisposeBase, Cancelable {
public typealias DisposeAction = () -> Void
private var _isDisposed: AtomicInt = 0
private var _disposeAction: DisposeAction?
/**
- returns: Was resource disposed.
*/
public var isDisposed: Bool {
return _isDisposed == 1
}
/**
Constructs a new disposable with the given action used for disposal.
- parameter disposeAction: Disposal action which will be run upon calling `dispose`.
*/
@available(*, deprecated, renamed: "Disposables.create")
public init(_ disposeAction: @escaping DisposeAction) {
_disposeAction = disposeAction
super.init()
}
// Non-deprecated version of the constructor, used by `Disposables.create(with:)`
fileprivate init(disposeAction: @escaping DisposeAction) {
_disposeAction = disposeAction
super.init()
}
/**
Calls the disposal action if and only if the current instance hasn't been disposed yet.
After invoking disposal action, disposal action will be dereferenced.
*/
public func dispose() {
if AtomicCompareAndSwap(0, 1, &_isDisposed) {
assert(_isDisposed == 1)
if let action = _disposeAction {
_disposeAction = nil
action()
}
}
}
}
public extension Disposables {
/**
Constructs a new disposable with the given action used for disposal.
- parameter dispose: Disposal action which will be run upon calling `dispose`.
*/
static func create(with dispose: @escaping () -> ()) -> Cancelable {
return AnonymousDisposable(disposeAction: dispose)
}
}

View File

@@ -0,0 +1,65 @@
//
// BinaryDisposable.swift
// RxSwift
//
// Created by Krunoslav Zaher on 6/12/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Represents two disposable resources that are disposed together.
*/
private final class BinaryDisposable : DisposeBase, Cancelable {
private var _isDisposed: AtomicInt = 0
// state
private var _disposable1: Disposable?
private var _disposable2: Disposable?
/**
- returns: Was resource disposed.
*/
var isDisposed: Bool {
return _isDisposed > 0
}
/**
Constructs new binary disposable from two disposables.
- parameter disposable1: First disposable
- parameter disposable2: Second disposable
*/
init(_ disposable1: Disposable, _ disposable2: Disposable) {
_disposable1 = disposable1
_disposable2 = disposable2
super.init()
}
/**
Calls the disposal action if and only if the current instance hasn't been disposed yet.
After invoking disposal action, disposal action will be dereferenced.
*/
func dispose() {
if AtomicCompareAndSwap(0, 1, &_isDisposed) {
_disposable1?.dispose()
_disposable2?.dispose()
_disposable1 = nil
_disposable2 = nil
}
}
}
public extension Disposables {
/**
Creates a disposable with the given disposables.
*/
static func create(_ disposable1: Disposable, _ disposable2: Disposable) -> Cancelable {
return BinaryDisposable(disposable1, disposable2)
}
}

View File

@@ -0,0 +1,45 @@
//
// BooleanDisposable.swift
// Rx
//
// Created by Junior B. on 10/29/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Represents a disposable resource that can be checked for disposal status.
*/
public final class BooleanDisposable : Disposable, Cancelable {
internal static let BooleanDisposableTrue = BooleanDisposable(isDisposed: true)
private var _isDisposed = false
/**
Initializes a new instance of the `BooleanDisposable` class
*/
public init() {
}
/**
Initializes a new instance of the `BooleanDisposable` class with given value
*/
public init(isDisposed: Bool) {
self._isDisposed = isDisposed
}
/**
- returns: Was resource disposed.
*/
public var isDisposed: Bool {
return _isDisposed
}
/**
Sets the status to disposed, which can be observer through the `isDisposed` property.
*/
public func dispose() {
_isDisposed = true
}
}

View File

@@ -0,0 +1,157 @@
//
// CompositeDisposable.swift
// Rx
//
// Created by Krunoslav Zaher on 2/20/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Represents a group of disposable resources that are disposed together.
*/
public final class CompositeDisposable : DisposeBase, Disposable, Cancelable {
public typealias DisposeKey = Bag<Disposable>.KeyType
private var _lock = SpinLock()
// state
private var _disposables: Bag<Disposable>? = Bag()
public var isDisposed: Bool {
_lock.lock(); defer { _lock.unlock() }
return _disposables == nil
}
public override init() {
}
/**
Initializes a new instance of composite disposable with the specified number of disposables.
*/
public init(_ disposable1: Disposable, _ disposable2: Disposable) {
// This overload is here to make sure we are using optimized version up to 4 arguments.
let _ = _disposables!.insert(disposable1)
let _ = _disposables!.insert(disposable2)
}
/**
Initializes a new instance of composite disposable with the specified number of disposables.
*/
public init(_ disposable1: Disposable, _ disposable2: Disposable, _ disposable3: Disposable) {
// This overload is here to make sure we are using optimized version up to 4 arguments.
let _ = _disposables!.insert(disposable1)
let _ = _disposables!.insert(disposable2)
let _ = _disposables!.insert(disposable3)
}
/**
Initializes a new instance of composite disposable with the specified number of disposables.
*/
public init(_ disposable1: Disposable, _ disposable2: Disposable, _ disposable3: Disposable, _ disposable4: Disposable, _ disposables: Disposable...) {
// This overload is here to make sure we are using optimized version up to 4 arguments.
let _ = _disposables!.insert(disposable1)
let _ = _disposables!.insert(disposable2)
let _ = _disposables!.insert(disposable3)
let _ = _disposables!.insert(disposable4)
for disposable in disposables {
let _ = _disposables!.insert(disposable)
}
}
/**
Initializes a new instance of composite disposable with the specified number of disposables.
*/
public init(disposables: [Disposable]) {
for disposable in disposables {
let _ = _disposables!.insert(disposable)
}
}
/**
Adds a disposable to the CompositeDisposable or disposes the disposable if the CompositeDisposable is disposed.
- parameter disposable: Disposable to add.
- returns: Key that can be used to remove disposable from composite disposable. In case dispose bag was already
disposed `nil` will be returned.
*/
@available(*, deprecated, renamed: "insert(_:)")
public func addDisposable(_ disposable: Disposable) -> DisposeKey? {
return insert(disposable)
}
/**
Adds a disposable to the CompositeDisposable or disposes the disposable if the CompositeDisposable is disposed.
- parameter disposable: Disposable to add.
- returns: Key that can be used to remove disposable from composite disposable. In case dispose bag was already
disposed `nil` will be returned.
*/
public func insert(_ disposable: Disposable) -> DisposeKey? {
let key = _insert(disposable)
if key == nil {
disposable.dispose()
}
return key
}
private func _insert(_ disposable: Disposable) -> DisposeKey? {
_lock.lock(); defer { _lock.unlock() }
return _disposables?.insert(disposable)
}
/**
- returns: Gets the number of disposables contained in the `CompositeDisposable`.
*/
public var count: Int {
_lock.lock(); defer { _lock.unlock() }
return _disposables?.count ?? 0
}
/**
Removes and disposes the disposable identified by `disposeKey` from the CompositeDisposable.
- parameter disposeKey: Key used to identify disposable to be removed.
*/
@available(*, deprecated, renamed: "remove(for:)")
public func removeDisposable(_ disposeKey: DisposeKey) {
remove(for: disposeKey)
}
/**
Removes and disposes the disposable identified by `disposeKey` from the CompositeDisposable.
- parameter disposeKey: Key used to identify disposable to be removed.
*/
public func remove(for disposeKey: DisposeKey) {
_remove(for: disposeKey)?.dispose()
}
private func _remove(for disposeKey: DisposeKey) -> Disposable? {
_lock.lock(); defer { _lock.unlock() }
return _disposables?.removeKey(disposeKey)
}
/**
Disposes all disposables in the group and removes them from the group.
*/
public func dispose() {
if let disposables = _dispose() {
disposeAll(in: disposables)
}
}
private func _dispose() -> Bag<Disposable>? {
_lock.lock(); defer { _lock.unlock() }
let disposeBag = _disposables
_disposables = nil
return disposeBag
}
}

View File

@@ -0,0 +1,61 @@
//
// Disposables.swift
// Rx
//
// Created by Mohsen Ramezanpoor on 01/08/2016.
// Copyright © 2016 Mohsen Ramezanpoor. All rights reserved.
//
import Foundation
/**
A collection of utility methods for common disposable operations.
*/
public struct Disposables {
private init() {}
}
public extension Disposables {
private static let noOp: Disposable = NopDisposable()
/**
Creates a disposable that does nothing on disposal.
*/
static func create() -> Disposable {
return noOp
}
/**
Creates a disposable with the given disposables.
*/
static func create(_ disposable1: Disposable, _ disposable2: Disposable, _ disposable3: Disposable) -> Cancelable {
return CompositeDisposable(disposable1, disposable2, disposable3)
}
/**
Creates a disposable with the given disposables.
*/
static func create(_ disposable1: Disposable, _ disposable2: Disposable, _ disposable3: Disposable, _ disposables: Disposable ...) -> Cancelable {
var disposables = disposables
disposables.append(disposable1)
disposables.append(disposable2)
disposables.append(disposable3)
return CompositeDisposable(disposables: disposables)
}
/**
Creates a disposable with the given disposables.
*/
static func create(_ disposables: [Disposable]) -> Cancelable {
switch disposables.count {
case 2:
return Disposables.create(disposables[0], disposables[1])
default:
return CompositeDisposable(disposables: disposables)
}
}
}

View File

@@ -0,0 +1,104 @@
//
// DisposeBag.swift
// Rx
//
// Created by Krunoslav Zaher on 3/25/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
extension Disposable {
/**
Adds `self` to `bag`.
- parameter bag: `DisposeBag` to add `self` to.
*/
public func addDisposableTo(_ bag: DisposeBag) {
bag.insert(self)
}
}
/**
Thread safe bag that disposes added disposables on `deinit`.
This returns ARC (RAII) like resource management to `RxSwift`.
In case contained disposables need to be disposed, just put a different dispose bag
or create a new one in its place.
self.existingDisposeBag = DisposeBag()
In case explicit disposal is necessary, there is also `CompositeDisposable`.
*/
public final class DisposeBag: DisposeBase {
private var _lock = SpinLock()
// state
private var _disposables = [Disposable]()
private var _isDisposed = false
/**
Constructs new empty dispose bag.
*/
public override init() {
super.init()
}
/**
Adds `disposable` to be disposed when dispose bag is being deinited.
- parameter disposable: Disposable to add.
*/
@available(*, deprecated, renamed: "insert(_:)")
public func addDisposable(_ disposable: Disposable) {
insert(disposable)
}
/**
Adds `disposable` to be disposed when dispose bag is being deinited.
- parameter disposable: Disposable to add.
*/
public func insert(_ disposable: Disposable) {
_insert(disposable)?.dispose()
}
private func _insert(_ disposable: Disposable) -> Disposable? {
_lock.lock(); defer { _lock.unlock() }
if _isDisposed {
return disposable
}
_disposables.append(disposable)
return nil
}
/**
This is internal on purpose, take a look at `CompositeDisposable` instead.
*/
private func dispose() {
let oldDisposables = _dispose()
for disposable in oldDisposables {
disposable.dispose()
}
}
private func _dispose() -> [Disposable] {
_lock.lock(); defer { _lock.unlock() }
let disposables = _disposables
_disposables.removeAll(keepingCapacity: false)
_isDisposed = true
return disposables
}
deinit {
dispose()
}
}

View File

@@ -0,0 +1,26 @@
//
// DisposeBase.swift
// Rx
//
// Created by Krunoslav Zaher on 4/4/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Base class for all disposables.
*/
public class DisposeBase {
init() {
#if TRACE_RESOURCES
let _ = AtomicIncrement(&resourceCount)
#endif
}
deinit {
#if TRACE_RESOURCES
let _ = AtomicDecrement(&resourceCount)
#endif
}
}

View File

@@ -0,0 +1,33 @@
//
// NopDisposable.swift
// Rx
//
// Created by Krunoslav Zaher on 2/15/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Represents a disposable that does nothing on disposal.
Nop = No Operation
*/
public struct NopDisposable : Disposable {
/**
Singleton instance of `NopDisposable`.
*/
@available(*, deprecated, renamed: "Disposables.create()")
public static let instance: Disposable = NopDisposable()
init() {
}
/**
Does nothing.
*/
public func dispose() {
}
}

View File

@@ -0,0 +1,127 @@
//
// RefCountDisposable.swift
// Rx
//
// Created by Junior B. on 10/29/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Represents a disposable resource that only disposes its underlying disposable resource when all dependent disposable objects have been disposed.
*/
public final class RefCountDisposable : DisposeBase, Cancelable {
private var _lock = SpinLock()
private var _disposable = nil as Disposable?
private var _primaryDisposed = false
private var _count = 0
/**
- returns: Was resource disposed.
*/
public var isDisposed: Bool {
_lock.lock(); defer { _lock.unlock() }
return _disposable == nil
}
/**
Initializes a new instance of the `RefCountDisposable`.
*/
public init(disposable: Disposable) {
_disposable = disposable
super.init()
}
/**
Holds a dependent disposable that when disposed decreases the refcount on the underlying disposable.
When getter is called, a dependent disposable contributing to the reference count that manages the underlying disposable's lifetime is returned.
*/
public func retain() -> Disposable {
return _lock.calculateLocked {
if let _ = _disposable {
do {
let _ = try incrementChecked(&_count)
} catch (_) {
rxFatalError("RefCountDisposable increment failed")
}
return RefCountInnerDisposable(self)
} else {
return Disposables.create()
}
}
}
/**
Disposes the underlying disposable only when all dependent disposables have been disposed.
*/
public func dispose() {
let oldDisposable: Disposable? = _lock.calculateLocked {
if let oldDisposable = _disposable, !_primaryDisposed
{
_primaryDisposed = true
if (_count == 0)
{
_disposable = nil
return oldDisposable
}
}
return nil
}
if let disposable = oldDisposable {
disposable.dispose()
}
}
fileprivate func release() {
let oldDisposable: Disposable? = _lock.calculateLocked {
if let oldDisposable = _disposable {
do {
let _ = try decrementChecked(&_count)
} catch (_) {
rxFatalError("RefCountDisposable decrement on release failed")
}
guard _count >= 0 else {
rxFatalError("RefCountDisposable counter is lower than 0")
}
if _primaryDisposed && _count == 0 {
_disposable = nil
return oldDisposable
}
}
return nil
}
if let disposable = oldDisposable {
disposable.dispose()
}
}
}
internal final class RefCountInnerDisposable: DisposeBase, Disposable
{
private let _parent: RefCountDisposable
private var _isDisposed: AtomicInt = 0
init(_ parent: RefCountDisposable)
{
_parent = parent
super.init()
}
internal func dispose()
{
if AtomicCompareAndSwap(0, 1, &_isDisposed) {
_parent.release()
}
}
}

View File

@@ -0,0 +1,58 @@
//
// ScheduledDisposable.swift
// RxSwift
//
// Created by Krunoslav Zaher on 6/13/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
private let disposeScheduledDisposable: (ScheduledDisposable) -> Disposable = { sd in
sd.disposeInner()
return Disposables.create()
}
/**
Represents a disposable resource whose disposal invocation will be scheduled on the specified scheduler.
*/
public final class ScheduledDisposable : Cancelable {
public let scheduler: ImmediateSchedulerType
private var _isDisposed: AtomicInt = 0
// state
private var _disposable: Disposable?
/**
- returns: Was resource disposed.
*/
public var isDisposed: Bool {
return _isDisposed == 1
}
/**
Initializes a new instance of the `ScheduledDisposable` that uses a `scheduler` on which to dispose the `disposable`.
- parameter scheduler: Scheduler where the disposable resource will be disposed on.
- parameter disposable: Disposable resource to dispose on the given scheduler.
*/
public init(scheduler: ImmediateSchedulerType, disposable: Disposable) {
self.scheduler = scheduler
_disposable = disposable
}
/**
Disposes the wrapped disposable on the provided scheduler.
*/
public func dispose() {
let _ = scheduler.schedule(self, action: disposeScheduledDisposable)
}
func disposeInner() {
if AtomicCompareAndSwap(0, 1, &_isDisposed) {
_disposable!.dispose()
_disposable = nil
}
}
}

View File

@@ -0,0 +1,85 @@
//
// SerialDisposable.swift
// Rx
//
// Created by Krunoslav Zaher on 3/12/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Represents a disposable resource whose underlying disposable resource can be replaced by another disposable resource, causing automatic disposal of the previous underlying disposable resource.
*/
public final class SerialDisposable : DisposeBase, Cancelable {
private var _lock = SpinLock()
// state
private var _current = nil as Disposable?
private var _isDisposed = false
/**
- returns: Was resource disposed.
*/
public var isDisposed: Bool {
return _isDisposed
}
/**
Initializes a new instance of the `SerialDisposable`.
*/
override public init() {
super.init()
}
/**
Gets or sets the underlying disposable.
Assigning this property disposes the previous disposable object.
If the `SerialDisposable` has already been disposed, assignment to this property causes immediate disposal of the given disposable object.
*/
public var disposable: Disposable {
get {
return _lock.calculateLocked {
return self.disposable
}
}
set (newDisposable) {
let disposable: Disposable? = _lock.calculateLocked {
if _isDisposed {
return newDisposable
}
else {
let toDispose = _current
_current = newDisposable
return toDispose
}
}
if let disposable = disposable {
disposable.dispose()
}
}
}
/**
Disposes the underlying disposable as well as all future replacements.
*/
public func dispose() {
_dispose()?.dispose()
}
private func _dispose() -> Disposable? {
_lock.lock(); defer { _lock.unlock() }
if _isDisposed {
return nil
}
else {
_isDisposed = true
let current = _current
_current = nil
return current
}
}
}

View File

@@ -0,0 +1,89 @@
//
// SingleAssignmentDisposable.swift
// Rx
//
// Created by Krunoslav Zaher on 2/15/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Represents a disposable resource which only allows a single assignment of its underlying disposable resource.
If an underlying disposable resource has already been set, future attempts to set the underlying disposable resource will throw an exception.
*/
public class SingleAssignmentDisposable : DisposeBase, Disposable, Cancelable {
private var _lock = SpinLock()
// state
private var _isDisposed = false
private var _disposableSet = false
private var _disposable = nil as Disposable?
/**
- returns: A value that indicates whether the object is disposed.
*/
public var isDisposed: Bool {
return _isDisposed
}
/**
Initializes a new instance of the `SingleAssignmentDisposable`.
*/
public override init() {
super.init()
}
/**
Gets or sets the underlying disposable. After disposal, the result of getting this property is undefined.
**Throws exception if the `SingleAssignmentDisposable` has already been assigned to.**
*/
public var disposable: Disposable {
get {
_lock.lock(); defer { _lock.unlock() }
return _disposable ?? Disposables.create()
}
set {
_setDisposable(newValue)?.dispose()
}
}
private func _setDisposable(_ newValue: Disposable) -> Disposable? {
_lock.lock(); defer { _lock.unlock() }
if _disposableSet {
rxFatalError("oldState.disposable != nil")
}
_disposableSet = true
if _isDisposed {
return newValue
}
_disposable = newValue
return nil
}
/**
Disposes the underlying disposable.
*/
public func dispose() {
if _isDisposed {
return
}
_dispose()?.dispose()
}
private func _dispose() -> Disposable? {
_lock.lock(); defer { _lock.unlock() }
_isDisposed = true
let disposable = _disposable
_disposable = nil
return disposable
}
}

View File

@@ -0,0 +1,16 @@
//
// StableCompositeDisposable.swift
// RxSwift
//
// Created by Krunoslav Zaher on 6/12/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
public final class StableCompositeDisposable {
@available(*, deprecated, renamed: "Disposables.create")
public static func create(_ disposable1: Disposable, _ disposable2: Disposable) -> Disposable {
return Disposables.create(disposable1, disposable2)
}
}

View File

@@ -0,0 +1,23 @@
//
// SubscriptionDisposable.swift
// Rx
//
// Created by Krunoslav Zaher on 10/25/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
struct SubscriptionDisposable<T: SynchronizedUnsubscribeType> : Disposable {
private let _key: T.DisposeKey
private weak var _owner: T?
init(owner: T, key: T.DisposeKey) {
_owner = owner
_key = key
}
func dispose() {
_owner?.synchronizedUnsubscribe(_key)
}
}

View File

@@ -0,0 +1,72 @@
//
// Errors.swift
// Rx
//
// Created by Krunoslav Zaher on 3/28/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
let RxErrorDomain = "RxErrorDomain"
let RxCompositeFailures = "RxCompositeFailures"
/**
Generic Rx error codes.
*/
public enum RxError
: Swift.Error
, CustomDebugStringConvertible {
/**
Unknown error occured.
*/
case unknown
/**
Performing an action on disposed object.
*/
case disposed(object: AnyObject)
/**
Aritmetic overflow error.
*/
case overflow
/**
Argument out of range error.
*/
case argumentOutOfRange
/**
Sequence doesn't contain any elements.
*/
case noElements
/**
Sequence contains more than one element.
*/
case moreThanOneElement
/**
Timeout error.
*/
case timeout
}
public extension RxError {
/**
A textual representation of `self`, suitable for debugging.
*/
public var debugDescription: String {
switch self {
case .unknown:
return "Unknown error occured."
case .disposed(let object):
return "Object `\(object)` was already disposed."
case .overflow:
return "Arithmetic overflow occured."
case .argumentOutOfRange:
return "Argument out of range."
case .noElements:
return "Sequence doesn't contain any elements."
case .moreThanOneElement:
return "Sequence contains more than one element."
case .timeout:
return "Sequence timeout."
}
}
}

View File

@@ -0,0 +1,66 @@
//
// Event.swift
// Rx
//
// Created by Krunoslav Zaher on 2/8/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Represents a sequence event.
Sequence grammar:
next\* (error | completed)
*/
public enum Event<Element> {
/// Next element is produced.
case next(Element)
/// Sequence terminated with an error.
case error(Swift.Error)
/// Sequence completed successfully.
case completed
}
extension Event : CustomDebugStringConvertible {
/// - returns: Description of event.
public var debugDescription: String {
switch self {
case .next(let value):
return "next(\(value))"
case .error(let error):
return "error(\(error))"
case .completed:
return "completed"
}
}
}
extension Event {
/// - returns: Is `Completed` or `Error` event.
public var isStopEvent: Bool {
switch self {
case .next: return false
case .error, .completed: return true
}
}
/// - returns: If `Next` event, returns element value.
public var element: Element? {
if case .next(let value) = self {
return value
}
return nil
}
/// - returns: If `Error` event, returns error.
public var error: Swift.Error? {
if case .error(let error) = self {
return error
}
return nil
}
}

View File

@@ -0,0 +1,26 @@
//
// String+Rx.swift
// Rx
//
// Created by Krunoslav Zaher on 12/25/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
extension String {
/**
This is needed because on Linux Swift doesn't have `rangeOfString(..., options: .BackwardsSearch)`
*/
func lastIndexOf(_ character: Character) -> Index? {
var index = endIndex
while index > startIndex {
index = self.index(before: index)
if self[index] == character {
return index
}
}
return nil
}
}

View File

@@ -0,0 +1,40 @@
//
// ImmediateSchedulerType.swift
// RxSwift
//
// Created by Krunoslav Zaher on 5/31/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Represents an object that immediately schedules units of work.
*/
public protocol ImmediateSchedulerType {
/**
Schedules an action to be executed immediatelly.
- parameter state: State passed to the action to be executed.
- parameter action: Action to be executed.
- returns: The disposable object used to cancel the scheduled action (best effort).
*/
func schedule<StateType>(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable
}
extension ImmediateSchedulerType {
/**
Schedules an action to be executed recursively.
- parameter state: State passed to the action to be executed.
- parameter action: Action to execute recursively. The last parameter passed to the action is used to trigger recursive scheduling of the action, passing in recursive invocation state.
- returns: The disposable object used to cancel the scheduled action (best effort).
*/
public func scheduleRecursive<State>(_ state: State, action: @escaping (_ state: State, _ recurse: (State) -> ()) -> ()) -> Disposable {
let recursiveScheduler = RecursiveImmediateScheduler(action: action, scheduler: self)
recursiveScheduler.schedule(state)
return Disposables.create(with: recursiveScheduler.dispose)
}
}

View File

@@ -0,0 +1,52 @@
//
// Observable.swift
// Rx
//
// Created by Krunoslav Zaher on 2/8/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
A type-erased `ObservableType`.
It represents a push style sequence.
*/
public class Observable<Element> : ObservableType {
/**
Type of elements in sequence.
*/
public typealias E = Element
init() {
#if TRACE_RESOURCES
OSAtomicIncrement32(&resourceCount)
#endif
}
public func subscribe<O: ObserverType>(_ observer: O) -> Disposable where O.E == E {
abstractMethod()
}
public func asObservable() -> Observable<E> {
return self
}
deinit {
#if TRACE_RESOURCES
let _ = AtomicDecrement(&resourceCount)
#endif
}
// this is kind of ugly I know :(
// Swift compiler reports "Not supported yet" when trying to override protocol extensions, so ¯\_()_/¯
/**
Optimizations for map operator
*/
internal func composeMap<R>(_ selector: @escaping (Element) throws -> R) -> Observable<R> {
return Map(source: self, selector: selector)
}
}

View File

@@ -0,0 +1,26 @@
//
// ObservableConvertibleType.swift
// Rx
//
// Created by Krunoslav Zaher on 9/17/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Type that can be converted to observable sequence (`Observer<E>`).
*/
public protocol ObservableConvertibleType {
/**
Type of elements in sequence.
*/
associatedtype E
/**
Converts `self` to `Observable` sequence.
- returns: Observable sequence that represents `self`.
*/
func asObservable() -> Observable<E>
}

View File

@@ -0,0 +1,179 @@
//
// ObservableType+Extensions.swift
// Rx
//
// Created by Krunoslav Zaher on 2/21/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
extension ObservableType {
/**
Subscribes an event handler to an observable sequence.
- parameter on: Action to invoke for each event in the observable sequence.
- returns: Subscription object used to unsubscribe from the observable sequence.
*/
// @warn_unused_result(message: "http://git.io/rxs.ud")
public func subscribe(_ on: @escaping (Event<E>) -> Void)
-> Disposable {
let observer = AnonymousObserver { e in
on(e)
}
return self.subscribeSafe(observer)
}
#if DEBUG
/**
Subscribes an element handler, an error handler, a completion handler and disposed handler to an observable sequence.
- parameter onNext: Action to invoke for each element in the observable sequence.
- parameter onError: Action to invoke upon errored termination of the observable sequence.
- parameter onCompleted: Action to invoke upon graceful termination of the observable sequence.
- parameter onDisposed: Action to invoke upon any type of termination of sequence (if the sequence has
gracefully completed, errored, or if the generation is cancelled by disposing subscription).
- returns: Subscription object used to unsubscribe from the observable sequence.
*/
// @warn_unused_result(message: "http://git.io/rxs.ud")
public func subscribe(file: String = #file, line: UInt = #line, function: String = #function, onNext: ((E) -> Void)? = nil, onError: ((Swift.Error) -> Void)? = nil, onCompleted: (() -> Void)? = nil, onDisposed: (() -> Void)? = nil)
-> Disposable {
let disposable: Disposable
if let disposed = onDisposed {
disposable = Disposables.create(with: disposed)
}
else {
disposable = Disposables.create()
}
let observer = AnonymousObserver<E> { e in
switch e {
case .next(let value):
onNext?(value)
case .error(let e):
if let onError = onError {
onError(e)
}
else {
print("Received unhandled error: \(file):\(line):\(function) -> \(e)")
}
disposable.dispose()
case .completed:
onCompleted?()
disposable.dispose()
}
}
return Disposables.create(
self.subscribeSafe(observer),
disposable
)
}
#else
/**
Subscribes an element handler, an error handler, a completion handler and disposed handler to an observable sequence.
- parameter onNext: Action to invoke for each element in the observable sequence.
- parameter onError: Action to invoke upon errored termination of the observable sequence.
- parameter onCompleted: Action to invoke upon graceful termination of the observable sequence.
- parameter onDisposed: Action to invoke upon any type of termination of sequence (if the sequence has
gracefully completed, errored, or if the generation is cancelled by disposing subscription).
- returns: Subscription object used to unsubscribe from the observable sequence.
*/
// @warn_unused_result(message: "http://git.io/rxs.ud")
public func subscribe(onNext: ((E) -> Void)? = nil, onError: ((Swift.Error) -> Void)? = nil, onCompleted: (() -> Void)? = nil, onDisposed: (() -> Void)? = nil)
-> Disposable {
let disposable: Disposable
if let disposed = onDisposed {
disposable = Disposables.create(with: disposed)
}
else {
disposable = Disposables.create()
}
let observer = AnonymousObserver<E> { e in
switch e {
case .next(let value):
onNext?(value)
case .error(let e):
onError?(e)
disposable.dispose()
case .completed:
onCompleted?()
disposable.dispose()
}
}
return Disposables.create(
self.subscribeSafe(observer),
disposable
)
}
#endif
/**
Subscribes an element handler to an observable sequence.
- parameter onNext: Action to invoke for each element in the observable sequence.
- returns: Subscription object used to unsubscribe from the observable sequence.
*/
// @warn_unused_result(message: "http://git.io/rxs.ud")
@available(*, deprecated, renamed: "subscribe(onNext:)")
public func subscribeNext(_ onNext: @escaping (E) -> Void)
-> Disposable {
let observer = AnonymousObserver<E> { e in
if case .next(let value) = e {
onNext(value)
}
}
return self.subscribeSafe(observer)
}
/**
Subscribes an error handler to an observable sequence.
- parameter onError: Action to invoke upon errored termination of the observable sequence.
- returns: Subscription object used to unsubscribe from the observable sequence.
*/
// @warn_unused_result(message: "http://git.io/rxs.ud")
@available(*, deprecated, renamed: "subscribe(onError:)")
public func subscribeError(_ onError: @escaping (Swift.Error) -> Void)
-> Disposable {
let observer = AnonymousObserver<E> { e in
if case .error(let error) = e {
onError(error)
}
}
return self.subscribeSafe(observer)
}
/**
Subscribes a completion handler to an observable sequence.
- parameter onCompleted: Action to invoke upon graceful termination of the observable sequence.
- returns: Subscription object used to unsubscribe from the observable sequence.
*/
// @warn_unused_result(message: "http://git.io/rxs.ud")
@available(*, deprecated, renamed: "subscribe(onCompleted:)")
public func subscribeCompleted(_ onCompleted: @escaping () -> Void)
-> Disposable {
let observer = AnonymousObserver<E> { e in
if case .completed = e {
onCompleted()
}
}
return self.subscribeSafe(observer)
}
}
public extension ObservableType {
/**
All internal subscribe calls go through this method.
*/
// @warn_unused_result(message: "http://git.io/rxs.ud")
func subscribeSafe<O: ObserverType>(_ observer: O) -> Disposable where O.E == E {
return self.asObservable().subscribe(observer)
}
}

View File

@@ -0,0 +1,60 @@
//
// ObservableType.swift
// RxSwift
//
// Created by Krunoslav Zaher on 8/8/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Represents a push style sequence.
*/
public protocol ObservableType : ObservableConvertibleType {
/**
Type of elements in sequence.
*/
associatedtype E
/**
Subscribes `observer` to receive events for this sequence.
### Grammar
**Next\* (Error | Completed)?**
* sequences can produce zero or more elements so zero or more `Next` events can be sent to `observer`
* once an `Error` or `Completed` event is sent, the sequence terminates and can't produce any other elements
It is possible that events are sent from different threads, but no two events can be sent concurrently to
`observer`.
### Resource Management
When sequence sends `Complete` or `Error` event all internal resources that compute sequence elements
will be freed.
To cancel production of sequence elements and free resources immediatelly, call `dispose` on returned
subscription.
- returns: Subscription for `observer` that can be used to cancel production of sequence elements and free resources.
*/
// @warn_unused_result(message: "http://git.io/rxs.ud")
func subscribe<O: ObserverType>(_ observer: O) -> Disposable where O.E == E
}
extension ObservableType {
/**
Default implementation of converting `ObservableType` to `Observable`.
*/
// @warn_unused_result(message:"http://git.io/rxs.uo")
public func asObservable() -> Observable<E> {
// temporary workaround
//return Observable.create(subscribe: self.subscribe)
return Observable.create { o in
return self.subscribe(o)
}
}
}

View File

@@ -0,0 +1,47 @@
//
// AddRef.swift
// Rx
//
// Created by Junior B. on 30/10/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class AddRefSink<O: ObserverType> : Sink<O>, ObserverType {
typealias Element = O.E
override init(observer: O) {
super.init(observer: observer)
}
func on(_ event: Event<Element>) {
switch event {
case .next(_):
forwardOn(event)
case .completed, .error(_):
forwardOn(event)
dispose()
}
}
}
class AddRef<Element> : Producer<Element> {
typealias EventHandler = (Event<Element>) throws -> Void
private let _source: Observable<Element>
private let _refCount: RefCountDisposable
init(source: Observable<Element>, refCount: RefCountDisposable) {
_source = source
_refCount = refCount
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == Element {
let releaseDisposable = _refCount.retain()
let sink = AddRefSink(observer: observer)
sink.disposable = Disposables.create(releaseDisposable, _source.subscribeSafe(sink))
return sink
}
}

View File

@@ -0,0 +1,122 @@
//
// Amb.swift
// RxSwift
//
// Created by Krunoslav Zaher on 6/14/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
enum AmbState {
case neither
case left
case right
}
class AmbObserver<ElementType, O: ObserverType> : ObserverType where O.E == ElementType {
typealias Element = ElementType
typealias Parent = AmbSink<ElementType, O>
typealias This = AmbObserver<ElementType, O>
typealias Sink = (This, Event<Element>) -> Void
fileprivate let _parent: Parent
fileprivate var _sink: Sink
fileprivate var _cancel: Disposable
init(parent: Parent, cancel: Disposable, sink: @escaping Sink) {
#if TRACE_RESOURCES
let _ = AtomicIncrement(&resourceCount)
#endif
_parent = parent
_sink = sink
_cancel = cancel
}
func on(_ event: Event<Element>) {
_sink(self, event)
if event.isStopEvent {
_cancel.dispose()
}
}
deinit {
#if TRACE_RESOURCES
let _ = AtomicDecrement(&resourceCount)
#endif
}
}
class AmbSink<ElementType, O: ObserverType> : Sink<O> where O.E == ElementType {
typealias Parent = Amb<ElementType>
typealias AmbObserverType = AmbObserver<ElementType, O>
private let _parent: Parent
private let _lock = NSRecursiveLock()
// state
private var _choice = AmbState.neither
init(parent: Parent, observer: O) {
_parent = parent
super.init(observer: observer)
}
func run() -> Disposable {
let subscription1 = SingleAssignmentDisposable()
let subscription2 = SingleAssignmentDisposable()
let disposeAll = Disposables.create(subscription1, subscription2)
let forwardEvent = { (o: AmbObserverType, event: Event<ElementType>) -> Void in
self.forwardOn(event)
}
let decide = { (o: AmbObserverType, event: Event<ElementType>, me: AmbState, otherSubscription: Disposable) in
self._lock.performLocked {
if self._choice == .neither {
self._choice = me
o._sink = forwardEvent
o._cancel = disposeAll
otherSubscription.dispose()
}
if self._choice == me {
self.forwardOn(event)
if event.isStopEvent {
self.dispose()
}
}
}
}
let sink1 = AmbObserver(parent: self, cancel: subscription1) { o, e in
decide(o, e, .left, subscription2)
}
let sink2 = AmbObserver(parent: self, cancel: subscription1) { o, e in
decide(o, e, .right, subscription1)
}
subscription1.disposable = _parent._left.subscribe(sink1)
subscription2.disposable = _parent._right.subscribe(sink2)
return disposeAll
}
}
class Amb<Element>: Producer<Element> {
fileprivate let _left: Observable<Element>
fileprivate let _right: Observable<Element>
init(left: Observable<Element>, right: Observable<Element>) {
_left = left
_right = right
}
override func run<O : ObserverType>(_ observer: O) -> Disposable where O.E == Element {
let sink = AmbSink(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}

View File

@@ -0,0 +1,56 @@
//
// AnonymousObservable.swift
// Rx
//
// Created by Krunoslav Zaher on 2/8/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class AnonymousObservableSink<O: ObserverType> : Sink<O>, ObserverType {
typealias E = O.E
typealias Parent = AnonymousObservable<E>
// state
private var _isStopped: AtomicInt = 0
override init(observer: O) {
super.init(observer: observer)
}
func on(_ event: Event<E>) {
switch event {
case .next:
if _isStopped == 1 {
return
}
forwardOn(event)
case .error, .completed:
if AtomicCompareAndSwap(0, 1, &_isStopped) {
forwardOn(event)
dispose()
}
}
}
func run(_ parent: Parent) -> Disposable {
return parent._subscribeHandler(AnyObserver(self))
}
}
class AnonymousObservable<Element> : Producer<Element> {
typealias SubscribeHandler = (AnyObserver<Element>) -> Disposable
let _subscribeHandler: SubscribeHandler
init(_ subscribeHandler: @escaping SubscribeHandler) {
_subscribeHandler = subscribeHandler
}
override func run<O : ObserverType>(_ observer: O) -> Disposable where O.E == Element {
let sink = AnonymousObservableSink(observer: observer)
sink.disposable = sink.run(self)
return sink
}
}

View File

@@ -0,0 +1,119 @@
//
// Buffer.swift
// Rx
//
// Created by Krunoslav Zaher on 9/13/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class BufferTimeCount<Element> : Producer<[Element]> {
fileprivate let _timeSpan: RxTimeInterval
fileprivate let _count: Int
fileprivate let _scheduler: SchedulerType
fileprivate let _source: Observable<Element>
init(source: Observable<Element>, timeSpan: RxTimeInterval, count: Int, scheduler: SchedulerType) {
_source = source
_timeSpan = timeSpan
_count = count
_scheduler = scheduler
}
override func run<O : ObserverType>(_ observer: O) -> Disposable where O.E == [Element] {
let sink = BufferTimeCountSink(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}
class BufferTimeCountSink<Element, O: ObserverType>
: Sink<O>
, LockOwnerType
, ObserverType
, SynchronizedOnType where O.E == [Element] {
typealias Parent = BufferTimeCount<Element>
typealias E = Element
private let _parent: Parent
let _lock = NSRecursiveLock()
// state
private let _timerD = SerialDisposable()
private var _buffer = [Element]()
private var _windowID = 0
init(parent: Parent, observer: O) {
_parent = parent
super.init(observer: observer)
}
func run() -> Disposable {
createTimer(_windowID)
return Disposables.create(_timerD, _parent._source.subscribe(self))
}
func startNewWindowAndSendCurrentOne() {
_windowID = _windowID &+ 1
let windowID = _windowID
let buffer = _buffer
_buffer = []
forwardOn(.next(buffer))
createTimer(windowID)
}
func on(_ event: Event<E>) {
synchronizedOn(event)
}
func _synchronized_on(_ event: Event<E>) {
switch event {
case .next(let element):
_buffer.append(element)
if _buffer.count == _parent._count {
startNewWindowAndSendCurrentOne()
}
case .error(let error):
_buffer = []
forwardOn(.error(error))
dispose()
case .completed:
forwardOn(.next(_buffer))
forwardOn(.completed)
dispose()
}
}
func createTimer(_ windowID: Int) {
if _timerD.isDisposed {
return
}
if _windowID != windowID {
return
}
let nextTimer = SingleAssignmentDisposable()
_timerD.disposable = nextTimer
nextTimer.disposable = _parent._scheduler.scheduleRelative(windowID, dueTime: _parent._timeSpan) { previousWindowID in
self._lock.performLocked {
if previousWindowID != self._windowID {
return
}
self.startNewWindowAndSendCurrentOne()
}
return Disposables.create()
}
}
}

View File

@@ -0,0 +1,162 @@
//
// Catch.swift
// RxSwift
//
// Created by Krunoslav Zaher on 4/19/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
// catch with callback
class CatchSinkProxy<O: ObserverType> : ObserverType {
typealias E = O.E
typealias Parent = CatchSink<O>
private let _parent: Parent
init(parent: Parent) {
_parent = parent
}
func on(_ event: Event<E>) {
_parent.forwardOn(event)
switch event {
case .next:
break
case .error, .completed:
_parent.dispose()
}
}
}
class CatchSink<O: ObserverType> : Sink<O>, ObserverType {
typealias E = O.E
typealias Parent = Catch<E>
private let _parent: Parent
private let _subscription = SerialDisposable()
init(parent: Parent, observer: O) {
_parent = parent
super.init(observer: observer)
}
func run() -> Disposable {
let d1 = SingleAssignmentDisposable()
_subscription.disposable = d1
d1.disposable = _parent._source.subscribe(self)
return _subscription
}
func on(_ event: Event<E>) {
switch event {
case .next:
forwardOn(event)
case .completed:
forwardOn(event)
dispose()
case .error(let error):
do {
let catchSequence = try _parent._handler(error)
let observer = CatchSinkProxy(parent: self)
_subscription.disposable = catchSequence.subscribe(observer)
}
catch let e {
forwardOn(.error(e))
dispose()
}
}
}
}
class Catch<Element> : Producer<Element> {
typealias Handler = (Swift.Error) throws -> Observable<Element>
fileprivate let _source: Observable<Element>
fileprivate let _handler: Handler
init(source: Observable<Element>, handler: @escaping Handler) {
_source = source
_handler = handler
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == Element {
let sink = CatchSink(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}
// catch enumerable
class CatchSequenceSink<S: Sequence, O: ObserverType>
: TailRecursiveSink<S, O>
, ObserverType where S.Iterator.Element : ObservableConvertibleType, S.Iterator.Element.E == O.E {
typealias Element = O.E
typealias Parent = CatchSequence<S>
private var _lastError: Swift.Error?
override init(observer: O) {
super.init(observer: observer)
}
func on(_ event: Event<Element>) {
switch event {
case .next:
forwardOn(event)
case .error(let error):
_lastError = error
schedule(.moveNext)
case .completed:
forwardOn(event)
dispose()
}
}
override func subscribeToNext(_ source: Observable<E>) -> Disposable {
return source.subscribe(self)
}
override func done() {
if let lastError = _lastError {
forwardOn(.error(lastError))
}
else {
forwardOn(.completed)
}
self.dispose()
}
override func extract(_ observable: Observable<Element>) -> SequenceGenerator? {
if let onError = observable as? CatchSequence<S> {
return (onError.sources.makeIterator(), nil)
}
else {
return nil
}
}
}
class CatchSequence<S: Sequence> : Producer<S.Iterator.Element.E> where S.Iterator.Element : ObservableConvertibleType {
typealias Element = S.Iterator.Element.E
let sources: S
init(sources: S) {
self.sources = sources
}
override func run<O : ObserverType>(_ observer: O) -> Disposable where O.E == Element {
let sink = CatchSequenceSink<S, O>(observer: observer)
sink.disposable = sink.run((self.sources.makeIterator(), nil))
return sink
}
}

View File

@@ -0,0 +1,125 @@
//
// CombineLatest+Collection.swift
// Rx
//
// Created by Krunoslav Zaher on 8/29/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class CombineLatestCollectionTypeSink<C: Collection, R, O: ObserverType>
: Sink<O> where C.Iterator.Element : ObservableConvertibleType, O.E == R {
typealias Parent = CombineLatestCollectionType<C, R>
typealias SourceElement = C.Iterator.Element.E
let _parent: Parent
let _lock = NSRecursiveLock()
// state
var _numberOfValues = 0
var _values: [SourceElement?]
var _isDone: [Bool]
var _numberOfDone = 0
var _subscriptions: [SingleAssignmentDisposable]
init(parent: Parent, observer: O) {
_parent = parent
_values = [SourceElement?](repeating: nil, count: parent._count)
_isDone = [Bool](repeating: false, count: parent._count)
_subscriptions = Array<SingleAssignmentDisposable>()
_subscriptions.reserveCapacity(parent._count)
for _ in 0 ..< parent._count {
_subscriptions.append(SingleAssignmentDisposable())
}
super.init(observer: observer)
}
func on(_ event: Event<SourceElement>, atIndex: Int) {
_lock.lock(); defer { _lock.unlock() } // {
switch event {
case .next(let element):
if _values[atIndex] == nil {
_numberOfValues += 1
}
_values[atIndex] = element
if _numberOfValues < _parent._count {
let numberOfOthersThatAreDone = self._numberOfDone - (_isDone[atIndex] ? 1 : 0)
if numberOfOthersThatAreDone == self._parent._count - 1 {
forwardOn(.completed)
dispose()
}
return
}
do {
let result = try _parent._resultSelector(_values.map { $0! })
forwardOn(.next(result))
}
catch let error {
forwardOn(.error(error))
dispose()
}
case .error(let error):
forwardOn(.error(error))
dispose()
case .completed:
if _isDone[atIndex] {
return
}
_isDone[atIndex] = true
_numberOfDone += 1
if _numberOfDone == self._parent._count {
forwardOn(.completed)
dispose()
}
else {
_subscriptions[atIndex].dispose()
}
}
// }
}
func run() -> Disposable {
var j = 0
for i in _parent._sources {
let index = j
let source = i.asObservable()
_subscriptions[j].disposable = source.subscribe(AnyObserver { event in
self.on(event, atIndex: index)
})
j += 1
}
return Disposables.create(_subscriptions)
}
}
class CombineLatestCollectionType<C: Collection, R> : Producer<R> where C.Iterator.Element : ObservableConvertibleType {
typealias ResultSelector = ([C.Iterator.Element.E]) throws -> R
let _sources: C
let _resultSelector: ResultSelector
let _count: Int
init(sources: C, resultSelector: @escaping ResultSelector) {
_sources = sources
_resultSelector = resultSelector
_count = Int(self._sources.count.toIntMax())
}
override func run<O : ObserverType>(_ observer: O) -> Disposable where O.E == R {
let sink = CombineLatestCollectionTypeSink(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}

View File

@@ -0,0 +1,726 @@
// This file is autogenerated. Take a look at `Preprocessor` target in RxSwift project
//
// CombineLatest+arity.swift
// RxSwift
//
// Created by Krunoslav Zaher on 4/22/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
// 2
extension Observable {
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element.
- seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html)
- parameter resultSelector: Function to invoke whenever any of the sources produces an element.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
// @warn_unused_result(message:"http://git.io/rxs.uo")
public static func combineLatest<O1: ObservableType, O2: ObservableType>
(_ source1: O1, _ source2: O2, resultSelector: @escaping (O1.E, O2.E) throws -> E)
-> Observable<E> {
return CombineLatest2(
source1: source1.asObservable(), source2: source2.asObservable(),
resultSelector: resultSelector
)
}
}
class CombineLatestSink2_<E1, E2, O: ObserverType> : CombineLatestSink<O> {
typealias R = O.E
typealias Parent = CombineLatest2<E1, E2, R>
let _parent: Parent
var _latestElement1: E1! = nil
var _latestElement2: E2! = nil
init(parent: Parent, observer: O) {
_parent = parent
super.init(arity: 2, observer: observer)
}
func run() -> Disposable {
let subscription1 = SingleAssignmentDisposable()
let subscription2 = SingleAssignmentDisposable()
let observer1 = CombineLatestObserver(lock: _lock, parent: self, index: 0, setLatestValue: { (e: E1) -> Void in self._latestElement1 = e }, this: subscription1)
let observer2 = CombineLatestObserver(lock: _lock, parent: self, index: 1, setLatestValue: { (e: E2) -> Void in self._latestElement2 = e }, this: subscription2)
subscription1.disposable = _parent._source1.subscribe(observer1)
subscription2.disposable = _parent._source2.subscribe(observer2)
return Disposables.create([
subscription1,
subscription2
])
}
override func getResult() throws -> R {
return try _parent._resultSelector(_latestElement1, _latestElement2)
}
}
class CombineLatest2<E1, E2, R> : Producer<R> {
typealias ResultSelector = (E1, E2) throws -> R
let _source1: Observable<E1>
let _source2: Observable<E2>
let _resultSelector: ResultSelector
init(source1: Observable<E1>, source2: Observable<E2>, resultSelector: @escaping ResultSelector) {
_source1 = source1
_source2 = source2
_resultSelector = resultSelector
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == R {
let sink = CombineLatestSink2_(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}
// 3
extension Observable {
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element.
- seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html)
- parameter resultSelector: Function to invoke whenever any of the sources produces an element.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
// @warn_unused_result(message:"http://git.io/rxs.uo")
public static func combineLatest<O1: ObservableType, O2: ObservableType, O3: ObservableType>
(_ source1: O1, _ source2: O2, _ source3: O3, resultSelector: @escaping (O1.E, O2.E, O3.E) throws -> E)
-> Observable<E> {
return CombineLatest3(
source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(),
resultSelector: resultSelector
)
}
}
class CombineLatestSink3_<E1, E2, E3, O: ObserverType> : CombineLatestSink<O> {
typealias R = O.E
typealias Parent = CombineLatest3<E1, E2, E3, R>
let _parent: Parent
var _latestElement1: E1! = nil
var _latestElement2: E2! = nil
var _latestElement3: E3! = nil
init(parent: Parent, observer: O) {
_parent = parent
super.init(arity: 3, observer: observer)
}
func run() -> Disposable {
let subscription1 = SingleAssignmentDisposable()
let subscription2 = SingleAssignmentDisposable()
let subscription3 = SingleAssignmentDisposable()
let observer1 = CombineLatestObserver(lock: _lock, parent: self, index: 0, setLatestValue: { (e: E1) -> Void in self._latestElement1 = e }, this: subscription1)
let observer2 = CombineLatestObserver(lock: _lock, parent: self, index: 1, setLatestValue: { (e: E2) -> Void in self._latestElement2 = e }, this: subscription2)
let observer3 = CombineLatestObserver(lock: _lock, parent: self, index: 2, setLatestValue: { (e: E3) -> Void in self._latestElement3 = e }, this: subscription3)
subscription1.disposable = _parent._source1.subscribe(observer1)
subscription2.disposable = _parent._source2.subscribe(observer2)
subscription3.disposable = _parent._source3.subscribe(observer3)
return Disposables.create([
subscription1,
subscription2,
subscription3
])
}
override func getResult() throws -> R {
return try _parent._resultSelector(_latestElement1, _latestElement2, _latestElement3)
}
}
class CombineLatest3<E1, E2, E3, R> : Producer<R> {
typealias ResultSelector = (E1, E2, E3) throws -> R
let _source1: Observable<E1>
let _source2: Observable<E2>
let _source3: Observable<E3>
let _resultSelector: ResultSelector
init(source1: Observable<E1>, source2: Observable<E2>, source3: Observable<E3>, resultSelector: @escaping ResultSelector) {
_source1 = source1
_source2 = source2
_source3 = source3
_resultSelector = resultSelector
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == R {
let sink = CombineLatestSink3_(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}
// 4
extension Observable {
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element.
- seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html)
- parameter resultSelector: Function to invoke whenever any of the sources produces an element.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
// @warn_unused_result(message:"http://git.io/rxs.uo")
public static func combineLatest<O1: ObservableType, O2: ObservableType, O3: ObservableType, O4: ObservableType>
(_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, resultSelector: @escaping (O1.E, O2.E, O3.E, O4.E) throws -> E)
-> Observable<E> {
return CombineLatest4(
source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(),
resultSelector: resultSelector
)
}
}
class CombineLatestSink4_<E1, E2, E3, E4, O: ObserverType> : CombineLatestSink<O> {
typealias R = O.E
typealias Parent = CombineLatest4<E1, E2, E3, E4, R>
let _parent: Parent
var _latestElement1: E1! = nil
var _latestElement2: E2! = nil
var _latestElement3: E3! = nil
var _latestElement4: E4! = nil
init(parent: Parent, observer: O) {
_parent = parent
super.init(arity: 4, observer: observer)
}
func run() -> Disposable {
let subscription1 = SingleAssignmentDisposable()
let subscription2 = SingleAssignmentDisposable()
let subscription3 = SingleAssignmentDisposable()
let subscription4 = SingleAssignmentDisposable()
let observer1 = CombineLatestObserver(lock: _lock, parent: self, index: 0, setLatestValue: { (e: E1) -> Void in self._latestElement1 = e }, this: subscription1)
let observer2 = CombineLatestObserver(lock: _lock, parent: self, index: 1, setLatestValue: { (e: E2) -> Void in self._latestElement2 = e }, this: subscription2)
let observer3 = CombineLatestObserver(lock: _lock, parent: self, index: 2, setLatestValue: { (e: E3) -> Void in self._latestElement3 = e }, this: subscription3)
let observer4 = CombineLatestObserver(lock: _lock, parent: self, index: 3, setLatestValue: { (e: E4) -> Void in self._latestElement4 = e }, this: subscription4)
subscription1.disposable = _parent._source1.subscribe(observer1)
subscription2.disposable = _parent._source2.subscribe(observer2)
subscription3.disposable = _parent._source3.subscribe(observer3)
subscription4.disposable = _parent._source4.subscribe(observer4)
return Disposables.create([
subscription1,
subscription2,
subscription3,
subscription4
])
}
override func getResult() throws -> R {
return try _parent._resultSelector(_latestElement1, _latestElement2, _latestElement3, _latestElement4)
}
}
class CombineLatest4<E1, E2, E3, E4, R> : Producer<R> {
typealias ResultSelector = (E1, E2, E3, E4) throws -> R
let _source1: Observable<E1>
let _source2: Observable<E2>
let _source3: Observable<E3>
let _source4: Observable<E4>
let _resultSelector: ResultSelector
init(source1: Observable<E1>, source2: Observable<E2>, source3: Observable<E3>, source4: Observable<E4>, resultSelector: @escaping ResultSelector) {
_source1 = source1
_source2 = source2
_source3 = source3
_source4 = source4
_resultSelector = resultSelector
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == R {
let sink = CombineLatestSink4_(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}
// 5
extension Observable {
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element.
- seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html)
- parameter resultSelector: Function to invoke whenever any of the sources produces an element.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
// @warn_unused_result(message:"http://git.io/rxs.uo")
public static func combineLatest<O1: ObservableType, O2: ObservableType, O3: ObservableType, O4: ObservableType, O5: ObservableType>
(_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, resultSelector: @escaping (O1.E, O2.E, O3.E, O4.E, O5.E) throws -> E)
-> Observable<E> {
return CombineLatest5(
source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(),
resultSelector: resultSelector
)
}
}
class CombineLatestSink5_<E1, E2, E3, E4, E5, O: ObserverType> : CombineLatestSink<O> {
typealias R = O.E
typealias Parent = CombineLatest5<E1, E2, E3, E4, E5, R>
let _parent: Parent
var _latestElement1: E1! = nil
var _latestElement2: E2! = nil
var _latestElement3: E3! = nil
var _latestElement4: E4! = nil
var _latestElement5: E5! = nil
init(parent: Parent, observer: O) {
_parent = parent
super.init(arity: 5, observer: observer)
}
func run() -> Disposable {
let subscription1 = SingleAssignmentDisposable()
let subscription2 = SingleAssignmentDisposable()
let subscription3 = SingleAssignmentDisposable()
let subscription4 = SingleAssignmentDisposable()
let subscription5 = SingleAssignmentDisposable()
let observer1 = CombineLatestObserver(lock: _lock, parent: self, index: 0, setLatestValue: { (e: E1) -> Void in self._latestElement1 = e }, this: subscription1)
let observer2 = CombineLatestObserver(lock: _lock, parent: self, index: 1, setLatestValue: { (e: E2) -> Void in self._latestElement2 = e }, this: subscription2)
let observer3 = CombineLatestObserver(lock: _lock, parent: self, index: 2, setLatestValue: { (e: E3) -> Void in self._latestElement3 = e }, this: subscription3)
let observer4 = CombineLatestObserver(lock: _lock, parent: self, index: 3, setLatestValue: { (e: E4) -> Void in self._latestElement4 = e }, this: subscription4)
let observer5 = CombineLatestObserver(lock: _lock, parent: self, index: 4, setLatestValue: { (e: E5) -> Void in self._latestElement5 = e }, this: subscription5)
subscription1.disposable = _parent._source1.subscribe(observer1)
subscription2.disposable = _parent._source2.subscribe(observer2)
subscription3.disposable = _parent._source3.subscribe(observer3)
subscription4.disposable = _parent._source4.subscribe(observer4)
subscription5.disposable = _parent._source5.subscribe(observer5)
return Disposables.create([
subscription1,
subscription2,
subscription3,
subscription4,
subscription5
])
}
override func getResult() throws -> R {
return try _parent._resultSelector(_latestElement1, _latestElement2, _latestElement3, _latestElement4, _latestElement5)
}
}
class CombineLatest5<E1, E2, E3, E4, E5, R> : Producer<R> {
typealias ResultSelector = (E1, E2, E3, E4, E5) throws -> R
let _source1: Observable<E1>
let _source2: Observable<E2>
let _source3: Observable<E3>
let _source4: Observable<E4>
let _source5: Observable<E5>
let _resultSelector: ResultSelector
init(source1: Observable<E1>, source2: Observable<E2>, source3: Observable<E3>, source4: Observable<E4>, source5: Observable<E5>, resultSelector: @escaping ResultSelector) {
_source1 = source1
_source2 = source2
_source3 = source3
_source4 = source4
_source5 = source5
_resultSelector = resultSelector
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == R {
let sink = CombineLatestSink5_(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}
// 6
extension Observable {
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element.
- seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html)
- parameter resultSelector: Function to invoke whenever any of the sources produces an element.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
// @warn_unused_result(message:"http://git.io/rxs.uo")
public static func combineLatest<O1: ObservableType, O2: ObservableType, O3: ObservableType, O4: ObservableType, O5: ObservableType, O6: ObservableType>
(_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, resultSelector: @escaping (O1.E, O2.E, O3.E, O4.E, O5.E, O6.E) throws -> E)
-> Observable<E> {
return CombineLatest6(
source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), source6: source6.asObservable(),
resultSelector: resultSelector
)
}
}
class CombineLatestSink6_<E1, E2, E3, E4, E5, E6, O: ObserverType> : CombineLatestSink<O> {
typealias R = O.E
typealias Parent = CombineLatest6<E1, E2, E3, E4, E5, E6, R>
let _parent: Parent
var _latestElement1: E1! = nil
var _latestElement2: E2! = nil
var _latestElement3: E3! = nil
var _latestElement4: E4! = nil
var _latestElement5: E5! = nil
var _latestElement6: E6! = nil
init(parent: Parent, observer: O) {
_parent = parent
super.init(arity: 6, observer: observer)
}
func run() -> Disposable {
let subscription1 = SingleAssignmentDisposable()
let subscription2 = SingleAssignmentDisposable()
let subscription3 = SingleAssignmentDisposable()
let subscription4 = SingleAssignmentDisposable()
let subscription5 = SingleAssignmentDisposable()
let subscription6 = SingleAssignmentDisposable()
let observer1 = CombineLatestObserver(lock: _lock, parent: self, index: 0, setLatestValue: { (e: E1) -> Void in self._latestElement1 = e }, this: subscription1)
let observer2 = CombineLatestObserver(lock: _lock, parent: self, index: 1, setLatestValue: { (e: E2) -> Void in self._latestElement2 = e }, this: subscription2)
let observer3 = CombineLatestObserver(lock: _lock, parent: self, index: 2, setLatestValue: { (e: E3) -> Void in self._latestElement3 = e }, this: subscription3)
let observer4 = CombineLatestObserver(lock: _lock, parent: self, index: 3, setLatestValue: { (e: E4) -> Void in self._latestElement4 = e }, this: subscription4)
let observer5 = CombineLatestObserver(lock: _lock, parent: self, index: 4, setLatestValue: { (e: E5) -> Void in self._latestElement5 = e }, this: subscription5)
let observer6 = CombineLatestObserver(lock: _lock, parent: self, index: 5, setLatestValue: { (e: E6) -> Void in self._latestElement6 = e }, this: subscription6)
subscription1.disposable = _parent._source1.subscribe(observer1)
subscription2.disposable = _parent._source2.subscribe(observer2)
subscription3.disposable = _parent._source3.subscribe(observer3)
subscription4.disposable = _parent._source4.subscribe(observer4)
subscription5.disposable = _parent._source5.subscribe(observer5)
subscription6.disposable = _parent._source6.subscribe(observer6)
return Disposables.create([
subscription1,
subscription2,
subscription3,
subscription4,
subscription5,
subscription6
])
}
override func getResult() throws -> R {
return try _parent._resultSelector(_latestElement1, _latestElement2, _latestElement3, _latestElement4, _latestElement5, _latestElement6)
}
}
class CombineLatest6<E1, E2, E3, E4, E5, E6, R> : Producer<R> {
typealias ResultSelector = (E1, E2, E3, E4, E5, E6) throws -> R
let _source1: Observable<E1>
let _source2: Observable<E2>
let _source3: Observable<E3>
let _source4: Observable<E4>
let _source5: Observable<E5>
let _source6: Observable<E6>
let _resultSelector: ResultSelector
init(source1: Observable<E1>, source2: Observable<E2>, source3: Observable<E3>, source4: Observable<E4>, source5: Observable<E5>, source6: Observable<E6>, resultSelector: @escaping ResultSelector) {
_source1 = source1
_source2 = source2
_source3 = source3
_source4 = source4
_source5 = source5
_source6 = source6
_resultSelector = resultSelector
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == R {
let sink = CombineLatestSink6_(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}
// 7
extension Observable {
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element.
- seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html)
- parameter resultSelector: Function to invoke whenever any of the sources produces an element.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
// @warn_unused_result(message:"http://git.io/rxs.uo")
public static func combineLatest<O1: ObservableType, O2: ObservableType, O3: ObservableType, O4: ObservableType, O5: ObservableType, O6: ObservableType, O7: ObservableType>
(_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, resultSelector: @escaping (O1.E, O2.E, O3.E, O4.E, O5.E, O6.E, O7.E) throws -> E)
-> Observable<E> {
return CombineLatest7(
source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), source6: source6.asObservable(), source7: source7.asObservable(),
resultSelector: resultSelector
)
}
}
class CombineLatestSink7_<E1, E2, E3, E4, E5, E6, E7, O: ObserverType> : CombineLatestSink<O> {
typealias R = O.E
typealias Parent = CombineLatest7<E1, E2, E3, E4, E5, E6, E7, R>
let _parent: Parent
var _latestElement1: E1! = nil
var _latestElement2: E2! = nil
var _latestElement3: E3! = nil
var _latestElement4: E4! = nil
var _latestElement5: E5! = nil
var _latestElement6: E6! = nil
var _latestElement7: E7! = nil
init(parent: Parent, observer: O) {
_parent = parent
super.init(arity: 7, observer: observer)
}
func run() -> Disposable {
let subscription1 = SingleAssignmentDisposable()
let subscription2 = SingleAssignmentDisposable()
let subscription3 = SingleAssignmentDisposable()
let subscription4 = SingleAssignmentDisposable()
let subscription5 = SingleAssignmentDisposable()
let subscription6 = SingleAssignmentDisposable()
let subscription7 = SingleAssignmentDisposable()
let observer1 = CombineLatestObserver(lock: _lock, parent: self, index: 0, setLatestValue: { (e: E1) -> Void in self._latestElement1 = e }, this: subscription1)
let observer2 = CombineLatestObserver(lock: _lock, parent: self, index: 1, setLatestValue: { (e: E2) -> Void in self._latestElement2 = e }, this: subscription2)
let observer3 = CombineLatestObserver(lock: _lock, parent: self, index: 2, setLatestValue: { (e: E3) -> Void in self._latestElement3 = e }, this: subscription3)
let observer4 = CombineLatestObserver(lock: _lock, parent: self, index: 3, setLatestValue: { (e: E4) -> Void in self._latestElement4 = e }, this: subscription4)
let observer5 = CombineLatestObserver(lock: _lock, parent: self, index: 4, setLatestValue: { (e: E5) -> Void in self._latestElement5 = e }, this: subscription5)
let observer6 = CombineLatestObserver(lock: _lock, parent: self, index: 5, setLatestValue: { (e: E6) -> Void in self._latestElement6 = e }, this: subscription6)
let observer7 = CombineLatestObserver(lock: _lock, parent: self, index: 6, setLatestValue: { (e: E7) -> Void in self._latestElement7 = e }, this: subscription7)
subscription1.disposable = _parent._source1.subscribe(observer1)
subscription2.disposable = _parent._source2.subscribe(observer2)
subscription3.disposable = _parent._source3.subscribe(observer3)
subscription4.disposable = _parent._source4.subscribe(observer4)
subscription5.disposable = _parent._source5.subscribe(observer5)
subscription6.disposable = _parent._source6.subscribe(observer6)
subscription7.disposable = _parent._source7.subscribe(observer7)
return Disposables.create([
subscription1,
subscription2,
subscription3,
subscription4,
subscription5,
subscription6,
subscription7
])
}
override func getResult() throws -> R {
return try _parent._resultSelector(_latestElement1, _latestElement2, _latestElement3, _latestElement4, _latestElement5, _latestElement6, _latestElement7)
}
}
class CombineLatest7<E1, E2, E3, E4, E5, E6, E7, R> : Producer<R> {
typealias ResultSelector = (E1, E2, E3, E4, E5, E6, E7) throws -> R
let _source1: Observable<E1>
let _source2: Observable<E2>
let _source3: Observable<E3>
let _source4: Observable<E4>
let _source5: Observable<E5>
let _source6: Observable<E6>
let _source7: Observable<E7>
let _resultSelector: ResultSelector
init(source1: Observable<E1>, source2: Observable<E2>, source3: Observable<E3>, source4: Observable<E4>, source5: Observable<E5>, source6: Observable<E6>, source7: Observable<E7>, resultSelector: @escaping ResultSelector) {
_source1 = source1
_source2 = source2
_source3 = source3
_source4 = source4
_source5 = source5
_source6 = source6
_source7 = source7
_resultSelector = resultSelector
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == R {
let sink = CombineLatestSink7_(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}
// 8
extension Observable {
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element.
- seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html)
- parameter resultSelector: Function to invoke whenever any of the sources produces an element.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
// @warn_unused_result(message:"http://git.io/rxs.uo")
public static func combineLatest<O1: ObservableType, O2: ObservableType, O3: ObservableType, O4: ObservableType, O5: ObservableType, O6: ObservableType, O7: ObservableType, O8: ObservableType>
(_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, _ source8: O8, resultSelector: @escaping (O1.E, O2.E, O3.E, O4.E, O5.E, O6.E, O7.E, O8.E) throws -> E)
-> Observable<E> {
return CombineLatest8(
source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), source6: source6.asObservable(), source7: source7.asObservable(), source8: source8.asObservable(),
resultSelector: resultSelector
)
}
}
class CombineLatestSink8_<E1, E2, E3, E4, E5, E6, E7, E8, O: ObserverType> : CombineLatestSink<O> {
typealias R = O.E
typealias Parent = CombineLatest8<E1, E2, E3, E4, E5, E6, E7, E8, R>
let _parent: Parent
var _latestElement1: E1! = nil
var _latestElement2: E2! = nil
var _latestElement3: E3! = nil
var _latestElement4: E4! = nil
var _latestElement5: E5! = nil
var _latestElement6: E6! = nil
var _latestElement7: E7! = nil
var _latestElement8: E8! = nil
init(parent: Parent, observer: O) {
_parent = parent
super.init(arity: 8, observer: observer)
}
func run() -> Disposable {
let subscription1 = SingleAssignmentDisposable()
let subscription2 = SingleAssignmentDisposable()
let subscription3 = SingleAssignmentDisposable()
let subscription4 = SingleAssignmentDisposable()
let subscription5 = SingleAssignmentDisposable()
let subscription6 = SingleAssignmentDisposable()
let subscription7 = SingleAssignmentDisposable()
let subscription8 = SingleAssignmentDisposable()
let observer1 = CombineLatestObserver(lock: _lock, parent: self, index: 0, setLatestValue: { (e: E1) -> Void in self._latestElement1 = e }, this: subscription1)
let observer2 = CombineLatestObserver(lock: _lock, parent: self, index: 1, setLatestValue: { (e: E2) -> Void in self._latestElement2 = e }, this: subscription2)
let observer3 = CombineLatestObserver(lock: _lock, parent: self, index: 2, setLatestValue: { (e: E3) -> Void in self._latestElement3 = e }, this: subscription3)
let observer4 = CombineLatestObserver(lock: _lock, parent: self, index: 3, setLatestValue: { (e: E4) -> Void in self._latestElement4 = e }, this: subscription4)
let observer5 = CombineLatestObserver(lock: _lock, parent: self, index: 4, setLatestValue: { (e: E5) -> Void in self._latestElement5 = e }, this: subscription5)
let observer6 = CombineLatestObserver(lock: _lock, parent: self, index: 5, setLatestValue: { (e: E6) -> Void in self._latestElement6 = e }, this: subscription6)
let observer7 = CombineLatestObserver(lock: _lock, parent: self, index: 6, setLatestValue: { (e: E7) -> Void in self._latestElement7 = e }, this: subscription7)
let observer8 = CombineLatestObserver(lock: _lock, parent: self, index: 7, setLatestValue: { (e: E8) -> Void in self._latestElement8 = e }, this: subscription8)
subscription1.disposable = _parent._source1.subscribe(observer1)
subscription2.disposable = _parent._source2.subscribe(observer2)
subscription3.disposable = _parent._source3.subscribe(observer3)
subscription4.disposable = _parent._source4.subscribe(observer4)
subscription5.disposable = _parent._source5.subscribe(observer5)
subscription6.disposable = _parent._source6.subscribe(observer6)
subscription7.disposable = _parent._source7.subscribe(observer7)
subscription8.disposable = _parent._source8.subscribe(observer8)
return Disposables.create([
subscription1,
subscription2,
subscription3,
subscription4,
subscription5,
subscription6,
subscription7,
subscription8
])
}
override func getResult() throws -> R {
return try _parent._resultSelector(_latestElement1, _latestElement2, _latestElement3, _latestElement4, _latestElement5, _latestElement6, _latestElement7, _latestElement8)
}
}
class CombineLatest8<E1, E2, E3, E4, E5, E6, E7, E8, R> : Producer<R> {
typealias ResultSelector = (E1, E2, E3, E4, E5, E6, E7, E8) throws -> R
let _source1: Observable<E1>
let _source2: Observable<E2>
let _source3: Observable<E3>
let _source4: Observable<E4>
let _source5: Observable<E5>
let _source6: Observable<E6>
let _source7: Observable<E7>
let _source8: Observable<E8>
let _resultSelector: ResultSelector
init(source1: Observable<E1>, source2: Observable<E2>, source3: Observable<E3>, source4: Observable<E4>, source5: Observable<E5>, source6: Observable<E6>, source7: Observable<E7>, source8: Observable<E8>, resultSelector: @escaping ResultSelector) {
_source1 = source1
_source2 = source2
_source3 = source3
_source4 = source4
_source5 = source5
_source6 = source6
_source7 = source7
_source8 = source8
_resultSelector = resultSelector
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == R {
let sink = CombineLatestSink8_(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}

View File

@@ -0,0 +1,134 @@
//
// CombineLatest.swift
// Rx
//
// Created by Krunoslav Zaher on 3/21/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
protocol CombineLatestProtocol : class {
func next(_ index: Int)
func fail(_ error: Swift.Error)
func done(_ index: Int)
}
class CombineLatestSink<O: ObserverType>
: Sink<O>
, CombineLatestProtocol {
typealias Element = O.E
let _lock = NSRecursiveLock()
private let _arity: Int
private var _numberOfValues = 0
private var _numberOfDone = 0
private var _hasValue: [Bool]
private var _isDone: [Bool]
init(arity: Int, observer: O) {
_arity = arity
_hasValue = [Bool](repeating: false, count: arity)
_isDone = [Bool](repeating: false, count: arity)
super.init(observer: observer)
}
func getResult() throws -> Element {
abstractMethod()
}
func next(_ index: Int) {
if !_hasValue[index] {
_hasValue[index] = true
_numberOfValues += 1
}
if _numberOfValues == _arity {
do {
let result = try getResult()
forwardOn(.next(result))
}
catch let e {
forwardOn(.error(e))
dispose()
}
}
else {
var allOthersDone = true
for i in 0 ..< _arity {
if i != index && !_isDone[i] {
allOthersDone = false
break
}
}
if allOthersDone {
forwardOn(.completed)
dispose()
}
}
}
func fail(_ error: Swift.Error) {
forwardOn(.error(error))
dispose()
}
func done(_ index: Int) {
if _isDone[index] {
return
}
_isDone[index] = true
_numberOfDone += 1
if _numberOfDone == _arity {
forwardOn(.completed)
dispose()
}
}
}
class CombineLatestObserver<ElementType>
: ObserverType
, LockOwnerType
, SynchronizedOnType {
typealias Element = ElementType
typealias ValueSetter = (Element) -> Void
private let _parent: CombineLatestProtocol
let _lock: NSRecursiveLock
private let _index: Int
private let _this: Disposable
private let _setLatestValue: ValueSetter
init(lock: NSRecursiveLock, parent: CombineLatestProtocol, index: Int, setLatestValue: @escaping ValueSetter, this: Disposable) {
_lock = lock
_parent = parent
_index = index
_this = this
_setLatestValue = setLatestValue
}
func on(_ event: Event<Element>) {
synchronizedOn(event)
}
func _synchronized_on(_ event: Event<Element>) {
switch event {
case .next(let value):
_setLatestValue(value)
_parent.next(_index)
case .error(let error):
_this.dispose()
_parent.fail(error)
case .completed:
_this.dispose()
_parent.done(_index)
}
}
}

View File

@@ -0,0 +1,63 @@
//
// Concat.swift
// Rx
//
// Created by Krunoslav Zaher on 3/21/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class ConcatSink<S: Sequence, O: ObserverType>
: TailRecursiveSink<S, O>
, ObserverType where S.Iterator.Element : ObservableConvertibleType, S.Iterator.Element.E == O.E {
typealias Element = O.E
override init(observer: O) {
super.init(observer: observer)
}
func on(_ event: Event<Element>){
switch event {
case .next:
forwardOn(event)
case .error:
forwardOn(event)
dispose()
case .completed:
schedule(.moveNext)
}
}
override func subscribeToNext(_ source: Observable<E>) -> Disposable {
return source.subscribe(self)
}
override func extract(_ observable: Observable<E>) -> SequenceGenerator? {
if let source = observable as? Concat<S> {
return (source._sources.makeIterator(), source._count)
}
else {
return nil
}
}
}
class Concat<S: Sequence> : Producer<S.Iterator.Element.E> where S.Iterator.Element : ObservableConvertibleType {
typealias Element = S.Iterator.Element.E
fileprivate let _sources: S
fileprivate let _count: IntMax?
init(sources: S, count: IntMax?) {
_sources = sources
_count = count
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == Element {
let sink = ConcatSink<S, O>(observer: observer)
sink.disposable = sink.run((_sources.makeIterator(), _count))
return sink
}
}

View File

@@ -0,0 +1,96 @@
//
// ConnectableObservable.swift
// Rx
//
// Created by Krunoslav Zaher on 3/1/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Represents an observable wrapper that can be connected and disconnected from its underlying observable sequence.
*/
public class ConnectableObservable<Element>
: Observable<Element>
, ConnectableObservableType {
/**
Connects the observable wrapper to its source. All subscribed observers will receive values from the underlying observable sequence as long as the connection is established.
- returns: Disposable used to disconnect the observable wrapper from its source, causing subscribed observer to stop receiving values from the underlying observable sequence.
*/
public func connect() -> Disposable {
abstractMethod()
}
}
class Connection<S: SubjectType> : Disposable {
private var _lock: NSRecursiveLock
// state
private var _parent: ConnectableObservableAdapter<S>?
private var _subscription : Disposable?
init(parent: ConnectableObservableAdapter<S>, lock: NSRecursiveLock, subscription: Disposable) {
_parent = parent
_subscription = subscription
_lock = lock
}
func dispose() {
_lock.lock(); defer { _lock.unlock() } // {
guard let parent = _parent else {
return
}
guard let oldSubscription = _subscription else {
return
}
_subscription = nil
if parent._connection === self {
parent._connection = nil
}
_parent = nil
oldSubscription.dispose()
// }
}
}
class ConnectableObservableAdapter<S: SubjectType>
: ConnectableObservable<S.E> {
typealias ConnectionType = Connection<S>
fileprivate let _subject: S
fileprivate let _source: Observable<S.SubjectObserverType.E>
fileprivate let _lock = NSRecursiveLock()
// state
fileprivate var _connection: ConnectionType?
init(source: Observable<S.SubjectObserverType.E>, subject: S) {
_source = source
_subject = subject
_connection = nil
}
override func connect() -> Disposable {
return _lock.calculateLocked {
if let connection = _connection {
return connection
}
let disposable = _source.subscribe(_subject.asObserver())
let connection = Connection(parent: self, lock: _lock, subscription: disposable)
_connection = connection
return connection
}
}
override func subscribe<O : ObserverType>(_ observer: O) -> Disposable where O.E == S.E {
return _subject.subscribe(observer)
}
}

View File

@@ -0,0 +1,81 @@
//
// Debug.swift
// RxSwift
//
// Created by Krunoslav Zaher on 5/2/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
let dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
func logEvent(_ identifier: String, dateFormat: DateFormatter, content: String) {
print("\(dateFormat.string(from: Date())): \(identifier) -> \(content)")
}
class DebugSink<Source: ObservableType, O: ObserverType> : Sink<O>, ObserverType where O.E == Source.E {
typealias Element = O.E
typealias Parent = Debug<Source>
private let _parent: Parent
private let _timestampFormatter = DateFormatter()
init(parent: Parent, observer: O) {
_parent = parent
_timestampFormatter.dateFormat = dateFormat
logEvent(_parent._identifier, dateFormat: _timestampFormatter, content: "subscribed")
super.init(observer: observer)
}
func on(_ event: Event<Element>) {
let maxEventTextLength = 40
let eventText = "\(event)"
let eventNormalized = eventText.characters.count > maxEventTextLength
? String(eventText.characters.prefix(maxEventTextLength / 2)) + "..." + String(eventText.characters.suffix(maxEventTextLength / 2))
: eventText
logEvent(_parent._identifier, dateFormat: _timestampFormatter, content: "Event \(eventNormalized)")
forwardOn(event)
if event.isStopEvent {
dispose()
}
}
override func dispose() {
logEvent(_parent._identifier, dateFormat: _timestampFormatter, content: "isDisposed")
super.dispose()
}
}
class Debug<Source: ObservableType> : Producer<Source.E> {
fileprivate let _identifier: String
fileprivate let _source: Source
init(source: Source, identifier: String?, file: String, line: UInt, function: String) {
if let identifier = identifier {
_identifier = identifier
}
else {
let trimmedFile: String
if let lastIndex = file.lastIndexOf("/") {
trimmedFile = file[file.index(after: lastIndex) ..< file.endIndex]
}
else {
trimmedFile = file
}
_identifier = "\(trimmedFile):\(line) (\(function))"
}
_source = source
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == Source.E {
let sink = DebugSink(parent: self, observer: observer)
sink.disposable = _source.subscribe(sink)
return sink
}
}

View File

@@ -0,0 +1,104 @@
//
// Debunce.swift
// Rx
//
// Created by Krunoslav Zaher on 9/11/16.
// Copyright © 2016 Krunoslav Zaher. All rights reserved.
//
import Foundation
class DebounceSink<O: ObserverType>
: Sink<O>
, ObserverType
, LockOwnerType
, SynchronizedOnType {
typealias Element = O.E
typealias ParentType = Debounce<Element>
private let _parent: ParentType
let _lock = NSRecursiveLock()
// state
private var _id = 0 as UInt64
private var _value: Element? = nil
let cancellable = SerialDisposable()
init(parent: ParentType, observer: O) {
_parent = parent
super.init(observer: observer)
}
func run() -> Disposable {
let subscription = _parent._source.subscribe(self)
return Disposables.create(subscription, cancellable)
}
func on(_ event: Event<Element>) {
synchronizedOn(event)
}
func _synchronized_on(_ event: Event<Element>) {
switch event {
case .next(let element):
_id = _id &+ 1
let currentId = _id
_value = element
let scheduler = _parent._scheduler
let dueTime = _parent._dueTime
let d = SingleAssignmentDisposable()
self.cancellable.disposable = d
d.disposable = scheduler.scheduleRelative(currentId, dueTime: dueTime, action: self.propagate)
case .error:
_value = nil
forwardOn(event)
dispose()
case .completed:
if let value = _value {
_value = nil
forwardOn(.next(value))
}
forwardOn(.completed)
dispose()
}
}
func propagate(_ currentId: UInt64) -> Disposable {
_lock.lock(); defer { _lock.unlock() } // {
let originalValue = _value
if let value = originalValue, _id == currentId {
_value = nil
forwardOn(.next(value))
}
// }
return Disposables.create()
}
}
class Debounce<Element> : Producer<Element> {
fileprivate let _source: Observable<Element>
fileprivate let _dueTime: RxTimeInterval
fileprivate let _scheduler: SchedulerType
init(source: Observable<Element>, dueTime: RxTimeInterval, scheduler: SchedulerType) {
_source = source
_dueTime = dueTime
_scheduler = scheduler
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == Element {
let sink = DebounceSink(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}

View File

@@ -0,0 +1,61 @@
//
// Deferred.swift
// RxSwift
//
// Created by Krunoslav Zaher on 4/19/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class DeferredSink<S: ObservableType, O: ObserverType> : Sink<O>, ObserverType where S.E == O.E {
typealias E = O.E
private let _observableFactory: () throws -> S
init(observableFactory: @escaping () throws -> S, observer: O) {
_observableFactory = observableFactory
super.init(observer: observer)
}
func run() -> Disposable {
do {
let result = try _observableFactory()
return result.subscribe(self)
}
catch let e {
forwardOn(.error(e))
dispose()
return Disposables.create()
}
}
func on(_ event: Event<E>) {
forwardOn(event)
switch event {
case .next:
break
case .error:
dispose()
case .completed:
dispose()
}
}
}
class Deferred<S: ObservableType> : Producer<S.E> {
typealias Factory = () throws -> S
private let _observableFactory : Factory
init(observableFactory: @escaping Factory) {
_observableFactory = observableFactory
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == S.E {
let sink = DeferredSink(observableFactory: _observableFactory, observer: observer)
sink.disposable = sink.run()
return sink
}
}

View File

@@ -0,0 +1,164 @@
//
// Delay.swift
// RxSwift
//
// Created by tarunon on 2016/02/09.
// Copyright © 2016 Krunoslav Zaher. All rights reserved.
//
import Foundation
class DelaySink<ElementType, O: ObserverType>
: Sink<O>
, ObserverType where O.E == ElementType {
typealias E = O.E
typealias Source = Observable<E>
typealias DisposeKey = Bag<Disposable>.KeyType
private let _lock = NSRecursiveLock()
private let _dueTime: RxTimeInterval
private let _scheduler: SchedulerType
private let _sourceSubscription = SingleAssignmentDisposable()
private let _cancelable = SerialDisposable()
// is scheduled some action
private var _active = false
// is "run loop" on different scheduler running
private var _running = false
private var _errorEvent: Event<E>? = nil
// state
private var _queue = Queue<(eventTime: RxTime, event: Event<E>)>(capacity: 0)
private var _disposed = false
init(observer: O, dueTime: RxTimeInterval, scheduler: SchedulerType) {
_dueTime = dueTime
_scheduler = scheduler
super.init(observer: observer)
}
// All of these complications in this method are caused by the fact that
// error should be propagated immediatelly. Error can bepotentially received on different
// scheduler so this process needs to be synchronized somehow.
//
// Another complication is that scheduler is potentially concurrent so internal queue is used.
func drainQueue(state: (), scheduler: AnyRecursiveScheduler<()>) {
_lock.lock() // {
let hasFailed = _errorEvent != nil
if !hasFailed {
_running = true
}
_lock.unlock() // }
if hasFailed {
return
}
var ranAtLeastOnce = false
while true {
_lock.lock() // {
let errorEvent = _errorEvent
let eventToForwardImmediatelly = ranAtLeastOnce ? nil : _queue.dequeue()?.event
let nextEventToScheduleOriginalTime: Date? = ranAtLeastOnce && !_queue.isEmpty ? _queue.peek().eventTime : nil
if let _ = errorEvent {
}
else {
if let _ = eventToForwardImmediatelly {
}
else if let _ = nextEventToScheduleOriginalTime {
_running = false
}
else {
_running = false
_active = false
}
}
_lock.unlock() // {
if let errorEvent = errorEvent {
self.forwardOn(errorEvent)
self.dispose()
return
}
else {
if let eventToForwardImmediatelly = eventToForwardImmediatelly {
ranAtLeastOnce = true
self.forwardOn(eventToForwardImmediatelly)
if case .completed = eventToForwardImmediatelly {
self.dispose()
return
}
}
else if let nextEventToScheduleOriginalTime = nextEventToScheduleOriginalTime {
let elapsedTime = _scheduler.now.timeIntervalSince(nextEventToScheduleOriginalTime)
let interval = _dueTime - elapsedTime
let normalizedInterval = interval < 0.0 ? 0.0 : interval
scheduler.schedule((), dueTime: normalizedInterval)
return
}
else {
return
}
}
}
}
func on(_ event: Event<E>) {
if event.isStopEvent {
_sourceSubscription.dispose()
}
switch event {
case .error(_):
_lock.lock() // {
let shouldSendImmediatelly = !_running
_queue = Queue(capacity: 0)
_errorEvent = event
_lock.unlock() // }
if shouldSendImmediatelly {
forwardOn(event)
dispose()
}
default:
_lock.lock() // {
let shouldSchedule = !_active
_active = true
_queue.enqueue((_scheduler.now, event))
_lock.unlock() // }
if shouldSchedule {
_cancelable.disposable = _scheduler.scheduleRecursive((), dueTime: _dueTime, action: self.drainQueue)
}
}
}
func run(source: Source) -> Disposable {
_sourceSubscription.disposable = source.subscribeSafe(self)
return Disposables.create(_sourceSubscription, _cancelable)
}
}
class Delay<Element>: Producer<Element> {
private let _source: Observable<Element>
private let _dueTime: RxTimeInterval
private let _scheduler: SchedulerType
init(source: Observable<Element>, dueTime: RxTimeInterval, scheduler: SchedulerType) {
_source = source
_dueTime = dueTime
_scheduler = scheduler
}
override func run<O : ObserverType>(_ observer: O) -> Disposable where O.E == Element {
let sink = DelaySink(observer: observer, dueTime: _dueTime, scheduler: _scheduler)
sink.disposable = sink.run(source: _source)
return sink
}
}

View File

@@ -0,0 +1,52 @@
//
// DelaySubscription.swift
// RxSwift
//
// Created by Krunoslav Zaher on 6/14/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class DelaySubscriptionSink<ElementType, O: ObserverType>
: Sink<O>
, ObserverType where O.E == ElementType {
typealias Parent = DelaySubscription<ElementType>
typealias E = O.E
private let _parent: Parent
init(parent: Parent, observer: O) {
_parent = parent
super.init(observer: observer)
}
func on(_ event: Event<E>) {
forwardOn(event)
if event.isStopEvent {
dispose()
}
}
}
class DelaySubscription<Element>: Producer<Element> {
private let _source: Observable<Element>
private let _dueTime: RxTimeInterval
private let _scheduler: SchedulerType
init(source: Observable<Element>, dueTime: RxTimeInterval, scheduler: SchedulerType) {
_source = source
_dueTime = dueTime
_scheduler = scheduler
}
override func run<O : ObserverType>(_ observer: O) -> Disposable where O.E == Element {
let sink = DelaySubscriptionSink(parent: self, observer: observer)
sink.disposable = _scheduler.scheduleRelative((), dueTime: _dueTime) { _ in
return self._source.subscribe(sink)
}
return sink
}
}

View File

@@ -0,0 +1,70 @@
//
// DistinctUntilChanged.swift
// Rx
//
// Created by Krunoslav Zaher on 3/15/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class DistinctUntilChangedSink<O: ObserverType, Key>: Sink<O>, ObserverType {
typealias E = O.E
private let _parent: DistinctUntilChanged<E, Key>
private var _currentKey: Key? = nil
init(parent: DistinctUntilChanged<E, Key>, observer: O) {
_parent = parent
super.init(observer: observer)
}
func on(_ event: Event<E>) {
switch event {
case .next(let value):
do {
let key = try _parent._selector(value)
var areEqual = false
if let currentKey = _currentKey {
areEqual = try _parent._comparer(currentKey, key)
}
if areEqual {
return
}
_currentKey = key
forwardOn(event)
}
catch let error {
forwardOn(.error(error))
dispose()
}
case .error, .completed:
forwardOn(event)
dispose()
}
}
}
class DistinctUntilChanged<Element, Key>: Producer<Element> {
typealias KeySelector = (Element) throws -> Key
typealias EqualityComparer = (Key, Key) throws -> Bool
fileprivate let _source: Observable<Element>
fileprivate let _selector: KeySelector
fileprivate let _comparer: EqualityComparer
init(source: Observable<Element>, selector: @escaping KeySelector, comparer: @escaping EqualityComparer) {
_source = source
_selector = selector
_comparer = comparer
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == Element {
let sink = DistinctUntilChangedSink(parent: self, observer: observer)
sink.disposable = _source.subscribe(sink)
return sink
}
}

View File

@@ -0,0 +1,63 @@
//
// Do.swift
// Rx
//
// Created by Krunoslav Zaher on 2/21/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class DoSink<O: ObserverType> : Sink<O>, ObserverType {
typealias Element = O.E
typealias Parent = Do<Element>
private let _parent: Parent
init(parent: Parent, observer: O) {
_parent = parent
super.init(observer: observer)
}
func on(_ event: Event<Element>) {
do {
try _parent._eventHandler(event)
forwardOn(event)
if event.isStopEvent {
dispose()
}
}
catch let error {
forwardOn(.error(error))
dispose()
}
}
}
class Do<Element> : Producer<Element> {
typealias EventHandler = (Event<Element>) throws -> Void
fileprivate let _source: Observable<Element>
fileprivate let _eventHandler: EventHandler
fileprivate let _onSubscribe: (() -> ())?
fileprivate let _onDispose: (() -> ())?
init(source: Observable<Element>, eventHandler: @escaping EventHandler, onSubscribe: (() -> ())?, onDispose: (() -> ())?) {
_source = source
_eventHandler = eventHandler
_onSubscribe = onSubscribe
_onDispose = onDispose
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == Element {
_onSubscribe?()
let sink = DoSink(parent: self, observer: observer)
let subscription = _source.subscribe(sink)
let onDispose = _onDispose
sink.disposable = Disposables.create {
subscription.dispose()
onDispose?()
}
return sink
}
}

View File

@@ -0,0 +1,79 @@
//
// ElementAt.swift
// Rx
//
// Created by Junior B. on 21/10/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class ElementAtSink<SourceType, O: ObserverType> : Sink<O>, ObserverType where O.E == SourceType {
typealias Parent = ElementAt<SourceType>
let _parent: Parent
var _i: Int
init(parent: Parent, observer: O) {
_parent = parent
_i = parent._index
super.init(observer: observer)
}
func on(_ event: Event<SourceType>) {
switch event {
case .next(_):
if (_i == 0) {
forwardOn(event)
forwardOn(.completed)
self.dispose()
}
do {
let _ = try decrementChecked(&_i)
} catch(let e) {
forwardOn(.error(e))
dispose()
return
}
case .error(let e):
forwardOn(.error(e))
self.dispose()
case .completed:
if (_parent._throwOnEmpty) {
forwardOn(.error(RxError.argumentOutOfRange))
} else {
forwardOn(.completed)
}
self.dispose()
}
}
}
class ElementAt<SourceType> : Producer<SourceType> {
let _source: Observable<SourceType>
let _throwOnEmpty: Bool
let _index: Int
init(source: Observable<SourceType>, index: Int, throwOnEmpty: Bool) {
if index < 0 {
rxFatalError("index can't be negative")
}
self._source = source
self._index = index
self._throwOnEmpty = throwOnEmpty
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == SourceType {
let sink = ElementAtSink(parent: self, observer: observer)
sink.disposable = _source.subscribeSafe(sink)
return sink
}
}

View File

@@ -0,0 +1,16 @@
//
// Empty.swift
// Rx
//
// Created by Krunoslav Zaher on 8/30/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class Empty<Element> : Producer<Element> {
override func subscribe<O : ObserverType>(_ observer: O) -> Disposable where O.E == Element {
observer.on(.completed)
return Disposables.create()
}
}

View File

@@ -0,0 +1,22 @@
//
// Error.swift
// Rx
//
// Created by Krunoslav Zaher on 8/30/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class Error<Element> : Producer<Element> {
private let _error: Swift.Error
init(error: Swift.Error) {
_error = error
}
override func subscribe<O : ObserverType>(_ observer: O) -> Disposable where O.E == Element {
observer.on(.error(_error))
return Disposables.create()
}
}

View File

@@ -0,0 +1,58 @@
//
// Filter.swift
// Rx
//
// Created by Krunoslav Zaher on 2/17/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class FilterSink<O : ObserverType>: Sink<O>, ObserverType {
typealias Predicate = (Element) throws -> Bool
typealias Element = O.E
private let _predicate: Predicate
init(predicate: @escaping Predicate, observer: O) {
_predicate = predicate
super.init(observer: observer)
}
func on(_ event: Event<Element>) {
switch event {
case .next(let value):
do {
let satisfies = try _predicate(value)
if satisfies {
forwardOn(.next(value))
}
}
catch let e {
forwardOn(.error(e))
dispose()
}
case .completed, .error:
forwardOn(event)
dispose()
}
}
}
class Filter<Element> : Producer<Element> {
typealias Predicate = (Element) throws -> Bool
private let _source: Observable<Element>
private let _predicate: Predicate
init(source: Observable<Element>, predicate: @escaping Predicate) {
_source = source
_predicate = predicate
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == Element {
let sink = FilterSink(predicate: _predicate, observer: observer)
sink.disposable = _source.subscribe(sink)
return sink
}
}

View File

@@ -0,0 +1,71 @@
//
// Generate.swift
// Rx
//
// Created by Krunoslav Zaher on 9/2/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class GenerateSink<S, O: ObserverType> : Sink<O> {
typealias Parent = Generate<S, O.E>
private let _parent: Parent
private var _state: S
init(parent: Parent, observer: O) {
_parent = parent
_state = parent._initialState
super.init(observer: observer)
}
func run() -> Disposable {
return _parent._scheduler.scheduleRecursive(true) { (isFirst, recurse) -> Void in
do {
if !isFirst {
self._state = try self._parent._iterate(self._state)
}
if try self._parent._condition(self._state) {
let result = try self._parent._resultSelector(self._state)
self.forwardOn(.next(result))
recurse(false)
}
else {
self.forwardOn(.completed)
self.dispose()
}
}
catch let error {
self.forwardOn(.error(error))
self.dispose()
}
}
}
}
class Generate<S, E> : Producer<E> {
fileprivate let _initialState: S
fileprivate let _condition: (S) throws -> Bool
fileprivate let _iterate: (S) throws -> S
fileprivate let _resultSelector: (S) throws -> E
fileprivate let _scheduler: ImmediateSchedulerType
init(initialState: S, condition: @escaping (S) throws -> Bool, iterate: @escaping (S) throws -> S, resultSelector: @escaping (S) throws -> E, scheduler: ImmediateSchedulerType) {
_initialState = initialState
_condition = condition
_iterate = iterate
_resultSelector = resultSelector
_scheduler = scheduler
super.init()
}
override func run<O : ObserverType>(_ observer: O) -> Disposable where O.E == E {
let sink = GenerateSink(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}

View File

@@ -0,0 +1,61 @@
//
// Just.swift
// Rx
//
// Created by Krunoslav Zaher on 8/30/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class JustScheduledSink<O: ObserverType> : Sink<O> {
typealias Parent = JustScheduled<O.E>
private let _parent: Parent
init(parent: Parent, observer: O) {
_parent = parent
super.init(observer: observer)
}
func run() -> Disposable {
let scheduler = _parent._scheduler
return scheduler.schedule(_parent._element) { element in
self.forwardOn(.next(element))
return scheduler.schedule(()) { _ in
self.forwardOn(.completed)
return Disposables.create()
}
}
}
}
class JustScheduled<Element> : Producer<Element> {
fileprivate let _scheduler: ImmediateSchedulerType
fileprivate let _element: Element
init(element: Element, scheduler: ImmediateSchedulerType) {
_scheduler = scheduler
_element = element
}
override func subscribe<O : ObserverType>(_ observer: O) -> Disposable where O.E == E {
let sink = JustScheduledSink(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}
class Just<Element> : Producer<Element> {
private let _element: Element
init(element: Element) {
_element = element
}
override func subscribe<O : ObserverType>(_ observer: O) -> Disposable where O.E == Element {
observer.on(.next(_element))
observer.on(.completed)
return Disposables.create()
}
}

View File

@@ -0,0 +1,140 @@
//
// Map.swift
// Rx
//
// Created by Krunoslav Zaher on 3/15/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class MapSink<SourceType, O : ObserverType> : Sink<O>, ObserverType {
typealias Selector = (SourceType) throws -> ResultType
typealias ResultType = O.E
typealias Element = SourceType
private let _selector: Selector
init(selector: @escaping Selector, observer: O) {
_selector = selector
super.init(observer: observer)
}
func on(_ event: Event<SourceType>) {
switch event {
case .next(let element):
do {
let mappedElement = try _selector(element)
forwardOn(.next(mappedElement))
}
catch let e {
forwardOn(.error(e))
dispose()
}
case .error(let error):
forwardOn(.error(error))
dispose()
case .completed:
forwardOn(.completed)
dispose()
}
}
}
class MapWithIndexSink<SourceType, O : ObserverType> : Sink<O>, ObserverType {
typealias Selector = (SourceType, Int) throws -> ResultType
typealias ResultType = O.E
typealias Element = SourceType
typealias Parent = MapWithIndex<SourceType, ResultType>
private let _selector: Selector
private var _index = 0
init(selector: @escaping Selector, observer: O) {
_selector = selector
super.init(observer: observer)
}
func on(_ event: Event<SourceType>) {
switch event {
case .next(let element):
do {
let mappedElement = try _selector(element, try incrementChecked(&_index))
forwardOn(.next(mappedElement))
}
catch let e {
forwardOn(.error(e))
dispose()
}
case .error(let error):
forwardOn(.error(error))
dispose()
case .completed:
forwardOn(.completed)
dispose()
}
}
}
class MapWithIndex<SourceType, ResultType> : Producer<ResultType> {
typealias Selector = (SourceType, Int) throws -> ResultType
private let _source: Observable<SourceType>
private let _selector: Selector
init(source: Observable<SourceType>, selector: @escaping Selector) {
_source = source
_selector = selector
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == ResultType {
let sink = MapWithIndexSink(selector: _selector, observer: observer)
sink.disposable = _source.subscribe(sink)
return sink
}
}
#if TRACE_RESOURCES
public var numberOfMapOperators: Int32 = 0
#endif
class Map<SourceType, ResultType>: Producer<ResultType> {
typealias Selector = (SourceType) throws -> ResultType
private let _source: Observable<SourceType>
private let _selector: Selector
init(source: Observable<SourceType>, selector: @escaping Selector) {
_source = source
_selector = selector
#if TRACE_RESOURCES
let _ = AtomicIncrement(&numberOfMapOperators)
#endif
}
override func composeMap<R>(_ selector: @escaping (ResultType) throws -> R) -> Observable<R> {
let originalSelector = _selector
return Map<SourceType, R>(source: _source, selector: { (s: SourceType) throws -> R in
let r: ResultType = try originalSelector(s)
return try selector(r)
})
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == ResultType {
let sink = MapSink(selector: _selector, observer: observer)
sink.disposable = _source.subscribe(sink)
return sink
}
#if TRACE_RESOURCES
deinit {
let _ = AtomicDecrement(&numberOfMapOperators)
}
#endif
}

View File

@@ -0,0 +1,424 @@
//
// Merge.swift
// Rx
//
// Created by Krunoslav Zaher on 3/28/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
// MARK: Limited concurrency version
class MergeLimitedSinkIter<S: ObservableConvertibleType, O: ObserverType>
: ObserverType
, LockOwnerType
, SynchronizedOnType where S.E == O.E {
typealias E = O.E
typealias DisposeKey = Bag<Disposable>.KeyType
typealias Parent = MergeLimitedSink<S, O>
private let _parent: Parent
private let _disposeKey: DisposeKey
var _lock: NSRecursiveLock {
return _parent._lock
}
init(parent: Parent, disposeKey: DisposeKey) {
_parent = parent
_disposeKey = disposeKey
}
func on(_ event: Event<E>) {
synchronizedOn(event)
}
func _synchronized_on(_ event: Event<E>) {
switch event {
case .next:
_parent.forwardOn(event)
case .error:
_parent.forwardOn(event)
_parent.dispose()
case .completed:
_parent._group.remove(for: _disposeKey)
if let next = _parent._queue.dequeue() {
_parent.subscribe(next, group: _parent._group)
}
else {
_parent._activeCount = _parent._activeCount - 1
if _parent._stopped && _parent._activeCount == 0 {
_parent.forwardOn(.completed)
_parent.dispose()
}
}
}
}
}
class MergeLimitedSink<S: ObservableConvertibleType, O: ObserverType>
: Sink<O>
, ObserverType
, LockOwnerType
, SynchronizedOnType where S.E == O.E {
typealias E = S
typealias QueueType = Queue<S>
fileprivate let _maxConcurrent: Int
let _lock = NSRecursiveLock()
// state
fileprivate var _stopped = false
fileprivate var _activeCount = 0
fileprivate var _queue = QueueType(capacity: 2)
fileprivate let _sourceSubscription = SingleAssignmentDisposable()
fileprivate let _group = CompositeDisposable()
init(maxConcurrent: Int, observer: O) {
_maxConcurrent = maxConcurrent
let _ = _group.insert(_sourceSubscription)
super.init(observer: observer)
}
func run(_ source: Observable<S>) -> Disposable {
let _ = _group.insert(_sourceSubscription)
let disposable = source.subscribe(self)
_sourceSubscription.disposable = disposable
return _group
}
func subscribe(_ innerSource: E, group: CompositeDisposable) {
let subscription = SingleAssignmentDisposable()
let key = group.insert(subscription)
if let key = key {
let observer = MergeLimitedSinkIter(parent: self, disposeKey: key)
let disposable = innerSource.asObservable().subscribe(observer)
subscription.disposable = disposable
}
}
func on(_ event: Event<E>) {
synchronizedOn(event)
}
func _synchronized_on(_ event: Event<E>) {
switch event {
case .next(let value):
let subscribe: Bool
if _activeCount < _maxConcurrent {
_activeCount += 1
subscribe = true
}
else {
_queue.enqueue(value)
subscribe = false
}
if subscribe {
self.subscribe(value, group: _group)
}
case .error(let error):
forwardOn(.error(error))
dispose()
case .completed:
if _activeCount == 0 {
forwardOn(.completed)
dispose()
}
else {
_sourceSubscription.dispose()
}
_stopped = true
}
}
}
class MergeLimited<S: ObservableConvertibleType> : Producer<S.E> {
private let _source: Observable<S>
private let _maxConcurrent: Int
init(source: Observable<S>, maxConcurrent: Int) {
_source = source
_maxConcurrent = maxConcurrent
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == S.E {
let sink = MergeLimitedSink<S, O>(maxConcurrent: _maxConcurrent, observer: observer)
sink.disposable = sink.run(_source)
return sink
}
}
// MARK: Merge
final class MergeBasicSink<S: ObservableConvertibleType, O: ObserverType> : MergeSink<S, S, O> where O.E == S.E {
override init(observer: O) {
super.init(observer: observer)
}
override func performMap(_ element: S) throws -> S {
return element
}
}
// MARK: flatMap
final class FlatMapSink<SourceType, S: ObservableConvertibleType, O: ObserverType> : MergeSink<SourceType, S, O> where O.E == S.E {
typealias Selector = (SourceType) throws -> S
private let _selector: Selector
init(selector: @escaping Selector, observer: O) {
_selector = selector
super.init(observer: observer)
}
override func performMap(_ element: SourceType) throws -> S {
return try _selector(element)
}
}
final class FlatMapWithIndexSink<SourceType, S: ObservableConvertibleType, O: ObserverType> : MergeSink<SourceType, S, O> where O.E == S.E {
typealias Selector = (SourceType, Int) throws -> S
private var _index = 0
private let _selector: Selector
init(selector: @escaping Selector, observer: O) {
_selector = selector
super.init(observer: observer)
}
override func performMap(_ element: SourceType) throws -> S {
return try _selector(element, try incrementChecked(&_index))
}
}
// MARK: FlatMapFirst
final class FlatMapFirstSink<SourceType, S: ObservableConvertibleType, O: ObserverType> : MergeSink<SourceType, S, O> where O.E == S.E {
typealias Selector = (SourceType) throws -> S
private let _selector: Selector
override var subscribeNext: Bool {
return _group.count == MergeNoIterators
}
init(selector: @escaping Selector, observer: O) {
_selector = selector
super.init(observer: observer)
}
override func performMap(_ element: SourceType) throws -> S {
return try _selector(element)
}
}
// It's value is one because initial source subscription is always in CompositeDisposable
private let MergeNoIterators = 1
class MergeSinkIter<SourceType, S: ObservableConvertibleType, O: ObserverType> : ObserverType where O.E == S.E {
typealias Parent = MergeSink<SourceType, S, O>
typealias DisposeKey = CompositeDisposable.DisposeKey
typealias E = O.E
private let _parent: Parent
private let _disposeKey: DisposeKey
init(parent: Parent, disposeKey: DisposeKey) {
_parent = parent
_disposeKey = disposeKey
}
func on(_ event: Event<E>) {
switch event {
case .next(let value):
_parent._lock.lock(); defer { _parent._lock.unlock() } // lock {
_parent.forwardOn(.next(value))
// }
case .error(let error):
_parent._lock.lock(); defer { _parent._lock.unlock() } // lock {
_parent.forwardOn(.error(error))
_parent.dispose()
// }
case .completed:
_parent._group.remove(for: _disposeKey)
// If this has returned true that means that `Completed` should be sent.
// In case there is a race who will sent first completed,
// lock will sort it out. When first Completed message is sent
// it will set observer to nil, and thus prevent further complete messages
// to be sent, and thus preserving the sequence grammar.
if _parent._stopped && _parent._group.count == MergeNoIterators {
_parent._lock.lock(); defer { _parent._lock.unlock() } // lock {
_parent.forwardOn(.completed)
_parent.dispose()
// }
}
}
}
}
class MergeSink<SourceType, S: ObservableConvertibleType, O: ObserverType>
: Sink<O>
, ObserverType where O.E == S.E {
typealias ResultType = O.E
typealias Element = SourceType
fileprivate let _lock = NSRecursiveLock()
fileprivate var subscribeNext: Bool {
return true
}
// state
fileprivate let _group = CompositeDisposable()
fileprivate let _sourceSubscription = SingleAssignmentDisposable()
fileprivate var _stopped = false
override init(observer: O) {
super.init(observer: observer)
}
func performMap(_ element: SourceType) throws -> S {
abstractMethod()
}
func on(_ event: Event<SourceType>) {
switch event {
case .next(let element):
if !subscribeNext {
return
}
do {
let value = try performMap(element)
subscribeInner(value.asObservable())
}
catch let e {
forwardOn(.error(e))
dispose()
}
case .error(let error):
_lock.lock(); defer { _lock.unlock() } // lock {
forwardOn(.error(error))
dispose()
// }
case .completed:
_lock.lock(); defer { _lock.unlock() } // lock {
_stopped = true
if _group.count == MergeNoIterators {
forwardOn(.completed)
dispose()
}
else {
_sourceSubscription.dispose()
}
//}
}
}
func subscribeInner(_ source: Observable<O.E>) {
let iterDisposable = SingleAssignmentDisposable()
if let disposeKey = _group.insert(iterDisposable) {
let iter = MergeSinkIter(parent: self, disposeKey: disposeKey)
let subscription = source.subscribe(iter)
iterDisposable.disposable = subscription
}
}
func run(_ source: Observable<SourceType>) -> Disposable {
let _ = _group.insert(_sourceSubscription)
let subscription = source.subscribe(self)
_sourceSubscription.disposable = subscription
return _group
}
}
// MARK: Producers
final class FlatMap<SourceType, S: ObservableConvertibleType>: Producer<S.E> {
typealias Selector = (SourceType) throws -> S
private let _source: Observable<SourceType>
private let _selector: Selector
init(source: Observable<SourceType>, selector: @escaping Selector) {
_source = source
_selector = selector
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == S.E {
let sink = FlatMapSink(selector: _selector, observer: observer)
sink.disposable = sink.run(_source)
return sink
}
}
final class FlatMapWithIndex<SourceType, S: ObservableConvertibleType>: Producer<S.E> {
typealias Selector = (SourceType, Int) throws -> S
private let _source: Observable<SourceType>
private let _selector: Selector
init(source: Observable<SourceType>, selector: @escaping Selector) {
_source = source
_selector = selector
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == S.E {
let sink = FlatMapWithIndexSink<SourceType, S, O>(selector: _selector, observer: observer)
sink.disposable = sink.run(_source)
return sink
}
}
final class FlatMapFirst<SourceType, S: ObservableConvertibleType>: Producer<S.E> {
typealias Selector = (SourceType) throws -> S
private let _source: Observable<SourceType>
private let _selector: Selector
init(source: Observable<SourceType>, selector: @escaping Selector) {
_source = source
_selector = selector
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == S.E {
let sink = FlatMapFirstSink<SourceType, S, O>(selector: _selector, observer: observer)
sink.disposable = sink.run(_source)
return sink
}
}
final class Merge<S: ObservableConvertibleType> : Producer<S.E> {
private let _source: Observable<S>
init(source: Observable<S>) {
_source = source
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == S.E {
let sink = MergeBasicSink<S, O>(observer: observer)
sink.disposable = sink.run(_source)
return sink
}
}

View File

@@ -0,0 +1,71 @@
//
// Multicast.swift
// Rx
//
// Created by Krunoslav Zaher on 2/27/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class MulticastSink<S: SubjectType, O: ObserverType>: Sink<O>, ObserverType {
typealias Element = O.E
typealias ResultType = Element
typealias MutlicastType = Multicast<S, O.E>
private let _parent: MutlicastType
init(parent: MutlicastType, observer: O) {
_parent = parent
super.init(observer: observer)
}
func run() -> Disposable {
do {
let subject = try _parent._subjectSelector()
let connectable = ConnectableObservableAdapter(source: _parent._source, subject: subject)
let observable = try _parent._selector(connectable)
let subscription = observable.subscribe(self)
let connection = connectable.connect()
return Disposables.create(subscription, connection)
}
catch let e {
forwardOn(.error(e))
dispose()
return Disposables.create()
}
}
func on(_ event: Event<ResultType>) {
forwardOn(event)
switch event {
case .next: break
case .error, .completed:
dispose()
}
}
}
class Multicast<S: SubjectType, R>: Producer<R> {
typealias SubjectSelectorType = () throws -> S
typealias SelectorType = (Observable<S.E>) throws -> Observable<R>
fileprivate let _source: Observable<S.SubjectObserverType.E>
fileprivate let _subjectSelector: SubjectSelectorType
fileprivate let _selector: SelectorType
init(source: Observable<S.SubjectObserverType.E>, subjectSelector: @escaping SubjectSelectorType, selector: @escaping SelectorType) {
_source = source
_subjectSelector = subjectSelector
_selector = selector
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == R {
let sink = MulticastSink(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}

View File

@@ -0,0 +1,15 @@
//
// Never.swift
// Rx
//
// Created by Krunoslav Zaher on 8/30/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class Never<Element> : Producer<Element> {
override func subscribe<O : ObserverType>(_ observer: O) -> Disposable where O.E == Element {
return Disposables.create()
}
}

View File

@@ -0,0 +1,133 @@
//
// ObserveOn.swift
// RxSwift
//
// Created by Krunoslav Zaher on 7/25/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class ObserveOn<E> : Producer<E> {
let scheduler: ImmediateSchedulerType
let source: Observable<E>
init(source: Observable<E>, scheduler: ImmediateSchedulerType) {
self.scheduler = scheduler
self.source = source
#if TRACE_RESOURCES
let _ = AtomicIncrement(&resourceCount)
#endif
}
override func run<O : ObserverType>(_ observer: O) -> Disposable where O.E == E {
let sink = ObserveOnSink(scheduler: scheduler, observer: observer)
sink._subscription.disposable = source.subscribe(sink)
return sink
}
#if TRACE_RESOURCES
deinit {
let _ = AtomicDecrement(&resourceCount)
}
#endif
}
enum ObserveOnState : Int32 {
// pump is not running
case stopped = 0
// pump is running
case running = 1
}
class ObserveOnSink<O: ObserverType> : ObserverBase<O.E> {
typealias E = O.E
let _scheduler: ImmediateSchedulerType
var _lock = SpinLock()
// state
var _state = ObserveOnState.stopped
var _observer: O?
var _queue = Queue<Event<E>>(capacity: 10)
let _scheduleDisposable = SerialDisposable()
let _subscription = SingleAssignmentDisposable()
init(scheduler: ImmediateSchedulerType, observer: O) {
_scheduler = scheduler
_observer = observer
}
override func onCore(_ event: Event<E>) {
let shouldStart = _lock.calculateLocked { () -> Bool in
self._queue.enqueue(event)
switch self._state {
case .stopped:
self._state = .running
return true
case .running:
return false
}
}
if shouldStart {
_scheduleDisposable.disposable = self._scheduler.scheduleRecursive((), action: self.run)
}
}
func run(_ state: Void, recurse: (Void) -> Void) {
let (nextEvent, observer) = self._lock.calculateLocked { () -> (Event<E>?, O?) in
if self._queue.count > 0 {
return (self._queue.dequeue(), self._observer)
}
else {
self._state = .stopped
return (nil, self._observer)
}
}
if let nextEvent = nextEvent {
observer?.on(nextEvent)
if nextEvent.isStopEvent {
dispose()
}
}
else {
return
}
let shouldContinue = _shouldContinue_synchronized()
if shouldContinue {
recurse()
}
}
func _shouldContinue_synchronized() -> Bool {
_lock.lock(); defer { _lock.unlock() } // {
if self._queue.count > 0 {
return true
}
else {
self._state = .stopped
return false
}
// }
}
override func dispose() {
super.dispose()
_subscription.dispose()
_scheduleDisposable.dispose()
_lock.lock(); defer { _lock.unlock() } // {
_observer = nil
// }
}
}

View File

@@ -0,0 +1,81 @@
//
// ObserveOnSerialDispatchQueue.swift
// RxSwift
//
// Created by Krunoslav Zaher on 5/31/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if TRACE_RESOURCES
/**
Counts number of `SerialDispatchQueueObservables`.
Purposed for unit tests.
*/
public var numberOfSerialDispatchQueueObservables: AtomicInt = 0
#endif
class ObserveOnSerialDispatchQueueSink<O: ObserverType> : ObserverBase<O.E> {
let scheduler: SerialDispatchQueueScheduler
let observer: O
let subscription = SingleAssignmentDisposable()
var cachedScheduleLambda: ((ObserveOnSerialDispatchQueueSink<O>, Event<E>) -> Disposable)!
init(scheduler: SerialDispatchQueueScheduler, observer: O) {
self.scheduler = scheduler
self.observer = observer
super.init()
cachedScheduleLambda = { sink, event in
sink.observer.on(event)
if event.isStopEvent {
sink.dispose()
}
return Disposables.create()
}
}
override func onCore(_ event: Event<E>) {
let _ = self.scheduler.schedule((self, event), action: cachedScheduleLambda)
}
override func dispose() {
super.dispose()
subscription.dispose()
}
}
class ObserveOnSerialDispatchQueue<E> : Producer<E> {
let scheduler: SerialDispatchQueueScheduler
let source: Observable<E>
init(source: Observable<E>, scheduler: SerialDispatchQueueScheduler) {
self.scheduler = scheduler
self.source = source
#if TRACE_RESOURCES
let _ = AtomicIncrement(&resourceCount)
let _ = AtomicIncrement(&numberOfSerialDispatchQueueObservables)
#endif
}
override func run<O : ObserverType>(_ observer: O) -> Disposable where O.E == E {
let sink = ObserveOnSerialDispatchQueueSink(scheduler: scheduler, observer: observer)
sink.subscription.disposable = source.subscribe(sink)
return sink
}
#if TRACE_RESOURCES
deinit {
let _ = AtomicDecrement(&resourceCount)
let _ = AtomicDecrement(&numberOfSerialDispatchQueueObservables)
}
#endif
}

View File

@@ -0,0 +1,30 @@
//
// Producer.swift
// Rx
//
// Created by Krunoslav Zaher on 2/20/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class Producer<Element> : Observable<Element> {
override init() {
super.init()
}
override func subscribe<O : ObserverType>(_ observer: O) -> Disposable where O.E == Element {
if !CurrentThreadScheduler.isScheduleRequired {
return run(observer)
}
else {
return CurrentThreadScheduler.instance.schedule(()) { _ in
return self.run(observer)
}
}
}
func run<O : ObserverType>(_ observer: O) -> Disposable where O.E == Element {
abstractMethod()
}
}

View File

@@ -0,0 +1,59 @@
//
// Range.swift
// Rx
//
// Created by Krunoslav Zaher on 9/13/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class RangeProducer<E: SignedInteger> : Producer<E> {
fileprivate let _start: E
fileprivate let _count: E
fileprivate let _scheduler: ImmediateSchedulerType
init(start: E, count: E, scheduler: ImmediateSchedulerType) {
if count < 0 {
rxFatalError("count can't be negative")
}
if start &+ (count - 1) < start {
rxFatalError("overflow of count")
}
_start = start
_count = count
_scheduler = scheduler
}
override func run<O : ObserverType>(_ observer: O) -> Disposable where O.E == E {
let sink = RangeSink(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}
class RangeSink<O: ObserverType> : Sink<O> where O.E: SignedInteger {
typealias Parent = RangeProducer<O.E>
private let _parent: Parent
init(parent: Parent, observer: O) {
_parent = parent
super.init(observer: observer)
}
func run() -> Disposable {
return _parent._scheduler.scheduleRecursive(0 as O.E) { i, recurse in
if i < self._parent._count {
self.forwardOn(.next(self._parent._start + i))
recurse(i + 1)
}
else {
self.forwardOn(.completed)
self.dispose()
}
}
}
}

View File

@@ -0,0 +1,74 @@
//
// Reduce.swift
// Rx
//
// Created by Krunoslav Zaher on 4/1/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class ReduceSink<SourceType, AccumulateType, O: ObserverType> : Sink<O>, ObserverType {
typealias ResultType = O.E
typealias Parent = Reduce<SourceType, AccumulateType, ResultType>
private let _parent: Parent
private var _accumulation: AccumulateType
init(parent: Parent, observer: O) {
_parent = parent
_accumulation = parent._seed
super.init(observer: observer)
}
func on(_ event: Event<SourceType>) {
switch event {
case .next(let value):
do {
_accumulation = try _parent._accumulator(_accumulation, value)
}
catch let e {
forwardOn(.error(e))
dispose()
}
case .error(let e):
forwardOn(.error(e))
dispose()
case .completed:
do {
let result = try _parent._mapResult(_accumulation)
forwardOn(.next(result))
forwardOn(.completed)
dispose()
}
catch let e {
forwardOn(.error(e))
dispose()
}
}
}
}
class Reduce<SourceType, AccumulateType, ResultType> : Producer<ResultType> {
typealias AccumulatorType = (AccumulateType, SourceType) throws -> AccumulateType
typealias ResultSelectorType = (AccumulateType) throws -> ResultType
fileprivate let _source: Observable<SourceType>
fileprivate let _seed: AccumulateType
fileprivate let _accumulator: AccumulatorType
fileprivate let _mapResult: ResultSelectorType
init(source: Observable<SourceType>, seed: AccumulateType, accumulator: @escaping AccumulatorType, mapResult: @escaping ResultSelectorType) {
_source = source
_seed = seed
_accumulator = accumulator
_mapResult = mapResult
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == ResultType {
let sink = ReduceSink(parent: self, observer: observer)
sink.disposable = _source.subscribe(sink)
return sink
}
}

View File

@@ -0,0 +1,84 @@
//
// RefCount.swift
// Rx
//
// Created by Krunoslav Zaher on 3/5/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class RefCountSink<CO: ConnectableObservableType, O: ObserverType>
: Sink<O>
, ObserverType where CO.E == O.E {
typealias Element = O.E
typealias Parent = RefCount<CO>
private let _parent: Parent
init(parent: Parent, observer: O) {
_parent = parent
super.init(observer: observer)
}
func run() -> Disposable {
let subscription = _parent._source.subscribeSafe(self)
_parent._lock.lock(); defer { _parent._lock.unlock() } // {
if _parent._count == 0 {
_parent._count = 1
_parent._connectableSubscription = _parent._source.connect()
}
else {
_parent._count = _parent._count + 1
}
// }
return Disposables.create {
subscription.dispose()
self._parent._lock.lock(); defer { self._parent._lock.unlock() } // {
if self._parent._count == 1 {
self._parent._connectableSubscription!.dispose()
self._parent._count = 0
self._parent._connectableSubscription = nil
}
else if self._parent._count > 1 {
self._parent._count = self._parent._count - 1
}
else {
rxFatalError("Something went wrong with RefCount disposing mechanism")
}
// }
}
}
func on(_ event: Event<Element>) {
switch event {
case .next:
forwardOn(event)
case .error, .completed:
forwardOn(event)
dispose()
}
}
}
class RefCount<CO: ConnectableObservableType>: Producer<CO.E> {
fileprivate let _lock = NSRecursiveLock()
// state
fileprivate var _count = 0
fileprivate var _connectableSubscription = nil as Disposable?
fileprivate let _source: CO
init(source: CO) {
_source = source
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == CO.E {
let sink = RefCountSink(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}

View File

@@ -0,0 +1,44 @@
//
// Repeat.swift
// RxExample
//
// Created by Krunoslav Zaher on 9/13/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class RepeatElement<Element> : Producer<Element> {
fileprivate let _element: Element
fileprivate let _scheduler: ImmediateSchedulerType
init(element: Element, scheduler: ImmediateSchedulerType) {
_element = element
_scheduler = scheduler
}
override func run<O : ObserverType>(_ observer: O) -> Disposable where O.E == Element {
let sink = RepeatElementSink(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}
class RepeatElementSink<O: ObserverType> : Sink<O> {
typealias Parent = RepeatElement<O.E>
private let _parent: Parent
init(parent: Parent, observer: O) {
_parent = parent
super.init(observer: observer)
}
func run() -> Disposable {
return _parent._scheduler.scheduleRecursive(_parent._element) { e, recurse in
self.forwardOn(.next(e))
recurse(e)
}
}
}

View File

@@ -0,0 +1,150 @@
//
// RetryWhen.swift
// Rx
//
// Created by Junior B. on 06/10/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class RetryTriggerSink<S: Sequence, O: ObserverType, TriggerObservable: ObservableType, Error>
: ObserverType where S.Iterator.Element : ObservableType, S.Iterator.Element.E == O.E {
typealias E = TriggerObservable.E
typealias Parent = RetryWhenSequenceSinkIter<S, O, TriggerObservable, Error>
fileprivate let _parent: Parent
init(parent: Parent) {
_parent = parent
}
func on(_ event: Event<E>) {
switch event {
case .next:
_parent._parent._lastError = nil
_parent._parent.schedule(.moveNext)
case .error(let e):
_parent._parent.forwardOn(.error(e))
_parent._parent.dispose()
case .completed:
_parent._parent.forwardOn(.completed)
_parent._parent.dispose()
}
}
}
class RetryWhenSequenceSinkIter<S: Sequence, O: ObserverType, TriggerObservable: ObservableType, Error>
: SingleAssignmentDisposable
, ObserverType where S.Iterator.Element : ObservableType, S.Iterator.Element.E == O.E {
typealias E = O.E
typealias Parent = RetryWhenSequenceSink<S, O, TriggerObservable, Error>
fileprivate let _parent: Parent
fileprivate let _errorHandlerSubscription = SingleAssignmentDisposable()
init(parent: Parent) {
_parent = parent
}
func on(_ event: Event<E>) {
switch event {
case .next:
_parent.forwardOn(event)
case .error(let error):
_parent._lastError = error
if let failedWith = error as? Error {
// dispose current subscription
super.dispose()
let errorHandlerSubscription = _parent._notifier.subscribe(RetryTriggerSink(parent: self))
_errorHandlerSubscription.disposable = errorHandlerSubscription
_parent._errorSubject.on(.next(failedWith))
}
else {
_parent.forwardOn(.error(error))
_parent.dispose()
}
case .completed:
_parent.forwardOn(event)
_parent.dispose()
}
}
override func dispose() {
super.dispose()
_errorHandlerSubscription.dispose()
}
}
class RetryWhenSequenceSink<S: Sequence, O: ObserverType, TriggerObservable: ObservableType, Error>
: TailRecursiveSink<S, O> where S.Iterator.Element : ObservableType, S.Iterator.Element.E == O.E {
typealias Element = O.E
typealias Parent = RetryWhenSequence<S, TriggerObservable, Error>
let _lock = NSRecursiveLock()
fileprivate let _parent: Parent
fileprivate var _lastError: Swift.Error?
fileprivate let _errorSubject = PublishSubject<Error>()
fileprivate let _handler: Observable<TriggerObservable.E>
fileprivate let _notifier = PublishSubject<TriggerObservable.E>()
init(parent: Parent, observer: O) {
_parent = parent
_handler = parent._notificationHandler(_errorSubject).asObservable()
super.init(observer: observer)
}
override func done() {
if let lastError = _lastError {
forwardOn(.error(lastError))
_lastError = nil
}
else {
forwardOn(.completed)
}
dispose()
}
override func extract(_ observable: Observable<E>) -> SequenceGenerator? {
// It is important to always return `nil` here because there are sideffects in the `run` method
// that are dependant on particular `retryWhen` operator so single operator stack can't be reused in this
// case.
return nil
}
override func subscribeToNext(_ source: Observable<E>) -> Disposable {
let iter = RetryWhenSequenceSinkIter(parent: self)
iter.disposable = source.subscribe(iter)
return iter
}
override func run(_ sources: SequenceGenerator) -> Disposable {
let triggerSubscription = _handler.subscribe(_notifier.asObserver())
let superSubscription = super.run(sources)
return Disposables.create(superSubscription, triggerSubscription)
}
}
class RetryWhenSequence<S: Sequence, TriggerObservable: ObservableType, Error> : Producer<S.Iterator.Element.E> where S.Iterator.Element : ObservableType {
typealias Element = S.Iterator.Element.E
fileprivate let _sources: S
fileprivate let _notificationHandler: (Observable<Error>) -> TriggerObservable
init(sources: S, notificationHandler: @escaping (Observable<Error>) -> TriggerObservable) {
_sources = sources
_notificationHandler = notificationHandler
}
override func run<O : ObserverType>(_ observer: O) -> Disposable where O.E == Element {
let sink = RetryWhenSequenceSink<S, O, TriggerObservable, Error>(parent: self, observer: observer)
sink.disposable = sink.run((self._sources.makeIterator(), nil))
return sink
}
}

View File

@@ -0,0 +1,129 @@
//
// Sample.swift
// RxSwift
//
// Created by Krunoslav Zaher on 5/1/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class SamplerSink<O: ObserverType, ElementType, SampleType>
: ObserverType
, LockOwnerType
, SynchronizedOnType where O.E == ElementType {
typealias E = SampleType
typealias Parent = SampleSequenceSink<O, SampleType>
fileprivate let _parent: Parent
var _lock: NSRecursiveLock {
return _parent._lock
}
init(parent: Parent) {
_parent = parent
}
func on(_ event: Event<E>) {
synchronizedOn(event)
}
func _synchronized_on(_ event: Event<E>) {
switch event {
case .next:
if let element = _parent._element {
if _parent._parent._onlyNew {
_parent._element = nil
}
_parent.forwardOn(.next(element))
}
if _parent._atEnd {
_parent.forwardOn(.completed)
_parent.dispose()
}
case .error(let e):
_parent.forwardOn(.error(e))
_parent.dispose()
case .completed:
if let element = _parent._element {
_parent._element = nil
_parent.forwardOn(.next(element))
}
if _parent._atEnd {
_parent.forwardOn(.completed)
_parent.dispose()
}
}
}
}
class SampleSequenceSink<O: ObserverType, SampleType>
: Sink<O>
, ObserverType
, LockOwnerType
, SynchronizedOnType {
typealias Element = O.E
typealias Parent = Sample<Element, SampleType>
fileprivate let _parent: Parent
let _lock = NSRecursiveLock()
// state
fileprivate var _element = nil as Element?
fileprivate var _atEnd = false
fileprivate let _sourceSubscription = SingleAssignmentDisposable()
init(parent: Parent, observer: O) {
_parent = parent
super.init(observer: observer)
}
func run() -> Disposable {
_sourceSubscription.disposable = _parent._source.subscribe(self)
let samplerSubscription = _parent._sampler.subscribe(SamplerSink(parent: self))
return Disposables.create(_sourceSubscription, samplerSubscription)
}
func on(_ event: Event<Element>) {
synchronizedOn(event)
}
func _synchronized_on(_ event: Event<Element>) {
switch event {
case .next(let element):
_element = element
case .error:
forwardOn(event)
dispose()
case .completed:
_atEnd = true
_sourceSubscription.dispose()
}
}
}
class Sample<Element, SampleType> : Producer<Element> {
fileprivate let _source: Observable<Element>
fileprivate let _sampler: Observable<SampleType>
fileprivate let _onlyNew: Bool
init(source: Observable<Element>, sampler: Observable<SampleType>, onlyNew: Bool) {
_source = source
_sampler = sampler
_onlyNew = onlyNew
}
override func run<O: ObserverType>(_ observer: O) -> Disposable where O.E == Element {
let sink = SampleSequenceSink(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}

View File

@@ -0,0 +1,64 @@
//
// Scan.swift
// RxSwift
//
// Created by Krunoslav Zaher on 6/14/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
class ScanSink<ElementType, Accumulate, O: ObserverType> : Sink<O>, ObserverType where O.E == Accumulate {
typealias Parent = Scan<ElementType, Accumulate>
typealias E = ElementType
fileprivate let _parent: Parent
fileprivate var _accumulate: Accumulate
init(parent: Parent, observer: O) {
_parent = parent
_accumulate = parent._seed
super.init(observer: observer)
}
func on(_ event: Event<ElementType>) {
switch event {
case .next(let element):
do {
_accumulate = try _parent._accumulator(_accumulate, element)
forwardOn(.next(_accumulate))
}
catch let error {
forwardOn(.error(error))
dispose()
}
case .error(let error):
forwardOn(.error(error))
dispose()
case .completed:
forwardOn(.completed)
dispose()
}
}
}
class Scan<Element, Accumulate>: Producer<Accumulate> {
typealias Accumulator = (Accumulate, Element) throws -> Accumulate
fileprivate let _source: Observable<Element>
fileprivate let _seed: Accumulate
fileprivate let _accumulator: Accumulator
init(source: Observable<Element>, seed: Accumulate, accumulator: @escaping Accumulator) {
_source = source
_seed = seed
_accumulator = accumulator
}
override func run<O : ObserverType>(_ observer: O) -> Disposable where O.E == Accumulate {
let sink = ScanSink(parent: self, observer: observer)
sink.disposable = _source.subscribe(sink)
return sink
}
}

Some files were not shown because too many files have changed in this diff Show More