مقدمة: لماذا يعتبر عميل 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
```