在 Swift Combine Publisher 中,我遇到了符合 'RangeReplaceableCollection' 的错误
In Swift Combine Publisher, I am getting conform to 'RangeReplaceableCollection' error
对于我正在处理的 Swift 发布者解决方案,我收到以下编译时错误:
Instance method 'store(in:)' requires that 'Set<[AnyCancellable]>' conform to 'RangeReplaceableCollection'
我的代码块如下:
public class APIService {
static let baseURL = URL(string: "https://api.recommendations.samba.tv")!
var apiKey: String
var logger = Logger(label: "com.samba.tv.recommendations")
private var subscriptions = Set<AnyCancellable>()
private let jsonDecoder: JSONDecoder = {
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-mm-dd"
jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter)
return jsonDecoder
}()
/// Defines the type of API error returbed,
/// An enumerating type to classify the types of errors that can occur. Returns an `Error`, such as `noResponse`. Specific errors with messages will return both an error message and code.
enum APIError: Error, LocalizedError {
case missingCredentials
case urlError(URLError)
case responseError(Int)
case decodingError(DecodingError)
case genericError
var localizedDescription: String {
switch self {
case .missingCredentials:
return "Missing credentials"
case .urlError(let error):
return error.localizedDescription
case .decodingError(let error):
return error.localizedDescription
case .responseError(let status):
return "Bad response code: \(status)"
case .genericError:
return "An unknown error has been occured"
}
}
}
/// Obtain Endpoint complete with baseURL
///
/// An enumeration type, with an in-line function. This method constructs the entire URL, using the `baseURL` variable declared prior.
/// By passing the specific enum type, such as `.popular(param)`, along with a `Popular` parameter, which is optional.
enum Endpoint {
case popular(popularParam: Popular?)
func path() -> URLComponents {
switch self {
case let .popular(popularParam):
var queryItems = [URLQueryItem]()
let urlString = "\(baseURL)/popularity"
var components = URLComponents(url: URL(string: urlString)!, resolvingAgainstBaseURL: true)!
if let countryCode = popularParam?.country {
queryItems.append(URLQueryItem(name: "country_code", value: countryCode.rawValue))
}
if let namespace = popularParam?.namespace {
queryItems.append(URLQueryItem(name: "response_namespace", value: namespace.rawValue))
}
if let contentType = popularParam?.contentType {
queryItems.append(URLQueryItem(name: "content_type", value: contentType.rawValue))
}
if let genres = popularParam?.genres {
let genreArrayString = genres.compactMap({String([=12=].rawValue)}).joined(separator: ",")
queryItems.append(URLQueryItem(name: "genre_ids", value: genreArrayString))
}
components.queryItems = queryItems
return components
}
}
}
init(key: String) {
apiKey = key
}
/// Generic `GET` function, facilitating calls by passing in an `Endpoint` along with any parameters.
/// It returns a `Result<T, APIError>`.
/// The method also inserts the required header parameters, including `apiKey`, and will fail if it is not valid, or missing.
func GET<T: Codable>(endpoint: Endpoint,
params: [String: String]?) -> Future<[T], APIError> {
// guard let apiKey = APIService.shared.apiKey else {
// throw APIError.missingCredentials
// }
return Future<[T], APIError> { promise in
var components = endpoint.path()
var request = URLRequest(url: components.url!)
request.addValue("Bearer \(self.apiKey)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "accept")
if let params = params {
for (_, value) in params.enumerated() {
components.queryItems?.append(URLQueryItem(name: value.key, value: value.value))
}
}
request.httpMethod = "GET"
URLSession.shared.dataTaskPublisher(for: request)
.tryMap{ data, response -> Data in
guard let httpResponse = response as? HTTPURLResponse, 200...299 ~= httpResponse.statusCode else {
throw APIError.responseError((response as? HTTPURLResponse)?.statusCode ?? 500)
}
return data
}
.decode(type: PaginatedResponse.self, decoder: self.jsonDecoder)
.receive(on: RunLoop.main)
.sink(receiveCompletion: { (completion) in
if case let .failure(error) = completion {
switch error {
case let urlError as URLError:
promise(.failure(.urlError(urlError)))
case let decodingError as DecodingError:
promise(.failure(.decodingError(decodingError)))
case let apiError as APIError:
promise(.failure(apiError))
default:
promise(.failure(.genericError))
}
}
}, receiveValue: { promise(.success([=12=].results)) })
.store(in: &self.subscriptions)
}
}
}
错误出现在最后一行,.store(in:....
。
问题
如何解决错误?
额外引用类
PaginatedResponse.swift
import Foundation
struct PaginatedResponse<T: Codable>: Codable {
let paginator: Paginator
let results: [T]
}
ContentItem.swift
struct ContentItem: Codable, Equatable {
enum ContentID: Codable {
case text(String)
case number(Int)
case empty
}
enum SeriesID: Codable {
case text(String)
case number(Int)
case empty
}
enum CodingKeys: String, CodingKey {
case contentID = "content_id"
case seriesID = "series_id"
case rank = "rank"
case score = "score"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
rank = try values.decode(Int.self, forKey: .rank)
score = try values.decode(Float.self, forKey: .score)
seriesID = try values.decode(SeriesID.self, forKey: .seriesID)
contentID = try values.decode(ContentID.self, forKey: .contentID)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(score, forKey: .score)
try container.encode(rank, forKey: .rank)
try container.encode(seriesID, forKey: .seriesID)
try container.encode(contentID, forKey: .contentID)
}
let rank: Int
let score: Float
let seriesID: SeriesID
let contentID: ContentID
}
extension ContentItem.SeriesID: Equatable, Hashable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .text(let text):
try container.encode(text)
case .number(let number):
try container.encode(number)
case .empty:
break
}
}
public func hash(into hasher: inout Hasher) {
hasher.combine(self.hashValue)
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let text = try? container.decode(String.self) {
self = .text(text)
} else if let number = try? container.decode(Int.self) {
self = .number(number)
} else {
//assertionFailure("Unknown id type")
self = .empty
}
}
}
extension ContentItem.ContentID: Equatable, Hashable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .text(let text):
try container.encode(text)
case .number(let number):
try container.encode(number)
case .empty:
break
}
}
public func hash(into hasher: inout Hasher) {
hasher.combine(self.hashValue)
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let text = try? container.decode(String.self) {
self = .text(text)
} else if let number = try? container.decode(Int.self) {
self = .number(number)
} else {
// assertionFailure("Unknown id type")
self = .empty
}
}
}
我试过 var subscriptions = [AnyCancellable]()
似乎成功了。不确定它为什么起作用,但如果有人对此有解释,那么后代在这里添加会很好。
对于我正在处理的 Swift 发布者解决方案,我收到以下编译时错误:
Instance method 'store(in:)' requires that 'Set<[AnyCancellable]>' conform to 'RangeReplaceableCollection'
我的代码块如下:
public class APIService {
static let baseURL = URL(string: "https://api.recommendations.samba.tv")!
var apiKey: String
var logger = Logger(label: "com.samba.tv.recommendations")
private var subscriptions = Set<AnyCancellable>()
private let jsonDecoder: JSONDecoder = {
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-mm-dd"
jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter)
return jsonDecoder
}()
/// Defines the type of API error returbed,
/// An enumerating type to classify the types of errors that can occur. Returns an `Error`, such as `noResponse`. Specific errors with messages will return both an error message and code.
enum APIError: Error, LocalizedError {
case missingCredentials
case urlError(URLError)
case responseError(Int)
case decodingError(DecodingError)
case genericError
var localizedDescription: String {
switch self {
case .missingCredentials:
return "Missing credentials"
case .urlError(let error):
return error.localizedDescription
case .decodingError(let error):
return error.localizedDescription
case .responseError(let status):
return "Bad response code: \(status)"
case .genericError:
return "An unknown error has been occured"
}
}
}
/// Obtain Endpoint complete with baseURL
///
/// An enumeration type, with an in-line function. This method constructs the entire URL, using the `baseURL` variable declared prior.
/// By passing the specific enum type, such as `.popular(param)`, along with a `Popular` parameter, which is optional.
enum Endpoint {
case popular(popularParam: Popular?)
func path() -> URLComponents {
switch self {
case let .popular(popularParam):
var queryItems = [URLQueryItem]()
let urlString = "\(baseURL)/popularity"
var components = URLComponents(url: URL(string: urlString)!, resolvingAgainstBaseURL: true)!
if let countryCode = popularParam?.country {
queryItems.append(URLQueryItem(name: "country_code", value: countryCode.rawValue))
}
if let namespace = popularParam?.namespace {
queryItems.append(URLQueryItem(name: "response_namespace", value: namespace.rawValue))
}
if let contentType = popularParam?.contentType {
queryItems.append(URLQueryItem(name: "content_type", value: contentType.rawValue))
}
if let genres = popularParam?.genres {
let genreArrayString = genres.compactMap({String([=12=].rawValue)}).joined(separator: ",")
queryItems.append(URLQueryItem(name: "genre_ids", value: genreArrayString))
}
components.queryItems = queryItems
return components
}
}
}
init(key: String) {
apiKey = key
}
/// Generic `GET` function, facilitating calls by passing in an `Endpoint` along with any parameters.
/// It returns a `Result<T, APIError>`.
/// The method also inserts the required header parameters, including `apiKey`, and will fail if it is not valid, or missing.
func GET<T: Codable>(endpoint: Endpoint,
params: [String: String]?) -> Future<[T], APIError> {
// guard let apiKey = APIService.shared.apiKey else {
// throw APIError.missingCredentials
// }
return Future<[T], APIError> { promise in
var components = endpoint.path()
var request = URLRequest(url: components.url!)
request.addValue("Bearer \(self.apiKey)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "accept")
if let params = params {
for (_, value) in params.enumerated() {
components.queryItems?.append(URLQueryItem(name: value.key, value: value.value))
}
}
request.httpMethod = "GET"
URLSession.shared.dataTaskPublisher(for: request)
.tryMap{ data, response -> Data in
guard let httpResponse = response as? HTTPURLResponse, 200...299 ~= httpResponse.statusCode else {
throw APIError.responseError((response as? HTTPURLResponse)?.statusCode ?? 500)
}
return data
}
.decode(type: PaginatedResponse.self, decoder: self.jsonDecoder)
.receive(on: RunLoop.main)
.sink(receiveCompletion: { (completion) in
if case let .failure(error) = completion {
switch error {
case let urlError as URLError:
promise(.failure(.urlError(urlError)))
case let decodingError as DecodingError:
promise(.failure(.decodingError(decodingError)))
case let apiError as APIError:
promise(.failure(apiError))
default:
promise(.failure(.genericError))
}
}
}, receiveValue: { promise(.success([=12=].results)) })
.store(in: &self.subscriptions)
}
}
}
错误出现在最后一行,.store(in:....
。
问题
如何解决错误?
额外引用类
PaginatedResponse.swift
import Foundation
struct PaginatedResponse<T: Codable>: Codable {
let paginator: Paginator
let results: [T]
}
ContentItem.swift
struct ContentItem: Codable, Equatable {
enum ContentID: Codable {
case text(String)
case number(Int)
case empty
}
enum SeriesID: Codable {
case text(String)
case number(Int)
case empty
}
enum CodingKeys: String, CodingKey {
case contentID = "content_id"
case seriesID = "series_id"
case rank = "rank"
case score = "score"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
rank = try values.decode(Int.self, forKey: .rank)
score = try values.decode(Float.self, forKey: .score)
seriesID = try values.decode(SeriesID.self, forKey: .seriesID)
contentID = try values.decode(ContentID.self, forKey: .contentID)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(score, forKey: .score)
try container.encode(rank, forKey: .rank)
try container.encode(seriesID, forKey: .seriesID)
try container.encode(contentID, forKey: .contentID)
}
let rank: Int
let score: Float
let seriesID: SeriesID
let contentID: ContentID
}
extension ContentItem.SeriesID: Equatable, Hashable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .text(let text):
try container.encode(text)
case .number(let number):
try container.encode(number)
case .empty:
break
}
}
public func hash(into hasher: inout Hasher) {
hasher.combine(self.hashValue)
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let text = try? container.decode(String.self) {
self = .text(text)
} else if let number = try? container.decode(Int.self) {
self = .number(number)
} else {
//assertionFailure("Unknown id type")
self = .empty
}
}
}
extension ContentItem.ContentID: Equatable, Hashable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .text(let text):
try container.encode(text)
case .number(let number):
try container.encode(number)
case .empty:
break
}
}
public func hash(into hasher: inout Hasher) {
hasher.combine(self.hashValue)
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let text = try? container.decode(String.self) {
self = .text(text)
} else if let number = try? container.decode(Int.self) {
self = .number(number)
} else {
// assertionFailure("Unknown id type")
self = .empty
}
}
}
我试过 var subscriptions = [AnyCancellable]()
似乎成功了。不确定它为什么起作用,但如果有人对此有解释,那么后代在这里添加会很好。