URL
URL์ ์๊ฒฉ ์๋ฒ์ ํญ๋ชฉ์ด๋ ๋ก์ปฌ ํ์ผ์ ๊ฒฝ๋ก์ ๊ฐ์ ํน์ ๋ฆฌ์์ค์ ์์น๋ฅผ ๋ํ๋ธ๋ค.
์ฃผ๋ก Network ์์ฒญ, FileManager ์์ ์ฌ์ฉ.
let urlString = URL(string: "https://api.themoviedb.org/3/movie/top_rated?api_key=123456780eb5a8248e85f2742a7868a8")
url.absoluteURL // https://api.themoviedb.org/3/movie/top_rated?api_key=123456780eb5a8248e85f2742a7868a8
url.scheme // "https"
url.host // "api.themoviedb.org"
url.path // "/3/movie/top_rated"
url.query // "api_key=123456780eb5a8248e85f2742a7868a8"
URLComponents
URL๊ณผ ๊ฐ์ด ์ฝ๊ธฐ ๋ถ๋ถ.
let urlComponents = URLComponents(string: "https://api.themoviedb.org/3/movie/top_rated?api_key=123456780eb5a8248e85f2742a7868a8")
urlComponents!.scheme // scheme: "https"
urlComponents!.host // host: "api.themoviedb.org"
urlComponents!.path // path: "/3/movie/top_rated"
urlComponents!.query // "api_key=426472920eb5a8248e85f2742a7868a8"
urlComponents!.queryItems // ["api_key=426472920eb5a8248e85f2742a7868a8"]
์ด๋ฐ์์ผ๋ก ์ฐ๊ธฐ๊ฐ ๊ฐ๋ฅํด URL๋ณด๋ค ์ ๋์ ์ผ๋ก ์ฌ์ฉ ๊ฐ๋ฅํ๋ค.
let urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "api.themoviedb.org"
urlComponents.path = "/3/movie/top_rated"
urlComponents.query = "api_key=426472920eb5a8248e85f2742a7868a8"
//์ฌ๋ฌ ์ฟผ๋ฆฌ ์์ดํ
์ด ์์ ์ queryItems์ผ๋ก ๋ฐฐ์ด๋ก ๋ค๋ฃฐ ์ ์์.
urlComponents.queryItems = [URLQueryItem(name: "api_key", value: "426472920eb5a8248e85f2742a7868a8")]
urlComponents.url // "https://api.themoviedb.org/3/movie/top_rated?api_key=123456780eb5a8248e85f2742a7868a8"
URLComponents๋ฅผ ์ฌ์ฉํ์ฌ URL์ ์์ฑ ์, ๋ค์ํ URL ๊ตฌ์ฑ ์์
1. scheme: URL์ ์คํด์ ์ค์ . ์๋ฅผ ๋ค์ด, "https", "http", "ftp" ๋ฑ
2. host: URL์ ํธ์คํธ ์ด๋ฆ์ ์ค์
3. port: URL์ ํฌํธ๋ฅผ ์ค์ . ์๋ตํ ๊ฒฝ์ฐ ๊ธฐ๋ณธ ํฌํธ๋ฅผ ์ฌ์ฉ
4. path: URL์ ๊ฒฝ๋ก๋ฅผ ์ค์
5. query: URL์ ์ฟผ๋ฆฌ ๋ฌธ์์ด์ ์ง์ ์ค์ . ์ฟผ๋ฆฌ ๋ฌธ์์ด์ key=value ์์ผ๋ก ๊ตฌ์ฑ
components.query = "key1=value1&key2=value2"
6. queryItems: URL์ ์ฟผ๋ฆฌ ํญ๋ชฉ์ ์ค์ . URLQueryItem ๊ฐ์ฒด์ ๋ฐฐ์ด๋ก ์ค์ ๋๋ฉฐ, ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ฅผ ์๋์ผ๋ก ์ธ์ฝ๋ฉ
๋ฐฐ์ด์ ํํ๋ฅผ ๊ฐ์ง๊ณ ์์ด ์ฌ๋ฌ ์์ดํ ์ถ๊ฐํด์ ์ฌ์ฉ ๊ฐ๋ฅ.
components.queryItems = [URLQueryItem(name: "key1", value: "value1"),
URLQueryItem(name: "key2", value: "value2")]
7. fragment: URL์ ํ๋๊ทธ๋จผํธ๋ฅผ ์ค์ . ์ด๋ URL์ ํด์ ๋ถ๋ถ์ ํด๋นํ๋ฉฐ, ํ์ด์ง ๋ด ์์น๋ฅผ ์ง์ ํ๋ ๋ฐ ์ฌ์ฉ
components.fragment = "section1"
๊ธฐ์กด์ ์ฌ์ฉํ๋ URLSession
//๋ฐฉ๋ฒ1
func fetch<T: Decodable>(urlString: String, type: T) -> T {
let session = URLSession(configuration: .default)
let url = URL(string: urlString)
let request = URLRequest(url: url)
session.dataTask(with: request) { data, response, error in
// do something
}
}
//๋ฐฉ๋ฒ2
func fetch<T: Decodable>(url: URL) -> Single<T> {
return Single.create { observer in
let session = URLSession(configuration: .default)
session.dataTask(with: URLRequest(url: url)) { data, response, error in
}.resume()
}
}
//๋ฐฉ๋ฒ2 ์ฌ์ฉ
let urlString = "https://api.themoviedb.org/3/movie/popular?api_key=\(apiKey)")
guard let url = urlString else { return }
NetworkManager.shared.fetch(url: url)
... ์๋ต
์ฌ๊ธฐ์ URL์์ ์์๊ฐ์ ์ง์ ์ ๋ ฅํ๊ณ ๊ด๋ฆฌํ ๋ ์คํ ๋ฑ๊ณผ ๊ฐ์ด ์ค์๊ฐ ๋ฐ์ํ ์ ์๋ค.
๋คํธ์ํฌ ์์ฒญ์ ํด์ผํ๋ ์ธ์คํด์ค๋ url, urlString์ ์์ฑ, ๊ด๋ฆฌํ๋ ์ฑ ์์ด ์ถ๊ฐ์ ์ผ๋ก ์๊ธด๋ค.
-> ์๋ํฌ์ธํธ๊ฐ ๋ง์์ง๋ค๋ฉด, ์ธ์คํด์ค๋ก ๋ฐ๋ก ๋ง๋ค์ด ์ฑ ์์ ๋ถ๋ฆฌํด๋ณผ ๊ฒ.
Endpoint(์๋ํฌ์ธํธ)
URLSession์์ endPoint๋ ํด๋ผ์ด์ธํธ(์ฌ์ฉ ์ค์ธ ์ฑ)๊ฐ ์๋ฒ๋ก ์์ฒญ์ ๋ณด๋ผ ๋ ๊ทธ ์์ฒญ์ด ์ด๋๋ก ๊ฐ์ผ ํ๋์ง ์๋ ค์ฃผ๋ ์๋ฒ์ ํน์ ์์น๋ ๊ฒฝ๋ก
์๋ฅผ ๋ค์ด์ด๋ณด์๋ฉด,
- Base URL: https://api.example.com
- ์๋ํฌ์ธํธ: /v1/users
์ฌ๊ธฐ์ https://api.example.com/v1/users๋ ์ฌ์ฉ์ ์ ๋ณด์ ์ ๊ทผํ๊ธฐ ์ํ ์ ์ฒด URL์ด ๋๋ค.
์ฟผ๋ฆฌ(Query)
์์ฒญํ๋ ๋ฐ์ดํฐ์ ๋ํด ์ถ๊ฐ์ ์ธ ์กฐ๊ฑด์ด๋ ํํฐ๋ฅผ ์ง์ ํ๊ธฐ ์ํด URL์ ๋ถ๋ ํ๋ผ๋ฏธํฐ๋ฅผ ๋งํ๋ค.
์ฃผ๋ก ? ๋ค์ ์์นํ๋ฉฐ, ์ฌ๋ฌ ํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ์ฉํ๋ฉด &๋ก ๊ตฌ๋ถ๋๋ค.
์๋ฅผ ๋ค์ด, ํน์ ์ฌ์ฉ์ ID์ ํด๋นํ๋ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๊ณ ์ถ๋ค๋ฉด,
- Base URL: https://api.example.com
- ์๋ํฌ์ธํธ: /v1/users
- ์ฟผ๋ฆฌ: ?id=123&name=John
์ ์ฒด URL์ https://api.example.com/v1/users?id=123&name=John์ด ๋๋ค.
์ฌ๊ธฐ์ id=123๊ณผ name=John์ ์๋ฒ์ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์์ฒญํ๋ ์กฐ๊ฑด์ด๋ค.
์ฐจ์ด์ ์์ฝ
์๋ํฌ์ธํธ(Endpoint): ์๋ฒ์ ํน์ ๋ฆฌ์์ค๋ ๊ธฐ๋ฅ์ ์ ๊ทผํ๊ธฐ ์ํ ๊ฒฝ๋ก.
์์: /v1/users
์ฟผ๋ฆฌ(Query): ์์ฒญ์ ์ถ๊ฐ ์กฐ๊ฑด์ ์ง์ ํ์ฌ ํํฐ๋งํ๊ฑฐ๋ ๊ฒ์ํ ๋ ์ฌ์ฉ.
์์: ?id=123&name=John
endpoint๋ URL์ด ์๋, URL์ ๊ตฌ์ฑํ๊ธฐ ์ํ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ Endpoint ํด๋์ค์ ์ธ์คํด์ค๋ค.
์ค์ URL ๊ฐ์ฒด๋ creatEndpoint() ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ URLRequest๋ฅผ ์์ฑํ๋ ๊ณผ์ ์์ ๋ง๋ค์ด์ง๋ค.
๋ฐ๋ผ์, endpoint ์์ฒด๋ URL์ด ์๋๋ฉฐ, endpoint์์ creatEndpoint() ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ
URLRequest ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ฉด, ์ด URLRequest ๊ฐ์ฒด๊ฐ URL์ ํฌํจํ๊ฒ ๋๋ค.
import Foundation
enum HTTPMethodType: String {
case post = "POST"
case get = "GET"
case put = "PUT"
case delete = "DELETE"
}
final class Endpoint{
let baseURL: String
let method: HTTPMethodType
let headerParpmeters: [String: String]
let path: String
let queryParameters: [String: Any]
init(baseURL: String = "https://api.themoviedb.org",
method: HTTPMethodType = .get,
headerParpmeters: [String : String] = [:],
path: String = "",
queryParameters: [String : Any] = [:]
) {
self.baseURL = baseURL
self.method = method
self.headerParpmeters = headerParpmeters
self.path = path
self.queryParameters = queryParameters
}
//URL ์์ฑ ๋ฉ์๋
func creatURL() -> URL? {
var urlComponents = URLComponents(string: baseURL.appending(path))
var queryItems = [URLQueryItem]() // name, value ์์ฑ ์์
queryParameters.forEach {
queryItems.append(URLQueryItem(name: $0.key, value: "\($0.value)"))
}
urlComponents?.queryItems = queryItems
return urlComponents?.url
}
//endpoint ์์ฑ ๋ฉ์๋
func creatEndpoint() throws -> URLRequest {
guard let url = creatURL() else {throw NetworkError.invalidUrl}
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
var allHeaders: [String: String] = [:]
headerParpmeters.forEach {
allHeaders.updateValue($1, forKey: $0)
}
return request
}
}
import Foundation
import RxSwift
enum NetworkError: Error {
case invalidUrl
case dataFetchFail
case decodingFail
}
class NetworkManager {
static let shared = NetworkManager()
private init(){}
//url์์ endpoint๋ก url ๋ฐ์์ค๊ธฐ
func fetch<T: Decodable>(endpoint: Endpoint) -> Single<T> {
do {
//URLRequest ์์ฑ
let request = try endpoint.creatEndpoint() //URLRequest ํ์
๋ฆฌํด
return Single.create { observer in
//URLSession์ ํตํด ์์ฒญ ๋ณด๋ด๊ธฐ
let session = URLSession(configuration: .default)
session.dataTask(with: request) { data, response, error in
// error ๊ฐ ์๋ค๋ฉด Single ์ fail ๋ฐฉ์ถ.
if let error = error {
observer(.failure(error))
return
}
// data ๊ฐ ์๊ฑฐ๋ http ํต์ ์๋ฌ ์ผ ๋ dataFetchFail ๋ฐฉ์ถ.
guard let data = data,
let response = response as? HTTPURLResponse,
(200..<300).contains(response.statusCode) else {
observer(.failure(NetworkError.dataFetchFail))
return
}
do {
// data ๋ฅผ ๋ฐ๊ณ json ๋์ฝ๋ฉ ๊ณผ์ ๊น์ง ์ฑ๊ณตํ๋ค๋ฉด ๊ฒฐ๊ณผ๋ฅผ success ์ ํจ๊ป ๋ฐฉ์ถ.
let decodedData = try JSONDecoder().decode(T.self, from: data)
observer(.success(decodedData))
} catch {
// ๋์ฝ๋ฉ ์คํจํ๋ค๋ฉด decodingFail ๋ฐฉ์ถ.
observer(.failure(NetworkError.decodingFail))
}
}.resume()
return Disposables.create()
}
} catch let error {
return Single.create { observer in
observer(.failure(error))
return Disposables.create()
}
}
}
// ๊ธฐ์กด ์ฝ๋
func fetch<T: Decodable>(url: URL) -> Single<T> {
return Single.create { observer in
let session = URLSession(configuration: .default)
session.dataTask(with: url) { data, response, error in
// error ๊ฐ ์๋ค๋ฉด Single ์ fail ๋ฐฉ์ถ.
if let error = error {
observer(.failure(error))
return
}
// data ๊ฐ ์๊ฑฐ๋ http ํต์ ์๋ฌ ์ผ ๋ dataFetchFail ๋ฐฉ์ถ.
guard let data = data,
let response = response as? HTTPURLResponse,
(200..<300).contains(response.statusCode) else {
observer(.failure(NetworkError.dataFetchFail))
return
}
do {
// data ๋ฅผ ๋ฐ๊ณ json ๋์ฝ๋ฉ ๊ณผ์ ๊น์ง ์ฑ๊ณตํ๋ค๋ฉด ๊ฒฐ๊ณผ๋ฅผ success ์ ํจ๊ป ๋ฐฉ์ถ.
let decodedData = try JSONDecoder().decode(T.self, from: data)
observer(.success(decodedData))
} catch {
// ๋์ฝ๋ฉ ์คํจํ๋ค๋ฉด decodingFail ๋ฐฉ์ถ.
observer(.failure(NetworkError.decodingFail))
}
}.resume()
return Disposables.create()
}
}
}
//MainViewModel
func fetchPopularMovie(){
//๊ธฐ์กด url ์์ฑํ๋ ๋ฐฉ๋ฒ
// guard let url = URL(string: "https://api.themoviedb.org/3/movie/popular?api_key=\(apiKey)") else {
// popularMovieSubject.onError(NetworkError.invalidUrl)
// return
// }
//endpoint ์ธ์คํด์ค ์์ฑ ํ ๋ณ์ ํ ๋น
let endpoint = Endpoint(
path: "/3/movie/popular",
queryParameters: ["api_key": apiKey]
)
// ์ด ๋คํธ์ํฌ fetch ์ ๊ฒฐ๊ณผ๊ฐ Single ํ์
์ผ๋ก ์ต์ ๋ฒ๋ธ์ด๊ธฐ ๋๋ฌธ์ ๊ตฌ๋
ํ ์ ์๋ค
NetworkManager.shared.fetch(endpoint: endpoint)
.subscribe(onSuccess: { [weak self] (movieResponse: MovieResponse) in //๊ตฌ๋
์์.
//fetch์์ ๊ฐ์ด ๋ฐฉ์ถ ๋๋ค๋ฉดonSuccess ๋๋ onFailure ๋ก์ง ์คํ
//๋์ฝ๋ฉ ์ฑ๊ณตํ๋ค๋ฉด observer(.success(decodedData))๋ก ์ด๋ฒคํธ๊ฐ ๋ฐฉ์ถ ๋๊ณ movieResponse๋ก ๊ฐ์ด ๋ค์ด์ BehaviorSubject์ ๊ฐ์ ๋ฃ์ด์ค๋ค.
self?.popularMovieSubject.onNext(movieResponse.results)
}, onFailure: { [weak self] error in
self?.popularMovieSubject.onError(error)
}).disposed(by: disposeBag)
}
'๐ iOS > Network' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
endpoint ์ฌ์ฉ (0) | 2024.08.23 |
---|---|
JSONObject์ JSONArray (0) | 2024.08.20 |
DispatchQueue.global().async์ DispatchQueue.main.async (0) | 2024.08.03 |
[URLSession] completion(nil) ๋์ Result ํ์ ์ฌ์ฉํด๋ณด๊ธฐ (0) | 2024.08.02 |
[URLSession] ๋น๋๊ธฐ ์์ , ํด๋ก์ , ์ฝ๋ฐฑ ํจ์, Completion Handler, ๊ทธ๋ฆฌ๊ณ @escaping์ ๊ฐ๋ ๊ณผ ์ญํ (0) | 2024.08.01 |