안녕하세요.
이번에는 SDK 개발자 입장에서 async/await 용 public API를 추가할 때 고려해야 할 사항에 대해 정리해볼게요.
이번 글은 아래 글을 토대로 작성하였습니다.
- https://swiftindepth.com/articles/async-await-modules/
Xcode 13.2부터 async/await 구문을 iOS 13에서도 사용할 수 있게 된 것 알고 계신가요??
https://developer.apple.com/documentation/xcode-release-notes/xcode-13_2-release-notes#Swift
(Xcode 13.1을 사용할 경우엔 iOS 15 이상만 사용할 수 있습니다ㅠㅠ)
SDK 개발자로서 iOS 13에서도 async/await 구문을 쓸 수 있다는 것은 군침이 도는 소식입니다... 😏
그럼 기존의 completion block 형태의 API를 async/await 형태로도 제공해주고 싶겠죠???
이때 SDK 개발자는 API를 어떻게 제공해줄 것인가에 대해 고민을 하게 됩니다.
1. 모든 사용자가 Xcode 13.2로 업데이트할 때까지 기존 completion block 형태의 public API 유지
2. completion block 형태의 public API를 유지하고, 내부 코드만 async/await 형태로 바꾸기
3. 그냥 다 무시하고 async/await 형태의 public API로 수정
4. 기존 completion block 형태의 public API는 유지하고, async/await 용 신규 API 추가 ✅
기존 SDK 사용자들을 위해서라도 4번 방법이 가장 좋다고 생각됩니다.
SDK 개발자의 시점에서 어떻게 async/await 용 API를 추가하는 것이 사용성이 좋을지에 대해 알아보겠습니다.
### (Step 1) async/await 용 public API 추가
아래 같이 completion block 형태의 API가 있다고 가정해볼게요.
public final class ArticlesAPI {
public func fetchArticles(completion: @escaping (Result<[Article], ArticleError>) -> Void) {
// .. implementation omitted
}
}
async/await API를 추가하고 전처리로 래핑해주면, 특정 조건에서만 async/await 용 신규 API를 사용할 수 있도록 할 수 있습니다.
public final class ArticlesAPI {
// New compiler flag
#if compiler(>=5.5) && canImport(_Concurrency)
// New method with an @available flag
@available(macOS 10.15, iOS 13, *)
public func fetchArticles() async throws -> [Article] {
// .. implementation omitted
}
#endif
// "Older" method
public func fetchArticles(completion: @escaping (Result<[Article], ArticleError>) -> Void) {
// .. implementation omitted
}
}
### (Step 2) 기존 completion block 로직 재사용
API를 completion block 용, async/await 용으로 분리했다고 해서, 내부 구현부까지 중복 코드가 있을 필요는 없겠죠??
그냥 async/await 용 API에서 completion block 형태의 API를 호출해주는 것이 중복코드도 줄이고, 관리하기에 더욱 용이할 것입니다.
이때, completion block -> async/await 형태로 바꿔주는 것이 withCheckedThrowingContinuation 함수입니다.
withCheckedThrowingContinuation 함수가 뭔지 간단하게 설명할게요.
withCheckedThrowingContinuation 이란?
withCheckedThrowingContinuation 클로저 안에서 값을 리턴해야 하는 경우에는 continuation.resume(returning:)을, 에러가 발생했을 경우에는 continuation.resume(throwing:)을 호출합니다.
그리고 continuation.resume(with:)를 사용하면 값을 리턴해야 하는 경우인지 에러가 발생한 경우인지 알아서 관리해줍니다.
신규로 추가한 async/await API는 withCheckedThrowingContinuation 클로저를 통해서 기존 completion block API를 호출해주면 되는 거예요ㅎㅎ
public final class ArticlesAPI {
#if compiler(>=5.5) && canImport(_Concurrency)
@available(macOS 10.15, iOS 13, *)
public func fetchArticles() async throws -> [Article] {
return try await withCheckedThrowingContinuation { continuation in
fetchArticles(completion: continuation.resume(with:))
}
}
#endif
public func fetchArticles(completion: @escaping (Result<[Article], ArticleError>) -> Void) {
// .. implementation omitted
}
}
이러면 기존 completion block API 로직을 재사용할 수 있겠죠???ㅎㅎ
### (Step 3) 기존 API Deprecated
기존 completion block API는 deprecated 시켜줍니다.
왜냐면, 신규로 유입되는 사용자들로 하여금 async/await API를 쓰도록 유도시키는 거죠ㅎㅎㅎ
public final class ArticlesAPI {
#if compiler(>=5.5) && canImport(_Concurrency)
@available(macOS 10.15, iOS 13, *)
public func fetchArticles() async throws -> [Article] {
func fetchArticles() async throws -> [Article] {
return try await withCheckedThrowingContinuation { continuation in
fetchArticles(completion: continuation.resume(with:))
}
}
#endif
// New deprecation flag
@available(*, deprecated, message: "Please use the async/await version of `fetchArticles`")
public func fetchArticles(completion: @escaping (Result<[Article], ArticleError>) -> Void) {
// .. implementation omitted
}
}
## 참고
- https://swiftindepth.com/articles/async-await-modules/
신규 기능이 추가되었다고 무작정 추가하는 것이 아닌, SDK 개발자 관점에서 어떻게 개발하고 관리를 할 것인가에 초점을 맞춰서 글을 써봤어요.
이번 글은 여기서 마무리.
'Swift' 카테고리의 다른 글
[Swift 5.6] Swift 5.6 추가된 기능들 (1) | 2022.03.12 |
---|---|
CryptoKit을 사용한 암호화 (0) | 2022.02.23 |
Core Data의 not Optional 설정 시 크래쉬 주의 (0) | 2022.02.20 |
enum 남용 주의 (0) | 2022.02.17 |
Literal 이란? (1) | 2022.02.07 |