# Networking & Codable Cheat Sheet

iOS ilovalarda REST API so'rovlarini yuborish, `URLSession`, xatolarni qayta ishlash va `Codable` orqali JSON bilan ishlash bo'yicha tezkor ma'lumotnoma.

---

## 1. URLSession & async/await Asoslari

```swift
func fetchPosts() async throws -> [Post] {
    guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else {
        throw URLError(.badURL)
    }

    let (data, response) = try await URLSession.shared.data(from: url)

    guard let httpResponse = response as? HTTPURLResponse,
          (200...299).contains(httpResponse.statusCode) else {
        throw URLError(.badServerResponse)
    }

    return try JSONDecoder().decode([Post].self, from: data)
}
```

---

## 2. URLRequest: POST, Headers va JSON Body

```swift
func createPost(post: PostCreateDTO, token: String) async throws -> Post {
    let url = URL(string: "https://api.example.com/posts")!
    
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.timeoutInterval = 30 // soniya
    
    // Sarlavhalar (Headers)
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
    
    // HTTP Body
    request.httpBody = try JSONEncoder().encode(post)
    
    let (data, response) = try await URLSession.shared.data(for: request)
    guard let http = response as? HTTPURLResponse, http.statusCode == 201 else {
        throw NetworkError.serverError
    }
    
    return try JSONDecoder().decode(Post.self, from: data)
}
```

---

## 3. URLComponents bilan Query Parametrlar

`https://api.example.com/search?query=swift&page=1&limit=20` ko'rinishidagi so'rovlarni xavfsiz shakllantirish:

```swift
var components = URLComponents(string: "https://api.example.com/search")
components?.queryItems = [
    URLQueryItem(name: "query", value: "swift"),
    URLQueryItem(name: "page", value: "1"),
    URLQueryItem(name: "limit", value: "20")
]

guard let url = components?.url else { return }
```

---

## 4. Codable va CodingKeys

Serverdagi `snake_case` kalitlarini Swift'dagi `camelCase` xususiyatlariga o'tkazish.

```swift
struct UserProfile: Codable {
    let id: Int
    let fullName: String
    let avatarURL: URL?
    let createdAt: Date

    enum CodingKeys: String, CustomCodingKey, CodingKey {
        case id
        case fullName = "full_name"
        case avatarURL = "avatar_url"
        case createdAt = "created_at"
    }
}
```

### JSONDecoder Sozlamalari:
Agar barcha kalitlar snake_case bo'lsa, `CodingKeys` yozmasdan avtomatik konvertatsiya qilish mumkin:

```swift
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601 // yoki .secondsSince1970

let profile = try decoder.decode(UserProfile.self, from: jsonData)
```

---

## 5. Custom Decoding (`init(from:)`)

Kutilmagan JSON tuzilmalarini yoki ichma-ich (nested) ma'lumotlarni tekislash (flattening) uchun:

```swift
/*
JSON ko'rinishi:
{
  "id": 1,
  "user": {
    "details": {
      "city": "Toshkent"
    }
  }
}
*/

struct CityInfo: Decodable {
    let id: Int
    let city: String

    enum RootKeys: String, CodingKey { case id, user }
    enum UserKeys: String, CodingKey { case details }
    enum DetailsKeys: String, CodingKey { case city }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: RootKeys.self)
        self.id = try container.decode(Int.self, forKey: .id)

        let userContainer = try container.nestedContainer(keyedBy: UserKeys.self, forKey: .user)
        let detailsContainer = try userContainer.nestedContainer(keyedBy: DetailsKeys.self, forKey: .details)
        self.city = try detailsContainer.decode(String.self, forKey: .city)
    }
}
```

---

## 6. Xatoliklarni (Error Handling) To'g'ri Tashkil Qilish

```swift
enum APIError: Error, LocalizedError {
    case invalidURL
    case badStatusCode(Int)
    case decodingFailed(Error)
    case noInternet
    case unknown(Error)

    var errorDescription: String? {
        switch self {
        case .invalidURL: return "Noto'g'ri URL manzil"
        case .badStatusCode(let code): return "Server xatosi: HTTP \(code)"
        case .decodingFailed: return "Ma'lumotni o'qib bo'lmadi"
        case .noInternet: return "Internet bilan aloqa yo'q"
        case .unknown(let error): return error.localizedDescription
        }
    }
}
```

---

## 7. Generic API Client Namunasi

Loyihalarda qayta ishlatiladigan universal tarmoq mijozi:

```swift
protocol NetworkServiceProtocol {
    func request<T: Decodable>(endpoint: Endpoint) async throws -> T
}

final class NetworkClient: NetworkServiceProtocol {
    static let shared = NetworkClient()
    private let session: URLSession

    init(session: URLSession = .shared) {
        self.session = session
    }

    func request<T: Decodable>(endpoint: Endpoint) async throws -> T {
        guard let url = endpoint.url else { throw APIError.invalidURL }
        
        var request = URLRequest(url: url)
        request.httpMethod = endpoint.method.rawValue
        request.allHTTPHeaderFields = endpoint.headers
        request.httpBody = endpoint.body

        let data: Data
        let response: URLResponse
        do {
            (data, response) = try await session.data(for: request)
        } catch let urlError as URLError where urlError.code == .notConnectedToInternet {
            throw APIError.noInternet
        } catch {
            throw APIError.unknown(error)
        }

        guard let http = response as? HTTPURLResponse else {
            throw APIError.invalidURL
        }

        guard (200...299).contains(http.statusCode) else {
            throw APIError.badStatusCode(http.statusCode)
        }

        do {
            let decoder = JSONDecoder()
            decoder.keyDecodingStrategy = .convertFromSnakeCase
            return try decoder.decode(T.self, from: data)
        } catch {
            throw APIError.decodingFailed(error)
        }
    }
}
```
