decodable 준수하지 않앗다고 자꾸 에러 뜬다...
private(set) var user: UserProfile?
이거 무슨 뜻일지...
// setupUI
// userprofile
// bind
// search control
// network
해야하는 일들 목록. 이거를 구현해줘야겠다
먼저 imageView 둥글게 만들어줄 건데
이미지 뷰 사이즈가 160 * 160이라서 똥그랗게 만드려면 코너를 반만큼 해주면 된다
bind() 메소드를 만들어줄 건데 유저가 업데이트 되면 ui가 변경되게끔 해줘야한다
@Published private(set) var user: UserProfile?
로 만들어줬다
var subscriptions = Set<AnyCancellable>()
섭스크립션도 만들어주고
$user로 접근해서 런루프 메인에 올리고 .sink .store 해준다
sink에서 update 해주면 되니까 또 새로운 update 메소드를 팠음!
이제 서치컨트롤 코드로 넣어주자
private func embedSearchControl() {
self.navigationItem.title = "Search"
let searchController = UISearchController(searchResultsController: nil)
searchController.hidesNavigationBarDuringPresentation = false
searchController.searchBar.placeholder = "woozoobro"
searchController.searchResultsUpdater = self
searchController.searchBar.delegate = self
self.navigationItem.searchController = searchController
}
서치results 업데이터와 delegate은 프로토콜이 필요하겠죠
private func update(_ user: UserProfile?) {
guard let user = user else {
self.nameLabel.text = "n/a"
self.loginLabel.text = "n/a"
self.followerLabel.text = "n/a"
self.followingLabel.text = "n/a"
self.thumbnail.image = UIImage(systemName: "network")
return
}
self.nameLabel.text = user.name
self.loginLabel.text = user.login
self.followerLabel.text = "follower: \(user.followers)"
self.followingLabel.text = "following: \(user.following)"
self.thumbnail.image = nil // avatar url
}
avatarUrl 로 이미지 세팅해줄거라 우선은 nil로 설정해두자
base 와 path로 나눠 봤다!
params와 header는 이번에 필요없지만 query 하는 거 대비해서 준비해둠
extension SearchViewController: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
print("button clicked: \(searchBar.text)")
guard let keyword = searchBar.text, !keyword.isEmpty else { return }
let base = "https://api.github.com/"
let path = "users/\(keyword)"
let params: [String: String] = [:]
let header: [String: String] = ["Content-Type":"application/json"]
var urlComponents = URLComponents(string: base + path)!
let queryItems = params.map { (key: String, value: String) in
return URLQueryItem(name: key, value: value)
}
urlComponents.queryItems = queryItems
var request = URLRequest(url: urlComponents.url!)
header.forEach { (key: String, value: String) in
request.addValue(value, forHTTPHeaderField: key)
}
}
}
urlComponents 로 url을 준비하고
queryItems도 준비하고
request를 만들면 header도 추가해줄수 있음
이제 dataTask를 추가해보자
URLSession.shared
.dataTaskPublisher(for: request)
.tryMap { result -> Data in
guard let response = result.response as? HTTPURLResponse, (200..<300).contains(response.statusCode) else {
let response = result.response as? HTTPURLResponse
let statusCode = response?.statusCode ?? -1
throw NetworkError.responseError(statusCode: statusCode)
}
return result.data
}
.decode(type: UserProfile.self, decoder: JSONDecoder())
.receive(on: RunLoop.main)
.sink { completion in
print("completion: \(completion)")
} receiveValue: { user in
self.user = user
}.store(in: &subscriptions)
session을 만들고 dataTaskPublihser
.tryMap으로 생길수 있는 에러도 throw해줌
이 때 리턴은 result의 data 타입으로 맵하고
나온 건 decode 해줍니다
그러면 userProfile이 나올텐데 RunLoop.main에서 작업되게 해주고
sink 메소드로 completion과 받은 value를 다시 user에 넣어주면
위에서 선언해뒀던 update 함수의 $user를 통해서 ui가 업뎃 되겠습니다
completion은 Result enum 타입이라
switch case로 .failure 과 .finished 설정 가능
imgae url -> image
Exact 버전으로 swiftPackage 설치 해줬음
사용법은 쉽다
리소스랑 네트워크로 나눠서 바꿔보자
네트워크 서비스를 먼저 가져오자
enum NetworkError: Error {
case invalidRequest
case invalidResponse
case responseError(statusCode: Int)
case jsonDecodingError(error: Error)
}
final class NetworkService {
let session: URLSession
init(configuration: URLSessionConfiguration) {
session = URLSession(configuration: configuration)
}
func load<T>(_ resource: Resource<T>) -> AnyPublisher<T, Error> {
guard let request = resource.urlRequest else {
return .fail(NetworkError.invalidRequest)
}
return session
.dataTaskPublisher(for: request)
.tryMap { result -> Data in
guard let response = result.response as? HTTPURLResponse,
(200..<300).contains(response.statusCode)
else {
let response = result.response as? HTTPURLResponse
let statusCode = response?.statusCode ?? -1
throw NetworkError.responseError(statusCode: statusCode)
}
return result.data
}
.decode(type: T.self, decoder: JSONDecoder())
.eraseToAnyPublisher()
}
}
NetworkService.swift 요렇게 선언해뒀다
Resource랑 NetworkService 구조 익히고 나면
이렇게 깔끔하게 네트워킹 구현이 가능해진다...!!