用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/tools-only/X-Skills --skill mobile-ios-networking命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | mobile-ios-networking |
| description | Building network layers for iOS/macOS apps |
Use this skill when:
Build a thread-safe network service with actor:
actor NetworkService {
static let shared = NetworkService()
private let session: URLSession
private let baseURL: URL
init(
baseURL: URL = URL(string: "https://api.example.com")!,
session: URLSession = .shared
) {
self.baseURL = baseURL
self.session = session
}
func request<T: Decodable>(
_ endpoint: Endpoint,
as type: T.Type
) async throws -> T {
let request = try buildRequest(for: endpoint)
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.invalidResponse
}
guard (200...299).contains(httpResponse.statusCode) else {
throw NetworkError.httpError(httpResponse.statusCode)
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return try decoder.decode(T.self, from: data)
}
private func buildRequest(for endpoint: Endpoint) throws -> URLRequest {
let url = baseURL.appendingPathComponent(endpoint.path)
var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true)
urlComponents?.queryItems = endpoint.queryItems
guard let finalURL = urlComponents?.url else {
throw NetworkError.invalidURL
}
var request = URLRequest(url: finalURL)
request.httpMethod = endpoint.method.rawValue
request.allHTTPHeaderFields = endpoint.headers
if let body = endpoint.body {
request.httpBody = try JSONEncoder().encode(body)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
return request
}
}
enum NetworkError: LocalizedError {
case invalidURL
case invalidResponse
case httpError(Int)
case decodingError(Error)
var errorDescription: String? {
switch self {
case .invalidURL:
return "Invalid URL"
case .invalidResponse:
return "Invalid response from server"
case .httpError(let code):
return "HTTP error: \(code)"
case .decodingError(let error):
return "Decoding failed: \(error.localizedDescription)"
}
}
}
Define endpoints with type safety:
protocol Endpoint {
var path: String { get }
var method: HTTPMethod { get }
var headers: [String: String]? { get }
var queryItems: [URLQueryItem]? { get }
var body: Encodable? { get }
}
enum HTTPMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case delete = "DELETE"
case patch = "PATCH"
}
enum UserEndpoint: Endpoint {
case getUser(id: UUID)
case listUsers(page: Int, limit: Int)
case createUser(UserCreateRequest)
case updateUser(id: UUID, UserUpdateRequest)
case deleteUser(id: UUID)
path: {
{
.getUser( id):
.listUsers:
.createUser:
.updateUser( id, ):
.deleteUser( id):
}
}
method: {
{
.getUser, .listUsers:
.get
.createUser:
.post
.updateUser:
.put
.deleteUser:
.delete
}
}
headers: [ : ] {
[: ]
}
queryItems: [] {
{
.listUsers( page, limit):
[
(name: , value: ),
(name: , value: )
]
:
}
}
body: ? {
{
.createUser( request):
request
.updateUser(, request):
request
:
}
}
}
: {
name:
email:
}
: {
name: ?
email: ?
}
: {
id:
name:
email:
createdAt:
}
Implement JWT token management:
actor AuthenticationService {
static let shared = AuthenticationService()
private var accessToken: String?
private var refreshToken: String?
private var tokenExpirationDate: Date?
func setTokens(access: String, refresh: String, expiresIn: TimeInterval) {
self.accessToken = access
self.refreshToken = refresh
self.tokenExpirationDate = Date().addingTimeInterval(expiresIn)
saveTokensToKeychain()
}
func getValidAccessToken() async throws -> String {
// Check if token exists and is valid
if let token = accessToken,
let expiration = tokenExpirationDate,
expiration > Date().addingTimeInterval(60) { // 60s buffer
return token
}
// Try to refresh token
if let refresh refreshToken {
refreshAccessToken(using: refresh)
}
.notAuthenticated
}
( : ) -> {
url (string: )
request (url: url)
request.httpMethod
request.setValue(, forHTTPHeaderField: )
(data, response) .shared.data(for: request)
httpResponse response ,
httpResponse.statusCode {
.refreshFailed
}
tokenResponse ().decode(., from: data)
setTokens(
access: tokenResponse.accessToken,
refresh: tokenResponse.refreshToken,
expiresIn: tokenResponse.expiresIn
)
tokenResponse.accessToken
}
() {
accessToken
refreshToken
tokenExpirationDate
clearTokensFromKeychain()
}
() {
}
() {
}
}
: {
accessToken:
refreshToken:
expiresIn:
}
: {
notAuthenticated
refreshFailed
errorDescription: ? {
{
.notAuthenticated:
.refreshFailed:
}
}
}
Add authentication to network service:
extension NetworkService {
func authenticatedRequest<T: Decodable>(
_ endpoint: Endpoint,
as type: T.Type
) async throws -> T {
let token = try await AuthenticationService.shared.getValidAccessToken()
var request = try buildRequest(for: endpoint)
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.invalidResponse
}
// Handle 401 - token might be invalid
if httpResponse.statusCode == 401 {
await AuthenticationService.shared.logout()
throw AuthError.notAuthenticated
}
guard (200...).contains(httpResponse.statusCode) {
.httpError(httpResponse.statusCode)
}
().decode(., from: data)
}
}
Implement exponential backoff:
extension NetworkService {
func requestWithRetry<T: Decodable>(
_ endpoint: Endpoint,
as type: T.Type,
maxRetries: Int = 3
) async throws -> T {
var lastError: Error?
for attempt in 0..<maxRetries {
do {
return try await request(endpoint, as: type)
} catch {
lastError = error
// Don't retry client errors (4xx)
if case NetworkError.httpError(let code) = error,
(400...499).contains(code) {
throw error
}
// Wait before retrying (exponential backoff)
if attempt < maxRetries - 1 {
let delay = pow(2.0, Double(attempt))
.sleep(for: .seconds(delay))
}
}
}
lastError .invalidResponse
}
}
Map network errors to domain errors:
enum DomainError: LocalizedError {
case network(NetworkError)
case validation(String)
case notFound
case unauthorized
case serverError
init(from networkError: NetworkError) {
switch networkError {
case .httpError(let code):
switch code {
case 401:
self = .unauthorized
case 404:
self = .notFound
case 422:
self = .validation("Invalid input")
case 500...599:
self = .serverError
default:
self = .network(networkError)
}
default:
self = .network(networkError)
}
}
var errorDescription: String? {
switch self {
case .network(let error):
error.localizedDescription
.validation( message):
message
.notFound:
.unauthorized:
.serverError:
}
}
}
Build focused API clients:
actor UserAPIClient {
private let networkService: NetworkService
init(networkService: NetworkService = .shared) {
self.networkService = networkService
}
func getUser(id: UUID) async throws -> User {
let response = try await networkService.authenticatedRequest(
UserEndpoint.getUser(id: id),
as: UserResponse.self
)
return User(from: response)
}
func listUsers(page: Int = 1, limit: Int = 20) async throws -> [User] {
let response = try await networkService.authenticatedRequest(
UserEndpoint.listUsers(page: page, limit: limit),
as: UsersListResponse.self
)
return response.users.map { User(from: $0) }
}
func (: , : ) -> {
request (name: name, email: email)
response networkService.authenticatedRequest(
.createUser(request),
as: .
)
(from: response)
}
(: , : ?, : ?) -> {
request (name: name, email: email)
response networkService.authenticatedRequest(
.updateUser(id: id, request),
as: .
)
(from: response)
}
(: ) {
networkService.authenticatedRequest(
.deleteUser(id: id),
as: .
)
}
}
: {
users: []
total:
page:
}
: {}
: {
id:
name:
email:
createdAt:
( : ) {
.id response.id
.name response.name
.email response.email
.createdAt response.createdAt
}
}
Upload files with progress:
extension NetworkService {
func upload(
_ endpoint: Endpoint,
file: URL,
progressHandler: @escaping (Double) -> Void
) async throws -> Data {
var request = try buildRequest(for: endpoint)
let token = try await AuthenticationService.shared.getValidAccessToken()
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
let uploadTask = session.uploadTask(with: request, fromFile: file)
// Monitor progress
for await progress in uploadTask.progress.values {
progressHandler(progress.fractionCompleted)
}
return try await uploadTask.value.0
}
}
// Usage
@Observable
final class UploadViewModel {
var uploadProgress: Double = 0
func uploadFile(url: URL) {
endpoint .upload
data .shared.upload(
endpoint,
file: url,
progressHandler: { progress
{
.uploadProgress progress
}
}
)
}
}
Download large files:
extension NetworkService {
func download(
from url: URL,
to destination: URL,
progressHandler: @escaping (Double) -> Void
) async throws {
let downloadTask = session.downloadTask(with: url)
// Monitor progress
Task {
for await progress in downloadTask.progress.values {
progressHandler(progress.fractionCompleted)
}
}
let (localURL, _) = try await downloadTask.value
// Move to final destination
try FileManager.default.moveItem(at: localURL, to: destination)
}
}
Implement simple cache:
actor ResponseCache {
private var cache: [URL: CachedResponse] = [:]
private let maxAge: TimeInterval = 300 // 5 minutes
struct CachedResponse {
let data: Data
let timestamp: Date
var isExpired: Bool {
Date().timeIntervalSince(timestamp) > 300
}
}
func get(for url: URL) -> Data? {
guard let cached = cache[url], !cached.isExpired else {
return nil
}
return cached.data
}
func set(_ data: Data, for url: URL) {
cache[url] = CachedResponse(data: data, timestamp: Date())
}
func clear() {
cache.removeAll()
}
func () {
cache cache.filter { .value.isExpired }
}
}
{
<: >(
: ,
: .,
:
) -> {
request buildRequest(for: endpoint)
cachedData cache.get(for: request.url) {
().decode(., from: cachedData)
}
(data, ) session.data(for: request)
cache.set(data, for: request.url)
().decode(., from: data)
}
}
Create testable network layer:
protocol NetworkServiceProtocol {
func request<T: Decodable>(_ endpoint: Endpoint, as type: T.Type) async throws -> T
}
extension NetworkService: NetworkServiceProtocol {}
final class MockNetworkService: NetworkServiceProtocol {
var responseToReturn: Any?
var errorToThrow: Error?
func request<T: Decodable>(_ endpoint: Endpoint, as type: T.Type) async throws -> T {
if let error = errorToThrow {
throw error
}
guard let response = responseToReturn as? T else {
throw NetworkError.invalidResponse
}
response
}
}
() {
mockService ()
mockService.responseToReturn (
id: (),
name: ,
email: ,
createdAt: ()
)
client (networkService: mockService )
user client.getUser(id: ())
#expect(user.name )
}
DON'T make synchronous network calls:
// ❌ BAD - Blocks thread
let data = try Data(contentsOf: url)
// ✅ GOOD
let (data, _) = try await URLSession.shared.data(from: url)
DON'T ignore error responses:
// ❌ BAD
let (data, _) = try await session.data(for: request)
return try JSONDecoder().decode(T.self, from: data)
// ✅ GOOD
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw NetworkError.httpError((response as? HTTPURLResponse)?.statusCode ?? 0)
}
return try JSONDecoder().decode(T.self, from: data)
DON'T hardcode URLs:
// ❌ BAD
let url = URL(string: "https://api.example.com/users/\(id)")!
// ✅ GOOD
enum UserEndpoint: Endpoint {
case getUser(id: UUID)
var path: String { "/users/\(id)" }
}