-
v2.0.0 Stable
released this
2026-08-29 19:01:22 -03:00 | 19 commits to main since this releaseBreaking release.
APIwas rebuilt around typed request bodies, first-class
multipart uploads, and Swift structured concurrency. Consuming projects must
update call sites — see the migration table below and
Documentation/API.md.Projects targeting iOS 13–14 should stay on
1.1.2.
Why this changed
The previous
API.requesthad grown unsafe and restrictive:params: Any?— no compile-time checking. The form-url-encoded path
silently dropped any non-Stringvalue.- File upload was a magic-string hack —
params["file"]had to be a path
string, one file only, and the whole file was read into memory. @MainActoron the whole type forced every caller onto the main thread,
andstatic var certDatawas an unprotected data race under Swift concurrency.T: Codableon responses forced every DTO to also beEncodablewhen
only decoding was needed.- ~10 force-unwraps plus
as!/try!in the request path. persistConnectionrecursed without bound on a permanent 4xx — an
infinite loop.
What changed
APIis now anactorNo
@MainActor. Calls run off the main thread; every call isawait.await API.shared.setupCertification(certData: p12, password: "…") // was setupCertificationRequest await API.shared.setPersistConnectionDelay(5) // was API.persistConnectionDelay = 5Typed request bodies
params:andjsonEncoding:are gone. Pass anHTTPBody:try await API.shared.request(url: "…/users", method: .post, body: jsonBody(dto)) try await API.shared.request(url: "…/token", method: .post, body: .form(["grant_type": "password"]))JSONBody,FormURLEncodedBody(percent-escapes values, never drops them),
RawBody, or your ownHTTPBodyconformance —APIneeds no change to accept
a new body type.First-class multipart uploads
var form = MultipartForm() form.field("caption", "Sunset") form.file("photo", data: jpegData, filename: "p.jpg") form.file("video", url: localURL) // streamed from disk, never fully in memory let result: UploadResult = try await API.shared.upload(url: "…/media", form: form)Plus an
onProgress:overload for progress reporting.Response constraint relaxed
T: Codable→T: Decodable & Sendable, acrossAPI.request,API.upload,
and allJSONDecoder.decodeoverloads.Other changes
- URL templating:
pathParams: ["id": "42"]fills{id}placeholders. - Custom headers now merge over the defaults (previously replaced them).
persistConnectionretries are bounded byAPI.maxPersistRetries(3) — the
old code could loop forever on a permanent 4xx.- Zero force operations (
!,as!,try!) in the networking code. Resultenum andAPI.defaultParamsremoved.Dictionary.toObjetct()nowthrowsinstead oftry!.- Deployment target: iOS 15 / watchOS 8 (was iOS 13 / watchOS 6).
Migration
Before After API.shared.request(url:params: ["k": v], method: .post)API.shared.request(url:body: .form(["k": v]), method: .post)— orjsonBody(dto)for JSONparams: ["file": "/path/f.png", "caption": "x"]var f = MultipartForm(); f.field("caption","x"); f.file("file", url: URL(fileURLWithPath: "/path/f.png")); try await API.shared.upload(url:form: f)paramsused for{id}URL templatingpathParams: ["id": id]API.shared.setupCertificationRequest(certData:password:)await API.shared.setupCertification(certData:password:)API.persistConnectionDelay = 5await API.shared.setPersistConnectionDelay(5)response type T: CodableT: Decodable & Sendable(non-Sendableclasses break)LCEssentials.ResultSwift.Resultdict.toObjetct() as Ttry dict.toObjetct() as TjsonEncoding:argumentremoved — the body type decides its encoding Full usage guide:
Documentation/API.md.
Tests
New
LCEssentialsTeststarget — 30 XCTest cases covering request bodies,
multipart serialisation, upload, actor isolation, error mapping, and retry
bounding, run against a stubURLProtocol.Downloads