You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift用Alamofire调用POST API遇SSL错误(-1200)及解包崩溃求助

解决Swift+Alamofire集成POST注册API的核心问题

问题概述

在Swift项目中使用Alamofire调用POST注册API时,遇到两个关键问题:

  1. 控制台抛出SSL错误(错误码-1200),提示“无法与服务器建立安全连接”
  2. 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
    // 处理响应逻辑
}

生产环境解决方案:

  1. 获取服务器的根证书/中间证书(格式为.cer或.pem)
  2. 将证书添加到Xcode项目中,确保在Target的Build Phases->Copy Bundle Resources中包含该证书
  3. 配置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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 09:50:32