Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
80
100%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Advisory
Suggest reviewing before use
Complete implementation patterns for URLSession-based networking.
A full-featured client with middleware support, configurable decoding, and response validation.
protocol APIClientProtocol: Sendable {
func request<T: Decodable & Sendable>(
_ type: T.Type,
endpoint: Endpoint
) async throws -> T
func request(endpoint: Endpoint) async throws
func upload<T: Decodable & Sendable>(
_ type: T.Type,
endpoint: Endpoint,
body: Data
) async throws -> T
}struct Endpoint: Sendable {
let path: String
var method: HTTPMethod = .get
var queryItems: [URLQueryItem] = []
var headers: [String: String] = [:]
var body: Data? = nil
var cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy
var timeoutInterval: TimeInterval = 30
enum HTTPMethod: String, Sendable {
case get = "GET"
case post = "POST"
case put = "PUT"
case patch = "PATCH"
case delete = "DELETE"
}
func urlRequest(relativeTo baseURL: URL) -> URLRequest {
var components = URLComponents(
url: baseURL.appendingPathComponent(path),
resolvingAgainstBaseURL: true
)!
if !queryItems.isEmpty {
components.queryItems = queryItems
}
var request = URLRequest(url: components.url!)
request.httpMethod = method.rawValue
request.httpBody = body
request.cachePolicy = cachePolicy
request.timeoutInterval = timeoutInterval
for (key, value) in headers {
request.setValue(value, forHTTPHeaderField: key)
}
return request
}
}final class APIClient: APIClientProtocol {
private let baseURL: URL
private let session: URLSession
private let decoder: JSONDecoder
private let encoder: JSONEncoder
private let middlewares: [any RequestMiddleware]
init(
baseURL: URL,
session: URLSession = .shared,
decoder: JSONDecoder = {
let d = JSONDecoder()
d.dateDecodingStrategy = .iso8601
d.keyDecodingStrategy = .convertFromSnakeCase
return d
}(),
encoder: JSONEncoder = {
let e = JSONEncoder()
e.dateEncodingStrategy = .iso8601
e.keyEncodingStrategy = .convertToSnakeCase
return e
}(),
middlewares: [any RequestMiddleware] = []
) {
self.baseURL = baseURL
self.session = session
self.decoder = decoder
self.encoder = encoder
self.middlewares = middlewares
}
func request<T: Decodable & Sendable>(
_ type: T.Type,
endpoint: Endpoint
) async throws -> T {
let request = try await prepareRequest(for: endpoint)
let (data, response) = try await session.data(for: request)
try validateResponse(response, data: data)
return try decoder.decode(T.self, from: data)
}
func request(endpoint: Endpoint) async throws {
let request = try await prepareRequest(for: endpoint)
let (data, response) = try await session.data(for: request)
try validateResponse(response, data: data)
}
func upload<T: Decodable & Sendable>(
_ type: T.Type,
endpoint: Endpoint,
body: Data
) async throws -> T {
var request = try await prepareRequest(for: endpoint)
request.httpBody = body
let (data, response) = try await session.upload(for: request, from: body)
try validateResponse(response, data: data)
return try decoder.decode(T.self, from: data)
}
// MARK: - Convenience methods
func get<T: Decodable & Sendable>(
_ type: T.Type,
path: String,
queryItems: [URLQueryItem] = []
) async throws -> T {
try await request(type, endpoint: Endpoint(
path: path,
method: .get,
queryItems: queryItems
))
}
func post<T: Decodable & Sendable, B: Encodable & Sendable>(
_ type: T.Type,
path: String,
body: B
) async throws -> T {
let bodyData = try encoder.encode(body)
return try await request(type, endpoint: Endpoint(
path: path,
method: .post,
headers: ["Content-Type": "application/json"],
body: bodyData
))
}
func delete(path: String) async throws {
try await request(endpoint: Endpoint(path: path, method: .delete))
}
// MARK: - Internal
private func prepareRequest(for endpoint: Endpoint) async throws -> URLRequest {
var request = endpoint.urlRequest(relativeTo: baseURL)
for middleware in middlewares {
request = try await middleware.prepare(request)
}
return request
}
private func validateResponse(_ response: URLResponse, data: Data) throws {
guard let http = response as? HTTPURLResponse else {
throw NetworkError.invalidResponse
}
guard (200..<300).contains(http.statusCode) else {
let apiError = try? decoder.decode(APIErrorBody.self, from: data)
throw NetworkError.httpError(
statusCode: http.statusCode,
data: data,
message: apiError?.message
)
}
}
}enum NetworkError: Error, Sendable, LocalizedError {
case invalidResponse
case httpError(statusCode: Int, data: Data, message: String? = nil)
case noConnection
case timedOut
case cancelled
var errorDescription: String? {
switch self {
case .invalidResponse:
return "Invalid server response"
case .httpError(let code, _, let message):
return message ?? "HTTP error \(code)"
case .noConnection:
return "No internet connection"
case .timedOut:
return "Request timed out"
case .cancelled:
return nil
}
}
static func from(_ urlError: URLError) -> NetworkError {
switch urlError.code {
case .notConnectedToInternet, .networkConnectionLost:
return .noConnection
case .timedOut:
return .timedOut
case .cancelled:
return .cancelled
default:
return .invalidResponse
}
}
}
struct APIErrorBody: Decodable, Sendable {
let code: String?
let message: String?
}protocol RequestMiddleware: Sendable {
func prepare(_ request: URLRequest) async throws -> URLRequest
}
struct AuthMiddleware: RequestMiddleware {
let tokenProvider: @Sendable () async throws -> String
func prepare(_ request: URLRequest) async throws -> URLRequest {
var request = request
let token = try await tokenProvider()
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
return request
}
}For complex request construction, a builder provides a fluent API that reduces errors.
struct RequestBuilder: Sendable {
private var method: String = "GET"
private var path: String
private var baseURL: URL
private var queryItems: [URLQueryItem] = []
private var headers: [String: String] = [:]
private var body: Data?
private var cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy
private var timeout: TimeInterval = 30
init(baseURL: URL, path: String) {
self.baseURL = baseURL
self.path = path
}
func method(_ method: String) -> RequestBuilder {
var copy = self
copy.method = method
return copy
}
func query(_ name: String, _ value: String?) -> RequestBuilder {
guard let value else { return self }
var copy = self
copy.queryItems.append(URLQueryItem(name: name, value: value))
return copy
}
func header(_ name: String, _ value: String) -> RequestBuilder {
var copy = self
copy.headers[name] = value
return copy
}
func jsonBody<T: Encodable>(_ value: T) throws -> RequestBuilder {
var copy = self
copy.body = try JSONEncoder().encode(value)
copy.headers["Content-Type"] = "application/json"
return copy
}
func timeout(_ interval: TimeInterval) -> RequestBuilder {
var copy = self
copy.timeout = interval
return copy
}
func cachePolicy(_ policy: URLRequest.CachePolicy) -> RequestBuilder {
var copy = self
copy.cachePolicy = policy
return copy
}
func build() -> URLRequest {
var components = URLComponents(
url: baseURL.appendingPathComponent(path),
resolvingAgainstBaseURL: true
)!
if !queryItems.isEmpty {
components.queryItems = queryItems
}
var request = URLRequest(url: components.url!)
request.httpMethod = method
request.httpBody = body
request.cachePolicy = cachePolicy
request.timeoutInterval = timeout
for (key, value) in headers {
request.setValue(value, forHTTPHeaderField: key)
}
return request
}
}
// Usage
let request = try RequestBuilder(baseURL: apiURL, path: "users")
.method("POST")
.header("X-Request-ID", UUID().uuidString)
.jsonBody(CreateUserRequest(name: "Alice", email: "alice@example.com"))
.timeout(15)
.build()Multipart/form-data uploads are common for file attachments. Build the body manually -- no third-party library needed.
struct MultipartFormData: Sendable {
private let boundary: String
private var parts: [Part] = []
init(boundary: String = UUID().uuidString) {
self.boundary = boundary
}
var contentType: String {
"multipart/form-data; boundary=\(boundary)"
}
mutating func addField(name: String, value: String) {
parts.append(Part(
headers: "Content-Disposition: form-data; name=\"\(name)\"",
body: Data(value.utf8)
))
}
mutating func addFile(
name: String,
filename: String,
mimeType: String,
data: Data
) {
parts.append(Part(
headers: """
Content-Disposition: form-data; name="\(name)"; filename="\(filename)"\r
Content-Type: \(mimeType)
""",
body: data
))
}
func encode() -> Data {
var data = Data()
let crlf = "\r\n"
for part in parts {
data.append("--\(boundary)\(crlf)")
data.append("\(part.headers)\(crlf)\(crlf)")
data.append(part.body)
data.append(crlf)
}
data.append("--\(boundary)--\(crlf)")
return data
}
private struct Part: Sendable {
let headers: String
let body: Data
}
}
extension Data {
mutating func append(_ string: String) {
append(Data(string.utf8))
}
}
// Usage
var form = MultipartFormData()
form.addField(name: "title", value: "Profile Photo")
form.addFile(
name: "image",
filename: "photo.jpg",
mimeType: "image/jpeg",
data: imageData
)
var request = URLRequest(url: uploadURL)
request.httpMethod = "POST"
request.setValue(form.contentType, forHTTPHeaderField: "Content-Type")
request.httpBody = form.encode()
let (data, response) = try await URLSession.shared.upload(
for: request,
from: form.encode()
)Use bytes(for:) for real-time progress. The response includes
expectedContentLength for calculating percentage.
@available(iOS 15.0, *)
func downloadWithProgress(
from url: URL,
progressHandler: @Sendable (Double) -> Void
) async throws -> Data {
let (bytes, response) = try await URLSession.shared.bytes(from: url)
let expectedLength = response.expectedContentLength
var receivedData = Data()
if expectedLength > 0 {
receivedData.reserveCapacity(Int(expectedLength))
}
var receivedLength: Int64 = 0
for try await byte in bytes {
receivedData.append(byte)
receivedLength += 1
if expectedLength > 0 {
let progress = Double(receivedLength) / Double(expectedLength)
progressHandler(progress)
}
}
return receivedData
}For large files, prefer URLSessionDownloadTask with a delegate for
better memory efficiency and background support.
@available(iOS 15.0, *)
final class DownloadManager: NSObject, URLSessionDownloadDelegate, Sendable {
private let continuation: AsyncStream<DownloadEvent>.Continuation
enum DownloadEvent: Sendable {
case progress(Double)
case completed(URL)
case failed(Error)
}
static func download(from url: URL) -> AsyncStream<DownloadEvent> {
AsyncStream { continuation in
let manager = DownloadManager(continuation: continuation)
let session = URLSession(
configuration: .default,
delegate: manager,
delegateQueue: nil
)
session.downloadTask(with: url).resume()
}
}
private init(continuation: AsyncStream<DownloadEvent>.Continuation) {
self.continuation = continuation
}
nonisolated func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL
) {
// Move file to permanent location before this method returns
let destination = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
do {
try FileManager.default.moveItem(at: location, to: destination)
continuation.yield(.completed(destination))
} catch {
continuation.yield(.failed(error))
}
continuation.finish()
}
nonisolated func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64
) {
guard totalBytesExpectedToWrite > 0 else { return }
let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
continuation.yield(.progress(progress))
}
nonisolated func urlSession(
_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: (any Error)?
) {
if let error {
continuation.yield(.failed(error))
continuation.finish()
}
}
}A reusable paginator that conforms to AsyncSequence, yielding pages
of results until the server indicates no more data.
struct PageResponse<T: Decodable & Sendable>: Decodable, Sendable {
let data: [T]
let pagination: PaginationInfo
}
struct PaginationInfo: Decodable, Sendable {
let nextCursor: String?
let hasMore: Bool
}
struct CursorPaginator<T: Decodable & Sendable>: AsyncSequence {
typealias Element = [T]
private let fetchPage: @Sendable (String?) async throws -> PageResponse<T>
init(fetchPage: @escaping @Sendable (String?) async throws -> PageResponse<T>) {
self.fetchPage = fetchPage
}
func makeAsyncIterator() -> Iterator {
Iterator(fetchPage: fetchPage)
}
struct Iterator: AsyncIteratorProtocol {
private let fetchPage: @Sendable (String?) async throws -> PageResponse<T>
private var cursor: String?
private var exhausted = false
init(fetchPage: @escaping @Sendable (String?) async throws -> PageResponse<T>) {
self.fetchPage = fetchPage
}
mutating func next() async throws -> [T]? {
guard !exhausted else { return nil }
try Task.checkCancellation()
let response = try await fetchPage(cursor)
cursor = response.pagination.nextCursor
exhausted = !response.pagination.hasMore
return response.data.isEmpty ? nil : response.data
}
}
}
// Usage
let paginator = CursorPaginator<User> { cursor in
var queryItems = [URLQueryItem(name: "limit", value: "50")]
if let cursor {
queryItems.append(URLQueryItem(name: "cursor", value: cursor))
}
return try await client.get(
PageResponse<User>.self,
path: "users",
queryItems: queryItems
)
}
var allUsers: [User] = []
for try await batch in paginator {
allUsers.append(contentsOf: batch)
}struct OffsetPaginator<T: Decodable & Sendable>: AsyncSequence {
typealias Element = [T]
private let pageSize: Int
private let fetchPage: @Sendable (Int, Int) async throws -> [T]
init(
pageSize: Int = 20,
fetchPage: @escaping @Sendable (_ offset: Int, _ limit: Int) async throws -> [T]
) {
self.pageSize = pageSize
self.fetchPage = fetchPage
}
func makeAsyncIterator() -> Iterator {
Iterator(pageSize: pageSize, fetchPage: fetchPage)
}
struct Iterator: AsyncIteratorProtocol {
private let pageSize: Int
private let fetchPage: @Sendable (Int, Int) async throws -> [T]
private var offset = 0
private var exhausted = false
init(
pageSize: Int,
fetchPage: @escaping @Sendable (Int, Int) async throws -> [T]
) {
self.pageSize = pageSize
self.fetchPage = fetchPage
}
mutating func next() async throws -> [T]? {
guard !exhausted else { return nil }
try Task.checkCancellation()
let items = try await fetchPage(offset, pageSize)
offset += items.count
if items.count < pageSize { exhausted = true }
return items.isEmpty ? nil : items
}
}
}URLProtocol is the correct way to mock network responses at the
transport level. It works with any URLSession configuration and does
not require changing production code.
final class MockURLProtocol: URLProtocol {
nonisolated(unsafe) static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data))?
override class func canInit(with request: URLRequest) -> Bool { true }
override class func canonicalRequest(for request: URLRequest) -> URLRequest { request }
override func startLoading() {
guard let handler = Self.requestHandler else {
fatalError("MockURLProtocol.requestHandler is not set")
}
do {
let (response, data) = try handler(request)
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
client?.urlProtocol(self, didLoad: data)
client?.urlProtocolDidFinishLoading(self)
} catch {
client?.urlProtocol(self, didFailWithError: error)
}
}
override func stopLoading() {}
}import Testing
@Suite struct APIClientTests {
let client: APIClient
let session: URLSession
init() {
let config = URLSessionConfiguration.ephemeral
config.protocolClasses = [MockURLProtocol.self]
session = URLSession(configuration: config)
client = APIClient(
baseURL: URL(string: "https://api.example.com")!,
session: session
)
}
@Test func fetchUsersDecodesCorrectly() async throws {
let usersJSON = """
[{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
"""
MockURLProtocol.requestHandler = { request in
#expect(request.url?.path == "/users")
let response = HTTPURLResponse(
url: request.url!,
statusCode: 200,
httpVersion: nil,
headerFields: ["Content-Type": "application/json"]
)!
return (response, Data(usersJSON.utf8))
}
let users: [User] = try await client.get([User].self, path: "users")
#expect(users.count == 2)
#expect(users[0].name == "Alice")
}
@Test func fetchReturnsHTTPError() async throws {
MockURLProtocol.requestHandler = { request in
let response = HTTPURLResponse(
url: request.url!,
statusCode: 404,
httpVersion: nil,
headerFields: nil
)!
return (response, Data())
}
await #expect(throws: NetworkError.self) {
let _: [User] = try await client.get([User].self, path: "missing")
}
}
@Test func requestIncludesAuthHeader() async throws {
let authClient = APIClient(
baseURL: URL(string: "https://api.example.com")!,
session: session,
middlewares: [AuthMiddleware { "test-token" }]
)
MockURLProtocol.requestHandler = { request in
#expect(request.value(forHTTPHeaderField: "Authorization") == "Bearer test-token")
let response = HTTPURLResponse(
url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil
)!
return (response, Data("{}".utf8))
}
let _: EmptyResponse = try await authClient.get(EmptyResponse.self, path: "me")
}
}
struct EmptyResponse: Decodable, Sendable {}Respect cancellation. Do not retry client errors (4xx except 429 rate limiting). Include jitter to prevent thundering herd.
func withRetry<T: Sendable>(
maxAttempts: Int = 3,
initialDelay: Duration = .seconds(1),
maxDelay: Duration = .seconds(30),
shouldRetry: @Sendable (Error) -> Bool = { error in
if error is CancellationError { return false }
if case NetworkError.httpError(let code, _, _) = error {
return code >= 500 || code == 429
}
if let urlError = error as? URLError {
return [.timedOut, .networkConnectionLost, .notConnectedToInternet]
.contains(urlError.code)
}
return false
},
operation: @Sendable () async throws -> T
) async throws -> T {
var lastError: Error?
for attempt in 0..<maxAttempts {
try Task.checkCancellation()
do {
return try await operation()
} catch {
lastError = error
guard shouldRetry(error), attempt < maxAttempts - 1 else {
throw error
}
// Exponential backoff with jitter
let base = Double(initialDelay.components.seconds) * pow(2.0, Double(attempt))
let capped = min(base, Double(maxDelay.components.seconds))
let jitter = Double.random(in: 0...(capped * 0.1))
let delay = Duration.seconds(capped + jitter)
try await Task.sleep(for: delay)
}
}
throw lastError!
}
// Usage
let users = try await withRetry {
try await client.get([User].self, path: "users")
}Prefer ATS NSPinnedDomains for declarative certificate pinning when the
pinset can ship in Info.plist. For manual URLSessionDelegate trust work,
defer to the swift-security skill: correct SPKI pinning requires hashing
the Subject Public Key Info structure, not just the raw key bytes returned by
SecKeyCopyExternalRepresentation.
Important considerations:
Log outgoing requests and incoming responses for debugging. Disable or reduce verbosity in release builds.
struct LoggingMiddleware: RequestMiddleware {
let logger: Logger
func prepare(_ request: URLRequest) async throws -> URLRequest {
#if DEBUG
let method = request.httpMethod ?? "GET"
let url = request.url?.absoluteString ?? "unknown"
logger.debug("[\(method)] \(url)")
if let headers = request.allHTTPHeaderFields {
for (key, value) in headers where key != "Authorization" {
logger.debug(" \(key): \(value)")
}
}
if let body = request.httpBody, body.count < 10_000 {
logger.debug(" Body: \(String(data: body, encoding: .utf8) ?? "<binary>")")
}
#endif
return request
}
}To log responses, wrap the transport call rather than using middleware:
func loggedRequest<T: Decodable & Sendable>(
_ type: T.Type,
endpoint: Endpoint,
logger: Logger
) async throws -> T {
let start = ContinuousClock().now
do {
let result: T = try await request(type, endpoint: endpoint)
let elapsed = ContinuousClock().now - start
logger.debug("[\(endpoint.method.rawValue)] \(endpoint.path) -> 200 (\(elapsed))")
return result
} catch {
let elapsed = ContinuousClock().now - start
logger.error("[\(endpoint.method.rawValue)] \(endpoint.path) -> ERROR (\(elapsed)): \(error)")
throw error
}
}// 50 MB memory / 200 MB disk cache
let cache = URLCache(
memoryCapacity: 50 * 1024 * 1024,
diskCapacity: 200 * 1024 * 1024,
directory: FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
.first?.appendingPathComponent("URLCache")
)
let config = URLSessionConfiguration.default
config.urlCache = cache
config.requestCachePolicy = .returnCacheDataElseLoad
let session = URLSession(configuration: config)// Force fresh data
var request = URLRequest(url: url)
request.cachePolicy = .reloadIgnoringLocalCacheData
// Use cached if available
request.cachePolicy = .returnCacheDataElseLoad
// Cache only (offline mode)
request.cachePolicy = .returnCacheDataDontLoadfunc fetchWithETag<T: Decodable & Sendable>(
_ type: T.Type,
url: URL,
cachedETag: String?,
cachedData: Data?
) async throws -> (T, String?) {
var request = URLRequest(url: url)
if let etag = cachedETag {
request.setValue(etag, forHTTPHeaderField: "If-None-Match")
}
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse else {
throw NetworkError.invalidResponse
}
if http.statusCode == 304, let cachedData {
// Not modified -- use cached data
let decoded = try JSONDecoder().decode(T.self, from: cachedData)
return (decoded, cachedETag)
}
let newETag = http.value(forHTTPHeaderField: "ETag")
let decoded = try JSONDecoder().decode(T.self, from: data)
return (decoded, newETag)
}Use bytes(for:) to consume a streaming SSE endpoint.
struct ServerSentEvent: Sendable {
var event: String?
var data: String
var id: String?
}
func sseStream(from url: URL) -> AsyncThrowingStream<ServerSentEvent, Error> {
AsyncThrowingStream { continuation in
let task = Task {
do {
var request = URLRequest(url: url)
request.setValue("text/event-stream", forHTTPHeaderField: "Accept")
let (bytes, _) = try await URLSession.shared.bytes(for: request)
var currentEvent: String?
var currentData = ""
var currentId: String?
for try await line in bytes.lines {
if line.isEmpty {
// Empty line = dispatch event
if !currentData.isEmpty {
continuation.yield(ServerSentEvent(
event: currentEvent,
data: currentData.trimmingCharacters(in: .newlines),
id: currentId
))
}
currentEvent = nil
currentData = ""
currentId = nil
} else if line.hasPrefix("event:") {
currentEvent = String(line.dropFirst(6)).trimmingCharacters(in: .whitespaces)
} else if line.hasPrefix("data:") {
let value = String(line.dropFirst(5)).trimmingCharacters(in: .whitespaces)
currentData += currentData.isEmpty ? value : "\n" + value
} else if line.hasPrefix("id:") {
currentId = String(line.dropFirst(3)).trimmingCharacters(in: .whitespaces)
}
}
continuation.finish()
} catch {
continuation.finish(throwing: error)
}
}
continuation.onTermination = { _ in task.cancel() }
}
}Use a configured session for production clients instead of calling
URLSession.shared from request methods. Set explicit request/resource
timeouts, cache behavior, connectivity policy, and any delegates needed for
authentication challenges, redirects, metrics, pinning boundaries, or
background transfers before creating the URLSession.
enum SessionFactory {
static func makeDefault(delegate: (any URLSessionDelegate)? = nil) -> URLSession {
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 30
config.timeoutIntervalForResource = 300
config.waitsForConnectivity = true
config.httpMaximumConnectionsPerHost = 6
config.requestCachePolicy = .useProtocolCachePolicy
config.httpAdditionalHeaders = [
"Accept": "application/json",
"Accept-Encoding": "gzip, deflate, br",
]
let cache = URLCache(
memoryCapacity: 25 * 1024 * 1024,
diskCapacity: 100 * 1024 * 1024
)
config.urlCache = cache
return URLSession(
configuration: config,
delegate: delegate,
delegateQueue: nil
)
}
}.tessl-plugin
skills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-ettrace-performance
ios-localization
ios-memgraph-analysis
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
references
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
references
swift-charts
references
swift-codable
references
swift-concurrency
swift-formatstyle
references
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references