عميل HTTP بلغة Swift: إجراء الطلبات بشكل صحيح

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة
```html

مقدمة: لماذا يعتبر عميل HTTP الصحيح مهمًا لتطبيق Swift الخاص بك

نادرًا ما تخلو تطبيقات iOS وmacOS الحديثة من التفاعل مع الخوادم البعيدة. سواء كان تحميل البيانات من REST API، أو إرسال التحليلات، أو العمل مع الخدمات السحابية — فإن عميل HTTP بلغة Swift هو أساس التفاعل الشبكي. ومع ذلك، يرتكب العديد من المطورين، خاصة المبتدئين، أخطاء نموذجية: يتجاهلون معالجة الأخطاء، ولا يستخدمون الإمكانيات الحديثة للغة (async/await)، أو ينشئون "كودًا معقدًا" من الاستدعاءات الشبكية. في هذه المقالة، سنشرح كيفية كتابة عميل HTTP موثوق وفعال بلغة Swift، باتباع أفضل الممارسات.

1. أساسيات URLSession: قلب الطلبات الشبكية

في المكتبة القياسية لـ Swift، يتولى إطار العمل Foundation والفئة URLSession مسؤولية التفاعل الشبكي. هذه أداة قوية تدعم تكوين الجلسات، والتخزين المؤقت، والطلبات الخلفية، وأكثر من ذلك بكثير.

1.1. إنشاء عميل HTTP أساسي

لنبدأ بهيكل بسيط يقوم بتنفيذ طلبات GET. سنستخدم enum لوصف الأخطاء المحتملة وبروتوكول للمرونة:

import Foundation

enum HTTPClientError: Error {
    case invalidURL
    case noData
    case decodingError(Error)
    case networkError(Error)
    case statusCode(Int)
}

protocol HTTPClientProtocol {
    func get<T: Decodable>(from urlString: String, responseType: T.Type) async throws -> T
}

final class HTTPClient: HTTPClientProtocol {
    private let session: URLSession
    private let decoder: JSONDecoder
    
    init(session: URLSession = .shared, decoder: JSONDecoder = JSONDecoder()) {
        self.session = session
        self.decoder = decoder
    }
    
    func get<T: Decodable>(from urlString: String, responseType: T.Type) async throws -> T {
        guard let url = URL(string: urlString) else {
            throw HTTPClientError.invalidURL
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        
        let (data, response) = try await session.data(for: request)
        
        guard let httpResponse = response as? HTTPURLResponse else {
            throw HTTPClientError.networkError(NSError(domain: "Invalid response", code: -1))
        }
        
        guard (200...299).contains(httpResponse.statusCode) else {
            throw HTTPClientError.statusCode(httpResponse.statusCode)
        }
        
        do {
            return try decoder.decode(T.self, from: data)
        } catch {
            throw HTTPClientError.decodingError(error)
        }
    }
}

1.2. لماذا async/await؟

قبل ظهور Swift 5.5، كان المطورون يستخدمون الإغلاقات (completion handlers) أو المكتبات الخارجية (Alamofire). Async/await يجعل الكود خطيًا وواضحًا وسهل القراءة. تتم معالجة الأخطاء من خلال do-catch، وليس من خلال الإغلاقات المتداخلة.

2. POST, PUT, DELETE: العمل مع طرق HTTP المختلفة

يجب أن يدعم عميل HTTP الحقيقي جميع الطرق الأساسية. سنضيف إلى البروتوكول طرقًا لإرسال البيانات وتحديث الموارد.

2.1. توسيع البروتوكول لـ POST و PUT

extension HTTPClientProtocol {
    func post<T: Decodable, U: Encodable>(
        to urlString: String,
        body: U,
        responseType: T.Type
    ) async throws -> T {
        guard let url = URL(string: urlString) else {
            throw HTTPClientError.invalidURL
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        request.httpBody = try JSONEncoder().encode(body)
        
        return try a
```                

المدونات

توصيات الكتب