iOS 网络、安全与编译优化面经
iOS 网络与安全深度
1. URLSession 深入
三种任务类型
let session = URLSession(configuration: .default)
// DataTask:内存中传输(小数据)
let (data, response) = try await session.data(for: request)
// DownloadTask:内容写入文件(大文件下载)
let (fileURL, _) = try await session.download(for: request)
// UploadTask:上传文件
let (data, _) = try await session.upload(for: request, from: fileData)
// WebSocket
let task = session.webSocketTask(with: URL(string: "wss://echo.test")!)
task.resume()
try await task.send(.string("Hello"))
let message = try await task.receive()
URLSession 配置
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 30
config.timeoutIntervalForResource = 300
config.requestCachePolicy = .returnCacheDataElseLoad
config.httpMaximumConnectionsPerHost = 6
config.waitsForConnectivity = true // 无网络时等待而非立即失败
let session = URLSession(configuration: config, delegate: myDelegate, delegateQueue: nil)
缓存策略
// URLCache 配置
URLCache.shared = URLCache(
memoryCapacity: 20 * 1024 * 1024, // 20MB 内存
diskCapacity: 100 * 1024 * 1024, // 100MB 磁盘
directory: nil
)
// 请求级缓存控制
var request = URLRequest(url: url)
request.cachePolicy = .returnCacheDataElseLoad // 有缓存就用缓存
2. App Transport Security (ATS)
<!-- Info.plist 常见配置 -->
<key>NSAppTransportSecurity</key>
<dict>
<!-- 允许特定域名 HTTP -->
<key>NSExceptionDomains</key>
<dict>
<key>api.example.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.2</string>
</dict>
</dict>
</dict>
3. 证书锁定(SSL Pinning)
// URLSession delegate 实现证书锁定
class PinningDelegate: NSObject, URLSessionDelegate {
func urlSession(
_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
let serverTrust = challenge.protectionSpace.serverTrust else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
// 比较公鑰
let serverCert = SecTrustGetCertificateAtIndex(serverTrust, 0)!
let serverKey = SecCertificateCopyKey(serverCert)!
let serverKeyData = SecKeyCopyExternalRepresentation(serverKey, nil)! as Data
if serverKeyData == expectedPublicKey {
completionHandler(.useCredential, URLCredential(trust: serverTrust))
} else {
completionHandler(.cancelAuthenticationChallenge, nil)
}
}
}
4. 安全存储
// Keychain 存储敬感信息
func save(token: String, for account: String) throws {
let data = token.data(using: .utf8)!
let query: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account,
kSecValueData: data,
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked // 解锁后才可访问
]
SecItemDelete(query as CFDictionary) // 先删除旧值
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else { throw KeychainError.saveFailed }
}
func read(for account: String) throws -> String {
let query: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account,
kSecReturnData: true,
kSecMatchLimit: kSecMatchLimitOne
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status == errSecSuccess, let data = result as? Data else { throw KeychainError.readFailed }
return String(data: data, encoding: .utf8)!
}
5. 安全威胁与防御
逆向工程防御
// 1. 判断是否运行在模拟器上
func isRunningOnSimulator() -> Bool {
#if targetEnvironment(simulator)
return true
#else
return false
#endif
}
// 2. 判断是否进行了 Jailbreak
func isJailbroken() -> Bool {
let paths = ["/Applications/Cydia.app", "/usr/sbin/sshd", "/etc/apt"]
return paths.contains { FileManager.default.fileExists(atPath: $0) }
}
// 3. 代码混淆:使用 Swift/C 加密关键逻辑
// 4. 字符串加密:避免明文存在于二进制
数据保护
// CryptoKit 加解密
import CryptoKit
func encrypt(data: Data, key: SymmetricKey) throws -> Data {
let sealedBox = try AES.GCM.seal(data, using: key)
return sealedBox.combined!
}
func decrypt(data: Data, key: SymmetricKey) throws -> Data {
let sealedBox = try AES.GCM.SealedBox(combined: data)
return try AES.GCM.open(sealedBox, using: key)
}
// 安全生成密鑰
let key = SymmetricKey(size: .bits256) // 256位 AES 密鑰
6. 编译优化技巧
// 1. 全模块优化(WMO)
// Build Settings -> Whole Module Optimization -> Yes
// 编译器看到全模块代码,可内联跨文件函数
// 2. @inline(__always) 强制内联
@inline(__always)
func criticalPath() -> Int { /* ... */ }
// 3. final 关键字避免动态分发
final class UserService { /* ... */ }
// 4. private/fileprivate 提供优化提示
// 局部小的方法更容易被内联
// 5. Existential 类型 vs Generic 类型
// 下面的写法会有装箱开销
// bad: func process(animal: any Animal) { ... }
// good: func process<T: Animal>(animal: T) { ... }
7. 资深面试题
- HTTPS 中证书屏蔽和证书锁定的区别?
- 证书屏蔽:浏览器内置的屏蔽已到期或不受信任证书的机制
- 证书锁定:应用主动将公鑰打入到应用中,与服务器公鑰对比
- 锁定更严格、主动防证书替换攻击
- Keychain 在 App 删除后是否也删除?
- 默认不删除,重新安装后仍可访问
- 要清理需要在 App 首次启动时主动判断和清除
- App 如何防止投屏时泏露敲感信息?
UITextField.isSecureTextEntry = true- App 进入后台时主动覆盖或模糊番幕
- 什么是 App Attest?
- Apple 提供的设备证明服务,验证请求来自真实未篡改的 App
- 防止服务器上的 API 被未授权的客户端调用