Swift用Alamofire调用POST API遇SSL错误(-1200)及解包崩溃求助
解决Swift+Alamofire集成POST注册API的核心问题
问题概述
在Swift项目中使用Alamofire调用POST注册API时,遇到两个关键问题:
- 控制台抛出SSL错误(错误码-1200),提示“无法与服务器建立安全连接”
- API Manager代码的do块中因强制解包
data!,触发“Unexpectedly found nil while unwrapping an Optional value”致命崩溃
完整错误日志
[Request]: POST https://b2c-api.digital.com/customerservice/customers/createCustomer [Headers]: Content-Type: application/json [Body]: {"email":"ashu@gmail.com","lastname":"Tosh","password":"Ashu123!!","firstname":"Ashu"} [Response]: None [Network Duration]: 0.41680002212524414s [Serialization Duration]: 0.0s [Result]: failure(Alamofire.AFError.sessionTaskFailed(error: Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, NSErrorPeerCertificateChainKey=( "<cert(0x7f87d8827c00) s: b2c-api.tadigital.com i: ca.oss.de.goskope.com>", "<cert(0x7f87d8823800) s: ca.oss.de.goskope.com i: caadmin.netskope.com>", "<cert(0x7f87d8897200) s: caadmin.netskope.com i: caadmin.netskope.com>" ), NSErrorClientCertificateStateKey=0, NSErrorFailingURLKey=https://b2c-api.tadigital.com/customerservice/customers/createCustomer, NSErrorFailingURLStringKey=https://b2c-api.tadigital.com/customerservice/customers/createCustomer, NSUnderlyingError=0x600000dbe250 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x6000032b00a0>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=( "<cert(0x7f87d8827c00) s: b2c-api.tadigital.com i: ca.oss.de.goskope.com>", "<cert(0x7f87d8823800) s: ca.oss.de.goskope.com i: caadmin.netskope.com>", "<cert(0x7f87d8897200) s: caadmin.netskope.com i: caadmin.netskope.com>" )}}, _NSURLErrorRelatedURLSessionTaskErrorKey=( "LocalDataTask <CEAA8DFD-FC6B-4C49-B906-1CD9AAB11F62>.<1>" ), _kCFStreamErrorCodeKey=-9802, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <CEAA8DFD-FC6B-4C49-B906-1CD9AAB11F62>.<1>, NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x6000032b00a0>, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made.})) URLSessionTask failed with error: An SSL error has occurred and a secure connection to the server cannot be made.
项目代码
1. Model
import Foundation struct RegisterModel: Encodable { let email: String let firstname: String let lastname: String let password: String }
2. View
import SwiftUI struct SignupFormView: View { @StateObject private var formModel = FormViewModel() var body: some View { ZStack { VStack { LogoImage() PrimarytextStyle(title: "Create an Account") Group{ TextField("Email address", text: $formModel.textEmail, onEditingChanged: { formModel.handleEditChanged($0, formModel: formModel) }) if !formModel.isEmailValid { InvalidEmailView() } TextField("First name", text: $formModel.firstname) .padding(.top) .padding(.bottom) TextField("Last name", text: $formModel.lastname) .padding(.bottom) TextField("Mobile Phone number (optional)", text: $formModel.phone) .padding(.bottom) SecureField("Create Password", text: $formModel.Password) Text(formModel.passwordPrompt) .foregroundColor(.red) .font(.system(size: 10)) .frame(maxWidth: .infinity, alignment: .leading) SecureField("Confirm Password", text: $formModel.ConfirmPassword) Text(formModel.confirmPwPrompt) .foregroundColor(.red) .font(.system(size: 10)) .frame(maxWidth: .infinity, alignment: .leading) } .textFieldStyle(.roundedBorder) .frame(width: 360) .autocapitalization(.none) Button { if formModel.isEmailValid && formModel.isPasswordCriteriaValid && formModel.isConfirmPasswordValid { let register = RegisterModel(email: formModel.textEmail, firstname: formModel.firstname, lastname: formModel.lastname, password: formModel.Password) APIManager.shareInstance.callingRegisterAPI(register: register) } } label: { PrimaryButton(title: "CREATE AN ACCOUNT", bgcolor: Color.blue, textcolor: Color.white) } .opacity(formModel.canCreateAccount ? 1 : 0.6) .disabled(!formModel.canCreateAccount) NavigationLink(destination: LoginView(), label: { SecondaryButton(title: "Or Sign-In") }) .navigationBarHidden(true) .navigationBarBackButtonHidden(true) } .padding(.bottom, 120) } } struct SignupFormView_Previews: PreviewProvider { static var previews: some View { SignupFormView() } }
3. ViewModel
import Foundation import Combine class FormViewModel: ObservableObject { @Published var Password = "" @Published var ConfirmPassword = "" @Published var firstname = "" @Published var lastname = "" @Published var phone = "" @Published var isPasswordCriteriaValid = true @Published var isConfirmPasswordValid = true @Published var canCreateAccount = true private var cancellableSet: Set<AnyCancellable> = [] @Published var emailString : String = "" @Published var textEmail : String = "" @Published var isEmailValid : Bool = true func textFieldValidatorEmail(_ string: String) -> Bool { if string.count > 100 { return false } let emailFormat = "(?:[\\p{L}0-9!#$%\\&'*+/=?\\^_`{|}~-]+(?:\\.[\\p{L}0-9!#$%\\&'*+/=?\\^_`{|}" + "~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\" + "x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[\\p{L}0-9](?:[a-" + "z0-9-]*[\\p{L}0-9])?\\.)+[\\p{L}0-9](?:[\\p{L}0-9-]*[\\p{L}0-9])?|\\[(?:(?:25[0-5" + "]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-" + "9][0-9]?|[\\p{L}0-9-]*[\\p{L}0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21" + "-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])" let emailPredicate = NSPredicate(format:"SELF MATCHES %@", emailFormat) return emailPredicate.evaluate(with: string) } let passwordPredicate = NSPredicate(format: "SELF MATCHES %@", "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,$}") init() { $Password .map { Password in return self.passwordPredicate.evaluate(with: Password) } .assign(to: \.isPasswordCriteriaValid, on: self) .store(in: &cancellableSet) Publishers.CombineLatest($Password , $ConfirmPassword) .map { Password , ConfirmPassword in return Password == ConfirmPassword } .assign(to: \.isConfirmPasswordValid, on: self) .store(in: &cancellableSet) Publishers.CombineLatest3($isEmailValid , $isPasswordCriteriaValid, $isConfirmPasswordValid) .map { isEmailValid , isPasswordCriteriaValid, isConfirmPasswordValid in return ( isEmailValid && isPasswordCriteriaValid && isConfirmPasswordValid) } .assign(to: \.canCreateAccount, on: self) .store(in: &cancellableSet) } var confirmPwPrompt: String { isConfirmPasswordValid ? "" : "Password does not match" } var passwordPrompt: String { isPasswordCriteriaValid ? "" : "Strong Password required" } func createAccount() { print("Creating an account") textEmail = "" Password = "" ConfirmPassword = "" firstname = "" lastname = "" phone = "" } func handleEditChanged(_ isChanged: Bool, formModel: FormViewModel) { if !isChanged { if formModel.textFieldValidatorEmail(formModel.textEmail) { formModel.isEmailValid = true } else { formModel.isEmailValid = false formModel.textEmail = "" } } } }
4. API Manager
import Foundation import Alamofire class APIManager { static let shareInstance = APIManager() func callingRegisterAPI(register: RegisterModel) { let headers: HTTPHeaders = [ .contentType("application/json") ] AF.request("https://b2c-api.digital.com/customerservice/customers/createCustomer", method: .post, parameters: register, encoder: JSONParameterEncoder.default, headers: headers).response { response in debugPrint(response) switch response.result { case .success(let data): do { let json = try JSONSerialization.jsonObject(with: data!, options: []) print(json) } catch { print("JSON解析失败:\(error.localizedDescription)") } case .failure(let err): print(err.localizedDescription) } } } }
解决方案
1. 修复SSL连接错误(错误码-1200)
原因:
服务器SSL证书未被iOS系统信任,可能是自签名证书、中间证书缺失或证书链不完整导致。
开发环境临时解决方案(仅用于测试,禁止上线):
在Alamofire请求中添加serverTrustManager,禁用SSL验证:
// 创建信任管理器,允许所有证书(仅开发用) let trustManager = ServerTrustManager(evaluators: ["b2c-api.digital.com": DisabledTrustEvaluator()]) // 创建会话配置 let session = Session(serverTrustManager: trustManager) // 使用自定义会话发起请求 session.request("https://b2c-api.digital.com/customerservice/customers/createCustomer", method: .post, parameters: register, encoder: JSONParameterEncoder.default, headers: headers).response { response in // 处理响应逻辑 }
生产环境解决方案:
- 获取服务器的根证书/中间证书(格式为
.cer或.pem) - 将证书添加到Xcode项目中,确保在
Target的Build Phases->Copy Bundle Resources中包含该证书 - 配置Alamofire的
ServerTrustManager,仅信任指定证书:
// 加载本地证书 guard let certPath = Bundle.main.path(forResource: "ca.oss.de.goskope", ofType: "cer"), let certData = try? Data(contentsOf: URL(fileURLWithPath: certPath)), let certificate = SecCertificateCreateWithData(nil, certData as CFData) else { fatalError("证书加载失败") } // 创建信任策略 let evaluator = PinnedCertificatesTrustEvaluator(certificates: [certificate], acceptSelfSignedCertificates: false, performDefaultValidation: true, validateHost: true) let trustManager = ServerTrustManager(evaluators: ["b2c-api.digital.com": evaluator]) let session = Session(serverTrustManager: trustManager) // 使用该会话发起请求
2. 修复强制解包data!导致的致命崩溃
原因:
当请求失败时(比如SSL错误),response.result.success不会触发,但如果请求成功但服务器未返回数据,data仍可能为nil,强制解包会直接崩溃。
解决方案:
使用可选绑定安全处理data,替换data!为if let或guard let:
case .success(let data): guard let responseData = data else { print("服务器未返回数据") return } do { let json = try JSONSerialization.jsonObject(with: responseData, options: []) print(json) } catch { print("JSON解析失败:\(error.localizedDescription)") }
内容的提问来源于stack exchange,提问作者Ashutosh Mukherjee
相关产品推荐
相关产品推荐

