iOS/macOS应用集成Gemini API:从云端调用到本地部署的完整实践指南

1. 项目概述:当Gemini遇上Apple生态

最近在开发者社区里,一个话题的热度持续攀升:如何将Google最新的Gemini大模型能力,无缝集成到基于Apple平台(iOS、iPadOS、macOS)的应用开发中。这不仅仅是“又一个AI模型”的接入,而是标志着移动与桌面应用开发范式的一次潜在变革。作为一名长期深耕Apple生态的开发者,我深切感受到,从Core ML到Create ML,苹果虽然提供了强大的本地机器学习框架,但在处理复杂、前沿的大语言模型任务时,我们往往需要借助云端能力。而Gemini系列模型,特别是其多模态和代码生成特性,为开发诸如智能编程助手、跨模态内容理解应用、实时交互式AI功能等场景,打开了新的想象空间。

这个“项目”的核心,就是解决一个关键问题:如何让Apple开发者能够高效、稳定、合规地在Xcode项目中调用Gemini API,构建下一代智能应用。它涉及从环境配置、API鉴权、网络请求封装,到模型选择、响应处理、错误调试乃至本地化部署考量等一系列实操环节。无论是想做一个能理解图片并生成描述的效率工具,还是一个能根据自然语言注释自动补全代码的Xcode扩展,Gemini都能提供强大的后端大脑。接下来,我将结合最近的实践,拆解从零开始将Gemini引入Apple开发工作流的完整路径,并分享其中踩过的坑和总结出的有效经验。

2. 核心思路与方案选型

在决定将Gemini集成到Apple项目时,首先需要明确技术路径。目前主要有三种主流方案,每种方案的选择都深刻影响着后续的开发体验、应用性能、成本以及最终的用户体验。

2.1 方案对比:云端API、本地部署与混合模式

2.1.1 云端API调用(推荐给大多数应用)这是最快速、最直接的入门方式。开发者通过Google AI Studio获取API密钥,然后在应用中通过HTTPS请求调用Gemini的RESTful端点。其最大优势是零运维负担:你无需关心模型加载、硬件资源、版本更新,Google会处理一切。同时,你总能访问到最新、能力最强的模型版本(如Gemini 1.5 Pro)。对于需要处理复杂多轮对话、长上下文或强大多模态理解的应用,这是目前唯一可行的选择。

然而,其缺点同样明显:强网络依赖持续调用成本。所有用户请求都需要往返于Google的服务器,这意味着离线功能无法实现,且网络延迟直接影响到应用的响应速度。此外,API调用按Token计费,对于高频次交互的应用,成本需要仔细核算。

2.1.2 本地模型部署(适用于特定高性能、隐私敏感场景)随着Gemini Nano模型的推出,本地部署成为了可能。Gemini Nano是专门为终端设备优化的轻量级模型,可以通过Core ML框架集成到App中。其最大优点是数据隐私离线可用:所有计算都在用户设备上完成,数据不出设备,完全符合某些地区严格的数据法规要求。同时,由于没有网络延迟,对于实时性要求极高的交互(如输入法预测、即时翻译)体验更佳。

但这条路挑战巨大。首先,模型能力受限:Nano的性能与Pro等云端版本有数量级差距,不适合复杂任务。其次,包体积膨胀:模型文件动辄几百MB,会显著增加App下载大小。最后,部署复杂度高:需要处理Core ML模型转换、设备性能适配(区分iPhone 15 Pro和旧款机型)、内存与功耗优化等一系列底层问题。

2.1.3 混合智能架构(平衡性能、成本与体验的未来方向)在实际项目中,我越来越倾向于采用混合架构。其核心思想是:根据任务复杂度、实时性要求和网络状况,动态选择执行路径。

  • 轻量级任务本地化:例如,文本的语法纠错、简单的情感分析、预设指令的响应,可以由设备端的Gemini Nano或更小的定制模型处理。
  • 复杂任务云端处理:当用户请求涉及图像深度分析、代码生成、复杂推理时,应用自动切换到调用云端Gemini API。
  • 智能缓存与预加载:对于可预测的用户行为(如打开文档编辑器时预加载代码补全模型),可以提前在后台静默下载所需资源或建立低功耗的云端连接。

这种架构设计复杂,但能最大程度地兼顾响应速度、用户隐私和功能强大。在方案选型时,务必回归产品本质:你的应用核心场景是什么?目标用户对网络和隐私的敏感度如何?长期运维成本预算多少?想清楚这些问题,才能做出最适合的技术决策。

2.2 工具链准备:Xcode、Swift与依赖管理

无论选择哪种方案,一套高效的开发工具链是基础。对于Apple平台,这几乎是不二之选:

  • Xcode:确保使用最新稳定版本。新版本通常对Swift并发特性(async/await)、Swift Package Manager(SPM)以及调试工具链有更好的支持,这对于处理异步网络请求和集成第三方库至关重要。
  • Swift语言:熟练掌握现代Swift语法,特别是Codable协议(用于JSON编解码)、URLSession(用于网络请求)和并发编程。Gemini API的交互本质上是异步的,优雅地处理回调是保证应用流畅的关键。
  • 依赖管理:强烈推荐使用Swift Package Manager (SPM)。它直接集成在Xcode中,无需额外工具,管理起来非常干净。你可以创建一个专门的NetworkServiceGeminiClient包,将所有的API交互逻辑封装其中,方便在主App和其他扩展(如Today Widget、Siri Intent)中复用。

注意:在集成任何第三方网络库(如Alamofire)之前,评估其必要性。对于单纯的REST API调用,URLSession已经足够强大且轻量。引入额外依赖会增加包体积和潜在的依赖冲突风险。

3. 实战:集成Gemini API到iOS/macOS应用

让我们从最常见的云端API集成开始,一步步构建一个健壮的Gemini服务层。我将以构建一个“智能笔记”应用为例,该应用能根据用户输入的图片和零星文字,自动生成结构化的笔记摘要。

3.1 获取与安全管理API密钥

第一步是前往Google AI Studio获取API密钥。这个过程很简单,但密钥的管理是安全的第一道防线,绝不能将密钥硬编码在源码中。

错误的做法:

// 绝对不要这样写! let apiKey = “YOUR_ACTUAL_API_KEY_HERE”

正确的安全实践:

  1. 使用Xcode配置(.xcconfig)文件
    • 在项目根目录创建Config.xcconfigConfig.debug.xcconfig(用于开发)、Config.release.xcconfig(用于发布)。
    • .xcconfig文件中定义变量:GEMINI_API_KEY = $(ENV_GEMINI_API_KEY)
    • 在Scheme的RunArchive配置中,分别设置环境变量ENV_GEMINI_API_KEY。开发时填入你的测试密钥,发布时使用CI/CD系统注入生产密钥。
  2. 在代码中安全读取
    import Foundation enum Config { static var geminiAPIKey: String { guard let key = Bundle.main.infoDictionary?[“GEMINI_API_KEY”] as? String, !key.isEmpty else { fatalError(“Gemini API Key is missing. Please check your .xcconfig files.”) } // 简单检查,防止误提交占位符 if key.hasPrefix(“$(ENV_”) || key == “YOUR_KEY_HERE” { fatalError(“Gemini API Key is not properly set.”) } return key } }
  3. 密钥使用与权限:在Google AI Studio中,可以为密钥设置应用限制(如只允许iOS/macOS App Bundle ID调用)和API限制(如只允许Gemini API访问)。务必遵循最小权限原则。

3.2 构建网络服务层

一个独立、可测试的网络服务层是良好架构的体现。我们将封装所有与Gemini API的交互。

import Foundation enum GeminiAPIError: Error, LocalizedError { case invalidURL case unauthorized case rateLimited case serverError(String) case decodingError case emptyResponse var errorDescription: String? { switch self { case .invalidURL: return “Invalid API endpoint.” case .unauthorized: return “API key is invalid or expired.” case .rateLimited: return “Too many requests. Please try again later.” case .serverError(let message): return “Server error: \(message)” case .decodingError: return “Failed to parse server response.” case .emptyResponse: return “Received an empty response from the server.” } } } struct GeminiRequest: Codable { let contents: [Content] let generationConfig: GenerationConfig? struct Content: Codable { let parts: [Part] let role: String? // “user” or “model” } struct Part: Codable { let text: String? let inlineData: InlineData? // 用于图片等二进制数据 struct InlineData: Codable { let mimeType: String // e.g., “image/jpeg” let data: String // Base64编码的字符串 } // 便捷初始化方法 init(text: String) { self.text = text self.inlineData = nil } init(imageData: Data, mimeType: String) { self.text = nil self.inlineData = InlineData(mimeType: mimeType, data: imageData.base64EncodedString()) } } struct GenerationConfig: Codable { let temperature: Double? let topP: Double? let topK: Int? let maxOutputTokens: Int? } } struct GeminiResponse: Codable { let candidates: [Candidate]? let usageMetadata: UsageMetadata? struct Candidate: Codable { let content: Content let finishReason: String? } struct UsageMetadata: Codable { let promptTokenCount: Int let candidatesTokenCount: Int let totalTokenCount: Int } // 便捷方法,提取第一个候选文本 var primaryText: String? { return candidates?.first?.content.parts?.first?.text } } actor GeminiService { private let apiKey: String private let baseURL = “https://generativelanguage.googleapis.com/v1beta” private let modelName: String // e.g., “gemini-1.5-pro-latest” private let urlSession: URLSession private let jsonDecoder: JSONDecoder init(apiKey: String, modelName: String = “gemini-1.5-flash-latest”) { self.apiKey = apiKey self.modelName = modelName self.urlSession = URLSession(configuration: .default) self.jsonDecoder = JSONDecoder() jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase } func generateContent(from request: GeminiRequest) async throws -> GeminiResponse { // 1. 构建URL let endpoint = “\(baseURL)/models/\(modelName):generateContent” guard var urlComponents = URLComponents(string: endpoint) else { throw GeminiAPIError.invalidURL } urlComponents.queryItems = [URLQueryItem(name: “key”, value: apiKey)] guard let url = urlComponents.url else { throw GeminiAPIError.invalidURL } // 2. 构建请求 var urlRequest = URLRequest(url: url) urlRequest.httpMethod = “POST” urlRequest.setValue(“application/json”, forHTTPHeaderField: “Content-Type”) do { urlRequest.httpBody = try JSONEncoder().encode(request) } catch { throw GeminiAPIError.decodingError } // 3. 发起网络请求 let (data, response) = try await urlSession.data(for: urlRequest) // 4. 处理HTTP状态码 guard let httpResponse = response as? HTTPURLResponse else { throw GeminiAPIError.serverError(“Invalid response type”) } switch httpResponse.statusCode { case 200: break // 成功 case 401: throw GeminiAPIError.unauthorized case 429: throw GeminiAPIError.rateLimited case 400…499: // 尝试解析错误信息 if let errorJson = try? JSONDecoder().decode([String: String].self, from: data), let message = errorJson[“error”]?[“message”] { throw GeminiAPIError.serverError(message) } else { throw GeminiAPIError.serverError(“Client error: \(httpResponse.statusCode)”) } case 500…599: throw GeminiAPIError.serverError(“Server error: \(httpResponse.statusCode)”) default: throw GeminiAPIError.serverError(“Unexpected status code: \(httpResponse.statusCode)”) } // 5. 解析响应数据 do { let geminiResponse = try jsonDecoder.decode(GeminiResponse.self, from: data) if geminiResponse.candidates?.isEmpty != false { throw GeminiAPIError.emptyResponse } return geminiResponse } catch { // 这里可以打印出原始响应数据,便于调试 // print(“Raw response: \(String(data: data, encoding: .utf8) ?? “”)“) throw GeminiAPIError.decodingError } } }

这个GeminiService类使用了Swift的actor来确保在多线程环境下的安全访问。它封装了完整的请求构建、发送、状态码处理和响应解析逻辑,并提供了清晰的错误类型。

3.3 实现多模态内容生成

现在,我们利用上面构建的服务层,来实现“智能笔记”应用的核心功能:上传图片,附带一些文字提示,让Gemini生成摘要。

import SwiftUI import PhotosUI // 用于图片选择 class NoteGeneratorViewModel: ObservableObject { @Published var generatedText: String = “” @Published var isGenerating: Bool = false @Published var errorMessage: String? private let geminiService: GeminiService init() { // 从安全配置中读取API Key self.geminiService = GeminiService(apiKey: Config.geminiAPIKey, modelName: “gemini-1.5-flash-latest”) } func generateNote(from imageData: Data?, userPrompt: String) async { // 切换到主线程更新UI状态 await MainActor.run { isGenerating = true errorMessage = nil generatedText = “” } do { var parts = [GeminiRequest.Part]() // 1. 添加图片部分(如果存在) if let imageData = imageData { // 注意:Gemini API对图片有大小和格式限制,通常需要压缩 let compressedImageData = compressImageIfNeeded(imageData) let imagePart = GeminiRequest.Part(imageData: compressedImageData, mimeType: “image/jpeg”) parts.append(imagePart) } // 2. 添加文本提示部分 let prompt = “”” 你是一个专业的笔记助手。请根据提供的图片和用户描述,生成一份清晰、有条理的Markdown格式笔记摘要。 用户描述:\(userPrompt) 请专注于提取图片中的关键信息、文字、图表数据,并结合用户描述进行总结。如果图片不相关,请主要依据用户描述。 “”” parts.append(GeminiRequest.Part(text: prompt)) // 3. 构建请求 let content = GeminiRequest.Content(parts: parts, role: “user”) let request = GeminiRequest( contents: [content], generationConfig: GeminiRequest.GenerationConfig( temperature: 0.7, // 创造性,0.1-1.0之间 maxOutputTokens: 1000 // 控制输出长度 ) ) // 4. 调用API let response = try await geminiService.generateContent(from: request) // 5. 处理成功响应 await MainActor.run { if let note = response.primaryText { generatedText = note } else { errorMessage = “生成失败,未获得有效文本。” } isGenerating = false } } catch { // 6. 处理错误 await MainActor.run { isGenerating = false if let geminiError = error as? GeminiAPIError { errorMessage = geminiError.errorDescription } else { errorMessage = “发生未知错误:\(error.localizedDescription)” } // 在实际应用中,这里可以记录日志或上报错误分析平台 print(“Generation failed: \(error)”) } } } private func compressImageIfNeeded(_ data: Data, maxSizeKB: Int = 1024) -> Data { // 简单的图片压缩逻辑,实际项目中可能需要更复杂的处理 guard data.count > maxSizeKB * 1024 else { return data } #if os(iOS) guard let image = UIImage(data: data) else { return data } return image.jpegData(compressionQuality: 0.5) ?? data #elseif os(macOS) guard let image = NSImage(data: data) else { return data } // macOS下的压缩逻辑略复杂,此处省略,可用`NSBitmapImageRep` return data #endif } }

在SwiftUI视图中,我们可以这样使用这个ViewModel:

struct ContentView: View { @StateObject private var viewModel = NoteGeneratorViewModel() @State private var selectedImage: UIImage? // iOS示例 @State private var promptText: String = “” @State private var showingImagePicker = false var body: some View { VStack(spacing: 20) { // 图片预览区域 if let image = selectedImage { Image(uiImage: image) .resizable() .scaledToFit() .frame(height: 200) .cornerRadius(8) } // 文字输入区域 TextField(“请输入你的笔记想法或对图片的描述…”, text: $promptText, axis: .vertical) .textFieldStyle(.roundedBorder) .lineLimit(3…6) // 操作按钮 HStack { Button(“选择图片”) { showingImagePicker = true } .buttonStyle(.bordered) Button(action: { Task { let imageData = selectedImage?.jpegData(compressionQuality: 0.8) await viewModel.generateNote(from: imageData, userPrompt: promptText) } }) { if viewModel.isGenerating { ProgressView() .scaleEffect(0.8) } else { Text(“生成智能笔记”) } } .buttonStyle(.borderedProminent) .disabled(viewModel.isGenerating || promptText.isEmpty) } // 结果显示区域 if let error = viewModel.errorMessage { Text(“错误: \(error)“) .foregroundColor(.red) .font(.caption) } ScrollView { Text(viewModel.generatedText) .textSelection(.enabled) // 允许用户复制生成的文本 .frame(maxWidth: .infinity, alignment: .leading) .padding() } .background(Color.gray.opacity(0.1)) .cornerRadius(8) } .padding() .sheet(isPresented: $showingImagePicker) { // 集成PHPickerViewController (iOS) 或 NSOpenPanel (macOS) // 此处省略具体实现 } } }

这个完整的例子展示了从UI交互到调用Gemini API,再到结果展示的闭环。关键在于generateNote方法中提示词(Prompt)的构建。好的提示词是获得高质量输出的前提。我通常遵循“角色定义 + 任务描述 + 输入格式 + 输出要求”的结构来编写。

4. 高级话题与性能优化

当基础集成完成后,要打造一个真正可用的产品,还需要解决一系列高级问题。

4.1 流式响应与实时体验

对于需要长时间生成文本的场景(如编写长篇文章、代码),等待整个响应完成再展示给用户的体验是灾难性的。Gemini API支持流式响应(Streaming),可以让结果像打字机一样逐字返回。

实现流式响应的核心是使用URLSessionbytes方法,并处理Server-Sent Events (SSE)或分块传输编码。你需要解析类似data: {…}这样的流式数据块。虽然实现起来比普通请求复杂,但它能极大提升用户体验。一个简单的处理框架如下:

func generateContentStreaming(from request: GeminiRequest) async throws -> AsyncThrowingStream<String, Error> { // … 构建URLRequest(与普通请求类似,但可能需要添加stream参数)… // urlRequest.setValue(“text/event-stream”, forHTTPHeaderField: “Accept”) // 有时需要 let (bytes, response) = try await urlSession.bytes(for: urlRequest) // … 检查HTTP状态码 … return AsyncThrowingStream { continuation in Task { do { for try await line in bytes.lines { // 解析SSE格式的行,提取`data`字段 if line.hasPrefix(“data: “) { let jsonString = String(line.dropFirst(6)) // 去掉”data: ” if jsonString == “[DONE]” { break } guard let data = jsonString.data(using: .utf8) else { continue } // 解析JSON,提取增量文本 if let chunk = try? JSONDecoder().decode(StreamingChunk.self, from: data), let text = chunk.candidates?.first?.content?.parts?.first?.text { continuation.yield(text) } } } continuation.finish() } catch { continuation.finish(throwing: error) } } } }

在UI层,你可以使用.task修饰符来消费这个流,并实时更新@State变量,实现打字机效果。

4.2 上下文管理与对话记忆

Gemini的API是无状态的。这意味着每次请求都是独立的,模型不会自动记住之前的对话。要实现多轮对话(聊天机器人),必须在客户端维护对话历史,并在每次请求时将完整的历史记录(或最近N轮)作为contents数组发送。

class ConversationManager { private var history: [GeminiRequest.Content] = [] private let maxHistoryTurns: Int init(maxHistoryTurns: Int = 10) { self.maxHistoryTurns = maxHistoryTurns } func addUserMessage(_ text: String) { history.append(GeminiRequest.Content(parts: [GeminiRequest.Part(text: text)], role: “user”)) trimHistory() } func addModelResponse(_ text: String) { history.append(GeminiRequest.Content(parts: [GeminiRequest.Part(text: text)], role: “model”)) trimHistory() } func getCurrentContext() -> [GeminiRequest.Content] { return history } func clear() { history.removeAll() } private func trimHistory() { // 简单的策略:保留最近N轮对话,或根据总Token数进行裁剪 // 注意:Gemini API有上下文长度限制(如1M tokens),需要计算 if history.count > maxHistoryTurns * 2 { // 每轮包含user和model history.removeFirst(2) // 移除最老的一轮对话 } } }

更高级的策略还需要估算每次交互的Token消耗,因为上下文越长,API调用成本越高,且可能触及模型的最大上下文窗口限制。

4.3 本地化与成本控制

对于全球发布的应用,直接让Gemini API返回用户本地语言的结果是最佳体验。这可以通过在系统提示词(System Instruction)或用户消息中明确指定语言来实现。例如,在请求的generationConfig中设置,或在提示词开头加入“请使用中文回答”。

成本控制是商业应用必须考虑的。除了选择性价比更高的模型(如gemini-1.5-flash),还需要:

  1. 实施缓存:对相同或相似的请求结果进行缓存,特别是那些不常变动的信息查询。
  2. 设置用量限制:在应用层面为免费用户或不同套餐等级的用户设置每日/每月调用次数上限。
  3. 监控与告警:集成像Google Cloud Monitoring这样的服务,监控API用量和费用,设置预算告警。
  4. 优化提示词:精确、简洁的提示词能减少不必要的Token消耗,同时可能获得更准确的回答,减少重试。

5. 常见问题与调试技巧

在实际集成过程中,你几乎一定会遇到下面这些问题。这里是我总结的排查清单和解决方法。

5.1 网络请求失败与错误处理

问题现象可能原因排查步骤与解决方案
错误码 401 (Unauthorized)API密钥无效、过期或未启用。1. 检查密钥字符串是否正确,有无多余空格。
2. 登录Google AI Studio,确认密钥状态为“启用”。
3. 检查密钥的“应用程序限制”和“API限制”是否过于严格,阻止了你的App。
错误码 429 (Too Many Requests)达到速率限制或配额不足。1. 检查Google AI Studio中的“配额”页面,查看调用次数和Token用量是否超限。
2. 在代码中实现请求重试机制(带指数退避)。例如,遇到429错误后等待2秒、4秒、8秒再重试。
3. 对于生产环境,考虑申请更高的配额。
错误码 400 (Bad Request)请求格式错误。1.最常用:检查请求体JSON格式。特别是多模态请求中,图片数据是否已正确Base64编码,mimeType是否正确。
2. 检查模型名称是否正确(如gemini-1.5-pro-latest)。
3. 提示词或上下文长度是否超过了模型限制。
长时间无响应或超时网络连接问题,或模型处理复杂请求时间过长。1. 为URLSession配置合理的timeoutIntervalForRequest(例如60秒)。
2. 对于复杂任务,考虑在UI上提供“取消”按钮,并调用URLSessionTaskcancel()方法。
3. 在后台线程执行请求,避免阻塞主线程导致UI卡死。
响应解析失败响应格式与Codable模型不匹配,或API返回了非JSON错误信息。1. 在catch块中打印出原始的data字符串(String(data: data, encoding: .utf8)),这是最有效的调试手段。
2. 对比打印出的JSON与你定义的GeminiResponse结构体是否一致。API版本更新可能导致字段变化。
3. 使用JSONDecoderkeyDecodingStrategy = .convertFromSnakeCase来处理下划线命名。

5.2 提示词工程与输出质量优化

模型输出不理想,很多时候问题不在代码,而在提示词。

  • 问题:输出过于简短或笼统。
    • 解决:在提示词中明确要求“详细阐述”、“分点说明”、“提供示例”。指定输出格式,如“请用Markdown列表形式输出”。
  • 问题:输出偏离主题或包含无关信息。
    • 解决:在提示词开头强化角色设定(“你是一个专业的iOS开发助手”),并严格限定回答范围(“请只回答与SwiftUI相关的问题”)。
  • 问题:处理多模态输入时,模型忽略了图片或文本某一部分。
    • 解决:在提示词中明确指示模型关注所有输入部分。例如:“请先描述图片中的主要内容,然后结合用户提供的文本‘这是一张关于…的图表’,分析其中的趋势。”
  • 调试技巧:在开发阶段,将构建好的完整提示词(包括图片的Base64前缀)复制到Google AI Studio的Web界面中进行测试,可以快速验证提示词的有效性,并直观调整参数(如Temperature)。

5.3 Xcode项目配置与上架注意事项

  • 网络权限:确保在Info.plist中正确配置了ATS(App Transport Security)。如果只使用Google的标准API域名(generativelanguage.googleapis.com),通常无需额外设置。但若需要更宽松的策略(不推荐),需添加对应例外。
  • 后台任务:如果应用需要在后台进行长时间的AI处理(如下载模型、处理队列任务),需要配置Background Modes能力,并妥善管理后台任务会话。
  • 隐私清单:随着Apple对隐私要求的加强,如果应用集成了第三方API,可能需要在新版Xcode中填写隐私清单,说明数据收集和使用情况。明确声明你使用了Gemini API,并链接到Google的隐私政策。
  • App Store审核:确保应用生成的AI内容有适当的过滤和审查机制,避免产生有害、歧视性或侵犯版权的内容。在App Store的审核备注中,可以简要说明AI功能的实现方式和内容安全措施。

将Gemini这样的强大模型引入Apple开发,不再是遥不可及的概念验证。通过清晰的架构设计、安全的代码实践和对细节的深入把控,我们可以构建出既智能又可靠的下一代应用。关键在于理解每种技术路径的取舍,并始终将用户体验和数据隐私放在核心位置。从一个小功能点开始尝试,逐步迭代,你会发现AI能力正在成为你应用中最具竞争力的亮点。