Когда я запускаю код, я получаю ошибку ---------------------- "[NSJSONSerialization dataWithJSONObject:options:error:]: Неверный тип верхнего уровня в JSON писать". ----------------------
class BaseAPI<T: TargetType> {
func fetchData<M: Decodable>(target: T, responseClass: M.Type, completion:@escaping(Result<M?, NSError>) -> Void) {
let method = Alamofire.HTTPMethod(rawValue: target.methods.rawValue)
let headers = Alamofire.HTTPHeaders(target.headers ?? [:])
let params = buildParams(task: target.task)
AF.request(target.baseUrl + target.path, method: method, parameters: params.0, encoding: params.1, headers: headers).responseDecodable(of: M.self) { response in
guard let statusCode = response.response?.statusCode else {
//ADD Custom Error
completion(.failure(NSError()))
return
}
if statusCode == 200 {
// Successful Request
guard let jsonResponse = try? response.result.get() else {
completion(.failure(NSError()))
return
}
guard let theJSONData = try? JSONSerialization.data(withJSONObject: jsonResponse, options: []) else {
completion(.failure(NSError()))
return
}
guard let responseObj = try? JSONDecoder().decode(M.self, from: theJSONData) else {
completion(.failure(NSError()))
return
}
completion(.success(responseObj))
} else {
completion(.failure(NSError()))
}
}
}
private func buildParams(task: Task) -> ([String:Any], ParameterEncoding) {
switch task {
case .requestPlain:
return ([:], URLEncoding.default)
case .requestParameters(parameters: let parameters, encoding: let encoding):
return (parameters, encoding)
}
}
}
вместо try? JSONSerialization.data(withJSONObject: jsonResponse, options: [])
Когда я пробую код try? JSONSerialization.data(withJSONObject: jsonResponse, options: [])
, он выдает ошибку No exact matches in call to class method 'jsonObject'
.
Саналкрлк, пожалуйста, простите за замечание по совершенно несвязанной теме, но я мог бы предложить переименовать ваш тип Task
, чтобы избежать коллизий с объектом Swift concurrency Task. Позже, когда вы начнете рассматривать параллелизм в Swift, это только станет источником путаницы.
jsonResponse
уже является декодированным объектом M
. Нельзя вызвать JSONSerialization
метод data(withJSONObject:options:) с произвольным объектом. Он работает только с очень специфическими типами, а именно со словарями со строками, числами и т. д. (Список допустимых типов см. в JSONSerialization
документации.) Вот почему вы получаете эту ошибку.
При этом возникает вопрос, зачем вообще пытаться перекодировать объект обратно в Data
с помощью JSONSerialization
и перекодировать этот Data
с помощью JSONDecoder
обратно в M
. Я бы предложил устранить это, упростив код:
func fetchData<M: Decodable>(target: T, responseClass: M.Type = M.self, completion: @escaping(Result<M, Error>) -> Void) {
let method = Alamofire.HTTPMethod(rawValue: target.methods.rawValue)
let headers = Alamofire.HTTPHeaders(target.headers ?? [:])
let params = buildParams(task: target.task)
AF.request(target.baseUrl + target.path, method: method, parameters: params.0, encoding: params.1, headers: headers)
.validate()
.responseDecodable(of: M.self) { response in
switch response.result {
case .success(let value): completion(.success(value))
case .failure(let error): completion(.failure(error))
}
}
}
Несколько наблюдений:
В качестве небольшого уточнения я бы посоветовал сделать responseClass
необязательным параметром (поскольку тип часто можно определить из контекста). Но я сохранил параметр для тех случаев, когда компилятор не может автоматически определить тип.
Я также использовал validate
, чтобы проверить код статуса. Вам не нужно вручную проверять это самостоятельно.
Также обратите внимание: в процессе упрощения я отказался от использования try?
. try?
является антипаттерном, поскольку он отбрасывает значимую диагностическую информацию.
Но мы всегда передаем значимую ошибку: если вызывающую программу не интересуют детали, она может игнорировать конкретную возникшую ошибку. Но в тех случаях, когда у вас действительно есть ошибки синтаксического анализа, чрезвычайно полезно иметь подробную информацию (чтобы вы знали, какие поля вызвали проблему).
Я также изменил тип возвращаемого значения на Result<M, Error>
. Две вещи, на которые следует обратить внимание:
M?
, скорее это может быть M
. Если все прошло успешно, нет необходимости требовать от вызывающей стороны развернуть необязательный элемент, которого никогда не будет nil
.Error
, а не NSError
. Теоретически это может быть AFError
(хотя мне обычно не нравится распространять вызывающие типы, специфичные для Alamofire).Кстати, параллельное исполнение Swift может выглядеть так:
func fetchData<M: Decodable>(target: T, responseClass: M.Type = M.self) async throws -> M {
let params = buildParams(task: target.task)
return try await AF.request(
target.baseUrl + target.path,
method: Alamofire.HTTPMethod(rawValue: target.methods.rawValue),
parameters: params.0,
encoding: params.1,
headers: target.headers.flatMap { Alamofire.HTTPHeaders($0) }
)
.validate()
.serializingDecodable(M.self)
.value
}
Это типа
M
.