基于 SwiftUI 开发 iOS Ollama 客户端实践
介绍使用 Swift 和 SwiftUI 构建 iOS 客户端连接本地 Ollama 服务的方案。内容包括数据模型定义、流式 API 调用实现、会话管理及界面设计。通过开源项目演示如何在移动端安全访问私有化部署的大语言模型,满足敏感数据处理需求。

介绍使用 Swift 和 SwiftUI 构建 iOS 客户端连接本地 Ollama 服务的方案。内容包括数据模型定义、流式 API 调用实现、会话管理及界面设计。通过开源项目演示如何在移动端安全访问私有化部署的大语言模型,满足敏感数据处理需求。

因行业特殊性,许多敏感数据需要使用大模型进行处理和分析,无法使用公有云上的各种模型。因此,在公司内部部署了 Ollama 来跑本地模型解决问题。
此时面临一个问题:手机端需要访问 Ollama API,但市面上缺乏趁手的 App。为了解决这一痛点,决定使用 Swift + SwiftUI 开发一个原生客户端。
支持以下配置项:
为了提升用户体验,采用流式调用方式。每次调用只返回一部分结果,客户端不断读取并实时渲染,直到全部返回。
为了方便调用和解析 JSON,首先定义核心数据模型。
struct OllamaChatRequest: Codable {
var model: String = ""
var messages: [OllamaMessage] = []
var stream: Bool = true
}
struct OllamaChatResponse: Codable {
var message: OllamaMessage
}
struct OllamaChatStream: Codable {
var done: Bool
var message: OllamaMessage
}
struct OllamaMessage: Codable, Identifiable {
public static let ROLE_SYSTEM = "system"
public static let ROLE_ASSISTANT = "assistant"
public static let ROLE_USER = "user"
let id = UUID()
var role: String
var content: String
}
struct OllamaModelResponse: Codable {
var models: [OllamaModel]
}
struct OllamaModel: Identifiable, Codable {
let id = UUID()
var name: String
}
struct OllamaConfig: Codable {
public static let CONFIG_PATH = "config.json"
var url: String
var prompt: String
var model: String
var models: [OllamaModel]
}
iOS 端推荐使用 URLSession 配合 async/await 进行网络请求。针对流式输出,需要处理 SSE (Server-Sent Events) 格式的数据。
class OllamaNetworkService {
private let session: URLSession
init() {
self.session = URLSession(configuration: .default)
}
func chat(request: OllamaChatRequest, baseURL: String) async throws -> AsyncThrowingStream<String, Error> {
var components = URLComponents(string: "\(baseURL)/api/chat")!
guard let url = components.url else { throw URLError(.badURL) }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(request)
let (bytes, response) = try await session.bytes(for: request)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return bytes.lines.asyncThrowingMap { line in
// 解析 SSE 格式数据
if line.isEmpty || line == "\n" { return nil }
// 实际项目中需解析 JSON 提取 content 字段
return line
}.filter { $0 != nil }.compactMap { $0 }
}
}
在网络请求中应包含完善的错误捕获机制,包括网络超时、JSON 解析失败、API 返回错误码等情况。
使用 ObservableObject 管理状态,监听流式数据更新。
class ChatViewModel: ObservableObject {
@Published var messages: [OllamaMessage] = []
@Published var isLoading = false
func sendMessage(_ text: String) async {
isLoading = true
defer { isLoading = false }
let userMsg = OllamaMessage(role: .ROLE_USER, content: text)
messages.append(userMsg)
do {
let assistantMsg = OllamaMessage(role: .ROLE_ASSISTANT, content: "")
messages.append(assistantMsg)
let service = OllamaNetworkService()
let request = OllamaChatRequest(model: "llama2", messages: messages)
for try await chunk in try await service.chat(request: request, baseURL: "http://localhost:11434") {
assistantMsg.content += chunk
Task { @MainActor in
messages[messages.count - 1] = assistantMsg
}
}
} catch {
print(error.localizedDescription)
}
}
}
主界面采用 NavigationView 或 TabView 结构,左侧为会话列表,右侧为聊天内容区域。输入框固定在底部。
建议使用 CoreData 或 UserDefaults 存储会话历史。对于长文本,可考虑本地数据库存储;对于配置信息,可使用 Codable 序列化到文件。
本方案展示了如何使用 Swift 和 SwiftUI 快速构建连接本地 Ollama 服务的移动端应用。通过流式接口优化体验,结合合理的架构设计,实现了敏感数据的本地化处理需求。后续可进一步扩展多模态支持及更复杂的提示词工程功能。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online