Irbisa · Шпаргалка
Сеть в iOS
URLSession, Codable, async-API, интерцепторы, обработка ошибок
Что такое REST и чем отличаются
GET,POST,PUT,PATCH,DELETEи коды ответа HTTP?Что происходит, когда приложение делает HTTP-запрос, и как отправить его через
URLSession?// GETlet url = URL(string: "https://api.example.com/users")!let (data, response) = try await URLSession.shared.data(from: url)guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { throw URLError(.badServerResponse)}…Чем отличаются
URLиURLRequest?var components = URLComponents(string: "https://api.example.com/users/42")!components.queryItems = [URLQueryItem(name: "fields", value: "name,avatar")]let url = components.url! // just the address var request = URLRequest(url: url) // address + how to send itrequest.httpMethod = "PATCH"…Чем отличаются задачи data, download и upload в
URLSession?Что такое App Transport Security и как разрешить домен, который работает только по обычному HTTP?
<key>NSAppTransportSecurity</key><dict> <key>NSExceptionDomains</key> <dict> <key>legacy-cdn.example.com</key> <dict>…Этот поиск ничего не находит по
"C++ & Swift", хотя в браузере тот же текст работает. Что не так со сборкой URL?func search(_ term: String, page: Int) async throws -> Data { let url = URL(string: "https://api.example.com/search?q=\(term)&page=\(page)")! let (data, _) = try await URLSession.shared.data(from: url) return data} // search("C++ & Swift", page: 1)Какие бывают
URLSessionConfiguration(default,ephemeralиbackground) и когда какую использовать?Этот загрузчик вызывается для каждой картинки в ленте. Что с ним не так и чего это стоит?
final class ImageLoader: NSObject, URLSessionDataDelegate { func load(_ url: URL) async throws -> Data { let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) let (data, _) = try await session.data(from: url) return data }}Как бы вы построили сетевой слой поверх
URLSessionи какие ошибки он должен различать?protocol HTTPTransport: Sendable { func data(for request: URLRequest) async throws -> (Data, URLResponse)}extension URLSession: HTTPTransport { // explicit: a method with a defaulted `delegate:` does not satisfy the requirement func data(for request: URLRequest) async throws -> (Data, URLResponse) {…Когда использовать
URLSessionнапрямую, а когда брать библиотеку вроде Alamofire?Декодирование падает с «Expected date string to be ISO8601-formatted», хотя
created_atвыглядит обычной ISO-датой. Что не нравитсяJSONDecoder?nonisolated(unsafe) private let fractional: ISO8601DateFormatter = { let f = ISO8601DateFormatter() f.formatOptions = [.withInternetDateTime, .withFractionalSeconds] return f}()nonisolated(unsafe) private let plain = ISO8601DateFormatter() // no fractions…Бэкенд добавил новый тип поста, и вместо пропуска одной строки пустеет вся лента. Как сделать декодирование устойчивым?
enum PostKind: String, Decodable { case text, photo, video, unknown init(from decoder: Decoder) throws { let raw = try decoder.singleValueContainer().decode(String.self) self = PostKind(rawValue: raw) ?? .unknown // new server value -> unknown…Как отменять сетевые запросы и обрабатывать timeout в Swift?
// Cancel-previous typeahead with debounce@MainActorfinal class SearchModel { private var task: Task<Void, Never>? private let api: SearchAPI var results: [Item] = []…Найдите баг: когда бэкенд отдаёт страницу ошибки, экран профиля бесконечно крутит спиннер вместо того, чтобы показать ошибку.
func fetchProfile(id: Int) async throws -> Profile { try await withCheckedThrowingContinuation { continuation in LegacyAPI.shared.get("/profiles/\(id)") { data, error in if let error { continuation.resume(throwing: error) return } guard let data, let profile = try? JSONDecoder().decode(Profile.self, from: data) else { return } continuation.resume(returning: profile) } }}Соединение пропадает посреди запроса. Как должен реагировать сетевой слой и почему не стоит проверять доступность сети перед каждым вызовом?
let config = URLSessionConfiguration.defaultconfig.waitsForConnectivity = true // queue rather than failconfig.timeoutIntervalForResource = 120 // but not foreverconfig.allowsConstrainedNetworkAccess = false // skip Low Data Modelet session = URLSession(configuration: config, delegate: MonitorDelegate(), delegateQueue: nil)…Какие упавшие запросы вы повторяете и как повторять, не устраивая лавину на сервер?
func withRetry<T>( attempts: Int = 3, base: Duration = .milliseconds(300), isRetryable: (Error) -> Bool = APIError.isTransient, operation: () async throws -> T) async throws -> T {…Как кешировать HTTP-ответы в iOS и за что отвечают
URLCache,Cache-ControlиETag?let config = URLSessionConfiguration.defaultconfig.urlCache = URLCache(memoryCapacity: 32 << 20, diskCapacity: 256 << 20)config.requestCachePolicy = .useProtocolCachePolicylet session = URLSession(configuration: config) // Explicit conditional request, when you keep the copy yourself…Как загрузить файл через
multipart/form-dataсURLSession?func uploadAvatar(_ image: Data, name: String) async throws -> Data { let boundary = "Boundary-\(UUID().uuidString)" var body = Data() func append(_ s: String) { body.append(s.data(using: .utf8)!) } // Text field…Как тестировать код, который ходит через URLSession, не выходя в сеть?
final class StubURLProtocol: URLProtocol { nonisolated(unsafe) static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))? override class func canInit(with request: URLRequest) -> Bool { true } override class func canonicalRequest(for request: URLRequest) -> URLRequest { request } override func stopLoading() {}…Когда выбирать WebSocket вместо HTTP-запросов и что нужно клиенту на
URLSessionWebSocketTask?final class PriceSocket { private let session: URLSession private var task: URLSessionWebSocketTask? init(session: URLSession = .shared) { self.session = session }…Как работает аутентификация по токенам в приложении и как обновить access-токен, когда много запросов одновременно получили 401?
actor TokenStore { private(set) var accessToken: String? private var refreshTask: Task<String, Error>? private let refresh: @Sendable () async throws -> String // calls /auth/refresh directly init(accessToken: String?, refresh: @escaping @Sendable () async throws -> String) {…Эндпоинт экспорта отдаёт 400 МБ NDJSON, и на старых телефонах
data(for:)приводит к убийству приложения по памяти. Что вызывать вместо него?struct Row: Decodable { let id: String; let amount: Decimal } func importRows(from url: URL, session: URLSession, store: RowStore) async throws { let (bytes, response) = try await session.bytes(from: url) guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { throw URLError(.badServerResponse) // check before consuming…Что такое certificate pinning, как реализовать его в iOS и что может пойти не так?
final class PinningDelegate: NSObject, URLSessionDelegate { private let pins: Set<Data> // SHA-256 of each pinned key: current + backup init(pins: Set<Data>) { self.pins = pins } func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,…Видео на два гигабайта должно докачаться, даже если пользователь вышел из приложения. Как это сделать?
final class Downloader: NSObject, URLSessionDownloadDelegate, @unchecked Sendable { static let shared = Downloader() var backgroundCompletion: (() -> Void)? // touched on the main queue only // Creating it again after a relaunch reconnects to the system's transfers lazy var session: URLSession = {…Авторизация, логирование, ретраи и метрики должны оборачивать каждый запрос, но клиент не должен превратиться в god object. Как это скомпоновать?
typealias Send = @Sendable (URLRequest) async throws -> (Data, HTTPURLResponse) protocol HTTPMiddleware: Sendable { func intercept(_ request: URLRequest, next: Send) async throws -> (Data, HTTPURLResponse)}…