Appwrite를 사용하여 신속하게 비동기 코드 대기

Swift를 사용한 최신 애플리케이션 개발에는 클로저 및 완료 핸들러를 사용하는 많은 비동기(또는 "비동기") 프로그래밍이 포함되지만 이러한 API는 사용하기 어렵습니다. 이는 많은 비동기 작업이 사용되거나 오류 처리가 필요하거나 비동기 호출 간의 제어 흐름이 필요할 때 특히 문제가 됩니다. 새로운 동시성 모델을 사용하면 훨씬 더 자연스럽고 오류가 덜 발생합니다.

를 통해 Apple 및 Swift SDK는 async/await에 대한 최고 수준의 지원으로 업데이트되었습니다. 이것이 중요한 이유를 이해하고 변경 사항을 구현하는 방법을 알아보기 위해 이전 API와 새 API를 비교해 보겠습니다.

🏚️ 옛날 방식



명시적 콜백(클로저 또는 완료 핸들러라고도 함)을 사용한 비동기 프로그래밍에는 많은 문제가 있습니다. 가장 분명한 문제는 코드를 읽고 유지하기가 더 어렵게 만든다는 것입니다. 또한 테스트 가능한 코드를 작성하기 어렵게 만듭니다. 이러한 문제 중 일부가 어떻게 나타나는지 살펴보겠습니다.

문제 1: 파멸의 피라미드



간단한 비동기 작업 시퀀스에는 종종 깊게 중첩된 클로저가 필요합니다.

func getUserProfileImage(userId: String, completion: (Result<ByteBuffer, AppwriteError>) -> Void) {
    let database = Database(self.client)
    let storage = Storage(self.client)

    database.getDocument(collectionId: "profiles", userId: userId) { docResult in
        switch docResult {
        case .success(let document):
            storage.getFileDownload(bucketId: "profiles", fileId: document.data["profileImageId"]) { fileResult in
                switch result {
                case .success(let bytes):
                    completion(.success(bytes))
                }
            }
        }
    }
}


이 "파멸의 피라미드"는 코드가 실행되는 위치를 읽고 추적하기 어렵게 만듭니다. 또한 클로저 스택을 사용해야 하는 것은 다음에 논의할 많은 2차 효과로 이어집니다.

문제 2: 오류 처리



위의 예를 보면 성공 사례만 처리하고 있습니다. 사용자가 존재하지 않으면 어떻게 됩니까? 사용자에게 프로필 이미지가 없으면 어떻게 됩니까? 콜백은 오류 처리를 어렵고 매우 장황하게 만듭니다.

func getUserProfileImage(userId: String, completion: (Result<ByteBuffer, AppwriteError>) -> Void) {
    let database = Database(self.client)
    let storage = Storage(self.client)

    database.getDocument(collectionId: "profiles", userId: userId) { docResult in
        switch docResult {
        case .success(let document):
            storage.getFileDownload(bucketId: "profiles", fileId: document.data["profileImageId"]) { fileResult in
                switch result {
                case .success(let bytes):
                    completion(.success(bytes))
                case .failure(let error):
                    completion(.failure(error))
                }
            }
        case .failure(let error):
            completion(docResult)
        }
    }
}


문제 3: 누락된 완료 호출 또는 반환 문



완료 블록을 호출하지 않고 단순히 반환함으로써 비동기 작업을 조기에 구제하기 쉽습니다. 잊어버리면 이 문제를 디버그하기가 매우 어려울 수 있습니다.

func getUserProfileImage(userId: String, completion: (Result<ByteBuffer, AppwriteError>) -> Void) {
    let database = Database(self.client)
    let storage = Storage(self.client)

    database.getDocument(collectionId: "profiles", userId: userId) { docResult in
        switch docResult {
        case .success(let document):
            // ...
        case .failure(let error):
            return // <- Bail out early
        }
    }
}      


이제 getUserProfileImage를 호출하고 오류가 발생하면 함수가 완료되지 않고 호출 사이트 코드가 교착 상태가 됩니다. 블록을 호출하는 것을 기억하더라도 그 후에 돌아가는 것을 잊을 수 있습니다.

func getUserProfileImage(userId: String, completion: (Result<ByteBuffer, AppwriteError>) -> Void) {
    let database = Database(self.client)
    let storage = Storage(self.client)

    database.getDocument(collectionId: "profiles", userId: userId) { docResult in
        switch docResult {
        case .success(let document):
            // ...
        case .failure(let error):
            completion(.failure(error)) // <- No return, execution continues
        }

        doSomethingElse(userId) // <- Executes on error
    }
} 


✨ 새로운 방식



비동기 함수(async/await)를 사용하면 비동기 코드를 동기 코드처럼 작성할 수 있습니다. 이는 프로그래머가 동기식 코드에 사용할 수 있는 동일한 언어 구성을 최대한 활용할 수 있도록 하여 위에서 설명한 모든 문제를 즉시 해결합니다.

해결책 1: 더 이상 운명의 피라미드는 그만



제어 흐름은 단순히 중첩 없이 위에서 아래로 읽습니다.

func getUserProfileImage(userId: String) async throws -> ByteBuffer {
    let database = Database(self.client)
    let storage = Storage(self.client)
    let document = try await database.getDocument(
        collectionId: "profiles", 
        userId: userId
    )
    let bytes = try await storage.getFileDownload(
        bucketId: "profiles", 
        fileId: document.data["profileImageId"]
    )
    return bytes
}


보시다시피 이 코드는 이전과 동일한 작업을 수행하면서 훨씬 더 읽기 쉽고 간결하며 추론하기 쉽습니다.

해결 방법 2: 오류 처리 실행/잡기



동기 코드와 같은 방식으로 do/catch 구문을 사용하여 오류를 처리할 수 있습니다.

func getUserProfileImage(userId: String) async throws -> ByteBuffer {
    let database = Database(self.client)
    let storage = Storage(self.client)

    do {
        let document = try await database.getDocument(
            collectionId: "profiles", 
            userId: userId
        )
        let bytes = try await storage.getFileDownload(
            bucketId: "profiles", 
            fileId: document.data["profileImageId"]
        )
    } catch {
        throw error
    }

    return bytes
}    


해결 방법 3: 완료 및 중첩 반환이 완전히 사라짐



세 번째 문제에 대한 해결책은 지금... 아무것도 하지 않는 것입니다. 더 이상 완료 핸들러 호출 누락 또는 async/await 코드를 사용한 중첩 반환에 대해 걱정할 필요가 없습니다.

🔮 미래의 가능성



비동기 코드는 Swift로 앱을 개발하는 방식을 바꿀 수 있는 잠재력이 있습니다. 전반적으로 코드는 훨씬 더 읽기 쉽고 강력해지며 일반적인 비동기 패턴을 구현하는 데 필요한 노력이 줄어듭니다. 개발자가 async/await로 무엇을 만들 수 있는지 기대됩니다.

📚 리소스



다음 리소스를 사용하여 자세히 알아보고 도움을 받을 수 있습니다.
  • 🚀 Getting Started for Apple Tutorial
  • 🚀 Appwrite Github
  • 📜 Appwrite Docs
  • 💬 Discord Community
  • 좋은 웹페이지 즐겨찾기