大家好,我正在尝试制作一个简单且可重复使用的Swift网络层
也许这不是在视图中循环返回数据的最佳方法,但是在我尝试将返回的Api数据传递到SwiftUI视图中进行循环后,我得到了错误提示:
Escaping closure captures mutating 'self' parameter
不知道我在本课中错过的地方或什么
这是文件的图片
ContentView.swift
struct ContentView: View {
var emptyDataArr: [CollectionItem] = []
init() {
ServiceLayer.request(router: Router.getSources) { (result: Result<[String : [CollectionItem]], Error>) in
switch result {
case .success(let data):
print(data)
self.emptyDataArr = data["custom_collections"]!
case .failure:
print(result)
}
}
}
var body: some View {
VStack (alignment: .leading) {
Text("No thing yet")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
ServiceLayer类ServiceLayer.swift
class ServiceLayer {
// 1.
class func request<T: Codable>(router: Router, completion: @escaping (Result<[String: [T]], Error>) -> ()) {
// 2.
var components = URLComponents()
components.scheme = router.scheme
components.host = router.host
components.path = router.path
components.queryItems = router.parameters
// 3.
guard let url = components.url else { return }
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = router.method
// 4.
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: urlRequest) { data, response, error in
// 5.
guard error == nil else {
completion(.failure(error!))
print(error?.localizedDescription)
return
}
guard response != nil else {
return
}
guard let data = data else {
return
}
print(data)
// 6.
let responseObject = try! JSONDecoder().decode([String: [T]].self, from: data)
// 7.
DispatchQueue.main.async {
// 8.
completion(.success(responseObject))
}
}
dataTask.resume()
}
}
答案 0 :(得分:6)
问题在于ContentView
是一个结构,这意味着它是一个值类型。您不能将其传递给闭包并对其进行突变。如果这样做的话,什么都不会改变,因为闭包将拥有其自己的struct的独立副本。
您的问题是您将视图和模型混合在一起。一个给定的View可以有很多很多副本(每次将其传递给函数时,都会创建一个副本)。您不希望所有这些副本都发起一个请求。而是将请求逻辑移到Model对象中,然后让View对其进行观察。
答案 1 :(得分:0)
我今天遇到了同样的问题,找到了这篇文章,然后我按照@Rob Napier的建议,最后做了一个可行的例子。
希望以下代码可以提供帮助(注意:它不能直接回答您的问题,但我认为它将作为示例):
import Combine
import SwiftUI
import PlaygroundSupport
let url = URL(string: "https://source.unsplash.com/random")!
// performs a network request to fetch a random image from Unsplash’s public API
func imagePub() -> AnyPublisher<Image?, Never> {
URLSession.shared
.dataTaskPublisher(for: url)
.map { data, _ in Image(uiImage: UIImage(data: data)!)}
.print("image")
.replaceError(with: nil)
.eraseToAnyPublisher()
}
class ViewModel: ObservableObject {
// model
@Published var image: Image?
// simulate user taps on a button
let taps = PassthroughSubject<Void, Never>()
var subscriptions = Set<AnyCancellable>()
init() {
taps
// ⭐️ map the tap to a new network request
.map { _ in imagePub() }
// ⭐️ accept only the latest tap
.switchToLatest()
.assign(to: \.image, on: self)
.store(in: &subscriptions)
}
func getImage() {
taps.send()
}
}
struct ContentView: View {
// view model
@ObservedObject var viewModel = ViewModel()
var body: some View {
VStack {
viewModel.image?
.resizable().scaledToFit().frame(height: 400).border(Color.black)
Button(action: {
self.viewModel.getImage()
}, label: {
Text("Tap")
.padding().foregroundColor(.white)
.background(Color.pink).cornerRadius(12)
})
}.padding().background(Color.gray)
}
}
PlaygroundPage.current.setLiveView(ContentView())
在点击按钮之前,ContentView
如下所示:
点击按钮后(等待几秒钟),看起来像这样:
所以我知道它正在工作^ _ ^
答案 2 :(得分:0)
只需将调用从 init
内部移动到 onAppear
中某个位置的 body