Azimjon Abdurasulov

Networking & Codable

Cheat Sheet'lar

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

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

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:

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.

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:

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:

/*
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

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:

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)
        }
    }
}

Markdown manba

iOS Lessons