如何使用在 swiftui 中存储为 UserDefaults 的环境变量创建 http 连接?

How do I create a http connection using environment variables that are stored as UserDefaults in swiftui?

我是 swift/swiftui 编程新手。

我有两个用户通过 TextFields 输入的环境变量。基于这两个文本字段,我必须初始化我的客户端。我使用 UserDefaults 而不是 EnvironmentObject,因为这两个字段可以被视为用户设置。

这是用户首选项Class

class UserPreferences: ObservableObject {
    @Published var accessKey: String {
        didSet {
            UserDefaults.standard.set(accessKey, forKey: "accessKey")
        }
    }
    
    @Published var secretKey: String {
        didSet {
            UserDefaults.standard.set(secretKey, forKey: "secretKey")
        }
    }
    
    @Published var region: String {
        didSet {
            UserDefaults.standard.set(region, forKey: "region")
        }
    }
    
    var regions = ["us-east-1", "us-east-2"]
            
    init() {
        self.accessKey = UserDefaults.standard.object(forKey: "accessKey") as? String ?? ""
        self.secretKey = UserDefaults.standard.object(forKey: "secretKey") as? String ?? ""
        self.region = UserDefaults.standard.object(forKey: "region") as? String ?? "us-east-1"
    }
    
}

根据用户的accessKey和secretKey,我需要创建一个客户端。我尝试将此客户端作为用户首选项中的附加变量 class.

var client: AWSClient {
        return AWSClient(
            credentialProvider: .static(accessKeyId: accessKey, secretAccessKey: secretKey),
            httpClientProvider: .createNew)
    }

但这并不是真正的用户设置。最重要的是,我还收到了我需要在 deinit 之前关闭 AWSClient 的错误。我能够使其与硬编码 accessKeysecretKey 一起工作,并在 class 的 deinit 中关闭客户端,如下所示。

    deinit {
        do {
            try client.syncShutdown()
        } catch {
            print("client shutdown error deinit")
        }
    }

我尝试的另一种方法是在 UserPreferences 对象中不包含客户端,而是在我的视图本身中创建一个新的客户端变量。但在一个视图中,我有一个辅助函数,我无法正常关闭客户端,因为函数和结构中没有 deinit。

但我觉得必须有更好的方法来初始化此客户端并在我的任何视图中使用它。

感谢您的帮助。

编辑:

这是我的偏好视图。

struct PreferencesView: View {
    
    @ObservedObject var userPreferences = UserPreferences()
    
    var body: some View{
        VStack{
            
            Text("Preferences")
                .font(.title)
                
            HStack{
                Text("Access Key:")
                SecureField("Access Key", text: $userPreferences.accessKey)
            }
            HStack{
                Text("Access Secret:")
                SecureField("Access Secret", text: $userPreferences.secretKey)
            }
            HStack {
                Picker(selection: $userPreferences.region, label: Text("Region:")) {
                    ForEach(userPreferences.regions, id: \.self) { region in Text(region)
                    }
                }
                Spacer()
                Button{checkAWSClient(accessKey: userPreferences.accessKey, secretKey: userPreferences.secretKey)} label: {
                    Text("Check AWS Credentials")
                }
                Button{print("done button clicked")} label:{
                    Text("Done")
                }
            }
            Spacer()
        }.frame(maxWidth: .infinity, maxHeight: .infinity)
        .padding()
    }
}

它有两个用户首选项字符串的两个文本字段,我将其存储为用户默认值。并且有一个按钮,点击后将使用用户首选项检查与某些外部服务的连接。

而函数CheckAWSClient如下

func checkAWSClient(accessKey: String, secretKey: String){
    print("Checking aws client")
    let client = AWSClient(credentialProvider: .static(accessKeyId: accessKey, secretAccessKey: secretKey), httpClientProvider: .createNew)
    print(client)
    let s3 = S3(client: client, region: .useast1)
    s3.listBuckets()
        .whenComplete {response in
            switch response {
            case .failure(let error):
                print(error)
                print("Failure s3")
            
            case .success(let output):
                print(output)
                print("Success s3")
            
            }
        }
    
    let ec2 = EC2(client: client, region: .useast1)
    let describeInstancesRequest = EC2.DescribeInstancesRequest(dryRun: false)
    ec2.describeInstances(describeInstancesRequest)
        .whenComplete {response in
            switch response {
            case .failure(let error):
                print(error)
                print("Failure EC2")
            case .success(let output):
                print(output)
                print("Success EC2")
            }
        }    
}

但是我收到一条错误消息,提示我需要在 deinit 中执行 client.shutdown()。但是这是什么意思。在某些视图中,我正在用辅助函数初始化客户端。

这里是错误

Assertion failed: AWSClient not shut down before the deinit. Please call client.syncShutdown() when no longer needed.: file SotoCore/AWSClient.swift, line 95
2021-02-23 03:41:20.839170+0530 EC2 Menu Bar[3047:23906299] Assertion failed: AWSClient not shut down before the deinit. Please call client.syncShutdown() when no longer needed.: file SotoCore/AWSClient.swift, line 95

tl;dr 我需要使用他设置的用户默认设置并建立数据库连接之类的东西。这样我就可以从不同的角度进行各种休息查询。这样做的好处是什么?

假设所有这些调用都是异步的,我想函数应该如下所示。主要思想是捕获客户端直到所有 activity 结束(因为它是在堆栈上创建的,所以没有所有者,一旦不再引用它就会被销毁),并且仅在失败时或在最后。

func checkAWSClient(accessKey: String, secretKey: String){
    print("Checking aws client")
    let client = AWSClient(credentialProvider: .static(accessKeyId: accessKey, secretAccessKey: secretKey), httpClientProvider: .createNew)
    print(client)
    
    let shutdown = {[client] in     // << capture client
        do {
            try client.syncShutdown()
        } catch {
            print("client shutdown error deinit")
        }
    }
    
    let s3 = S3(client: client, region: .useast1)
    s3.listBuckets()
        .whenComplete {response in
            switch response {
            case .failure(let error):
                print(error)
                print("Failure s3")
                
                // failure: client is not needed anymore - shutdown
                shutdown()
                
            case .success(let output):
                print(output)
                print("Success s3")
                
                // continue with EC2 only on list success
                let ec2 = EC2(client: client, region: .useast1)
                let describeInstancesRequest = EC2.DescribeInstancesRequest(dryRun: false)
                ec2.describeInstances(describeInstancesRequest)
                    .whenComplete {response in
                        switch response {
                        case .failure(let error):
                            print(error)
                            print("Failure EC2")
                        case .success(let output):
                            print(output)
                            print("Success EC2")
                        }
                        
                        //completed: client is not needed anymore - shutdown
                        shutdown()
                    }
            }
        }
}

注意:无法测试,因此可能需要一些拼写错误修复或调整