我正在尝试从API加载自定义数据类型,并将其显示在iOS应用程序的登录页上。
据我所知,我应该称之为:
override func viewDidLoad() {
performSelector(inBackground: #selector(fetchJSON), with: nil)
let myCustomDataType = //how so I get this back
tableView.reloadData()
//...
}
显然,fetchJSON的声明应该在另一个文件中,这样我就不会用不需要做的事情来填充我的控制器。然而,我需要这个函数来返回[MyCustomDataType]的列表,并在我的登录页上显示这些列表。
func fetchJSON() -> [MyCustomDataType] {
//get api, fetch data, put it in my custom data type list
return myCustomDataType
}
我使用闭包吗?创建全局[MyCystomDataType]。或者我该如何做到这一点?注意任务是异步的,因为我显示的表格单元格有点像facebook或instagram新闻订阅页面。
如果fetchJSON
异步加载数据,则必须添加一个完成处理程序
func fetchJSON(completion: @escaping ([MyCustomDataType]) -> Void){
API.fetch { fetchedData in
completion(fetchedData)
}
}
然后在后台线程上调用fetchJSON
,并在主线程上重新加载表视图
override func viewDidLoad() {
DispatchQueue.global().async {
self.fetchJSON { data in
DispatchQueue.main.async {
self.myCustomDataType = data
self.tableView.reloadData()
}
}
}
}
流行的异步API(如URLSession
和Alamofire
(在后台线程上隐式地执行网络请求。如果要使用其中一个,则可以省略第一个async
块。
performSelector
是一个过时的objective-c-ishAPI。不要在Swift 中使用
performSelector(inBackground:)
不是GCD。这是OSX10.5中的一个预GCD方法,您几乎永远不应该使用它。(自从10.6引入GCD以来,我想我没有任何理由使用它。(
通常,对于网络请求,您根本不需要直接使用GCD。URLSessionTask已经是异步的。有关更多信息,请参阅将网站数据提取到内存中。如图所示,您通常需要使用GCD(DispatchQueue.main.async
(将数据返回到UI,但不需要它来启动请求。
但对于你的基本问题,答案是,到viewDidLoad
完成时,你还没有数据。你需要能够处理没有数据的问题,并适当地绘制你的UI。当数据显示时,您可以更新UI。
正如Vadian所说(+1(,您希望使用异步模式,而使用闭包是一个很好的模式。但问题不仅在于如何返回数据,还在于在出现错误的情况下,如何报告Error
。我们经常使用Result<Success, Failure>
模式来实现这一点。例如,它可能具有Result<[MyCustomDataType], Error>
类型,而不是[MyCustomDataType]
参数。
例如,假设您正在通过URLSession
执行请求。然后你可能会有一个例程,比如:
enum ApiError: Error {
case networkError(Data?, URLResponse?)
}
func fetchJSON<T: Decodable>(_ request: URLRequest, queue: DispatchQueue = .main, completion: @escaping (Result<T, Error>) -> Void) {
let task = URLSession.shared.dataTask(with: request) { data, response, error in
// detect and report error
guard
error == nil,
let responseData = data,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode
else {
queue.async {
completion(.failure(error ?? ApiError.networkError(data, response)))
}
return
}
// no network error, so let's parse the response
do {
let responseObject = try JSONDecoder().decode(T.self, from: responseData)
queue.async {
completion(.success(responseObject))
}
} catch let parseError {
queue.async {
completion(.failure(parseError))
}
}
}
task.resume()
}
并执行特定请求:
func fetchMyCustomDataTypes(completion: @escaping (Result<[MyCustomDataType], Error>) -> Void) {
let request: URLRequest = ... // build your request here
fetchJSON(request) { result in
completion(result)
}
}
现在,不要过于沉迷于上面的细节,因为您的实现可能会有所不同(例如,您可以很容易地使用Alamofire或其他什么(。关键是我们有一个包含Swift.Result
参数的完成处理程序闭包,通过该参数将数据或错误信息提供给调用者。
现在,呼叫者可以根据结果是success
还是failure
:采取相应的行动
override func viewDidLoad() {
super.viewDidLoad() // make sure to call `super`
fetchMyCustomDataTypes { result in
switch result {
case .failure(let error):
// handle error here, e.g., report the error in the UI; or at the minimum, during development, just print the error
print(error)
case .success(let objects):
// use the `[MyCustomDataType]` array, `objects` here, e.g.
self.objects = objects
self.tableView.reloadData()
}
}
}
但是调用者不使用performSelector
。它也不需要将此网络请求调度到后台队列,因为网络请求本身就已经是异步的。只需调用fetchJSON
并指定完成处理程序中必须进行的UI更新即可。