๋๊ธฐ & ๋น๋๊ธฐ
synchronous๋ ์์ ์ ๋๊ธฐ์ ์ผ๋ก ์คํ. ์คํ ์ค์ธ ์์ ์ด ๋๋ ๋ ๊น์ง ๋ค๋ฅธ ์์ ์ ๊ธฐ๋ค๋ฆผ
asynchronous๋ ๋น๋๊ธฐ๋ก ํ์ฌ ์คํ ์ค์ธ ์์ ์ด ์์ด๋ ๋ค๋ฅธ ์์ ์ ์คํ ํ ์ ์๋ค. ์ค๋ ๊ฑธ๋ฆฌ๋ ์์ ์ ๋น๋๊ธฐ๋ก ์ฒ๋ฆฌํด์ ๋ฉ์ธ ์ค๋ ๋์์ ํ์๋ก ํ๋ ์์ ์ด ๋ฉ์ถ์ง ์๊ฒ ํ๋ค.
MainActor
UI ๊ด๋ จ ์์ ์ ๋ฉ์ธ ์ค๋ ๋์์ ์คํ๋์ด์ผ ํ๋๋ฐ, MainActor๋ ์ด๋ฅผ ์ฝ๊ฒ ๊ด๋ฆฌํ ์ ์๊ฒ ํด์ค๋ค. UI ์ ๋ฐ์ดํธ์ ๊ด๋ จ๋ ์์ ์ ๋ฉ์ธ ์ค๋ ๋์์ ์คํํ๋๋ก ๋ณด์ฅํ๋ค. ๋น๋๊ธฐ ์์ ์์ ๋ฉ์ธ ์ค๋ ๋๋ก์ ์ ๊ทผ์ ์์ ํ๊ฒ ๊ด๋ฆฌํ๊ธฐ ๋๋ฌธ์ ์ค๋ ๋ ์์ ์ ์ด๋ฉฐ DispatchQueue.main.async์ ๊ฐ์ ์ฝ๋๋ฅผ ์์ฑํ ํ์๊ฐ ์์ด ๊ฐ๊ฒฐํ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ค.
struct DefaultProductDetailUseCase: ProductDetailUseCase {
func fetchProductDetail(idx: Int) async throws -> Product {
let (data, response) = try await URLSession.shared.data(from: URL(string: "https://dummyjson.com/products/\(idx)")!)
guard let response = response as? HTTPURLResponse,
(200..<300).contains(response.statusCode) else {
throw CustomError.statusCodeError
}
guard let result = try? JSONDecoder().decode(Product.self, from: data) else {
throw CustomError.invalidResponse
}
return result
}
}
๋น๋๊ธฐ ์์ ์ ์ํํ๋ ํจ์๊ฐ ์๋ค๊ณ ํ ๋ ViewModel์์ ์๋์ ๊ฐ์ด ์ฌ์ฉํ ์ ์๋ค.
class ProductDetailViewModel: ObservableObject {
var useCase: ProductDetailUseCase?
@Published var product: Product?
init(useCase: ProductDetailUseCase) {
self.useCase = useCase
}
func getchProductDetail(idx: Int) {
Task {
let product = try await self.useCase?.fetchProductDetail(idx: idx)
await updateUI(with: product)
}
}
@MainActor
private func updateUI(with product: Product?) async {
self.product = product
}
}
MainActor์ ์ฌ์ฉํด์ ๋ฉ์ธ ์ค๋ ๋์์ ๋์ํด์ผ ํ๋ ์ฝ๋๋ฅผ ์คํํ๋ค. ๋น๋๊ธฐ ์์ ํ UI ์ ๋ฐ์ดํธ ์ฝ๋๋ฅผ ๋ฉ์ธ ์ค๋ ๋์์ ์์ ํ๊ฒ ์คํํ ์ ์๋ค.
Async
๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ํํ๋ ํจ์๋ฅผ ์ ์ํ๋ ค๋ฉด async ํค์๋๋ฅผ ์ฌ์ฉํด ์ค๋ค.
func fetchProductDetail(idx: Int) async -> Product {
// ์๋ต
}
async ์ฝ๋๋ concurrent ์ปจํ ์คํธ์์๋ง ์คํ ๊ฐ๋ฅํ๋ค. Task ํน์ ๋ค๋ฅธ async ํจ์์์ async ํจ์๋ฅผ ํธ์ถํ ์ ์๋ค.
์๋ฌ๋ฅผ ๋ฐํํ ์ ์๋ ๊ฒฝ์ฐ async throws๋ก ์ ์ํด ์ค๋ค.
func fetchProductDetail(idx: Int) async throws -> Product {
// ์๋ต
}
Await
Task {
let product = try await self.useCase?.fetchProductDetail(idx: idx)
await updateUI(with: product)
}
async ํจ์๋ฅผ ํธ์ถํ๊ธฐ ์ํด์ await ํค์๋๋ฅผ ์ฌ์ฉํ๋ค. await์ผ๋ก ๋งํน๋ ๊ณณ์ potential suspension point(์ ์ฌ์ ์ธ ์ผ์ ์ค๋จ ์ง์ )์ผ๋ก ์ง์ ๋๋ค. ์ฆ ์ด ํจ์๋ฅผ ํธ์ถํ ๋ถ๋ถ๋ ๋๊ธฐ ์ํ๊ฐ ๋ ์ ์๋ค๋ ๊ฒ์ ์๋ฏธํ๋ค.
์ฌ๊ธฐ์ suspension point ์ suspend๋ ์ค๋ ๋๋ฅผ block ์ํค๋ ๊ฒ์ด ์๋๋ผ ๋ค๋ฅธ ๋์์ ์ํํ ์ ์๋๋ก ์ปจํธ๋กค์ ํฌ๊ธฐํ๋ ๊ฒ์ ์๋ฏธํ๋ค. ์ค๋ ๋์ ๋ํ ์ ์ด๊ถ์ system์๊ฒ ์ ๋ฌํ๊ฒ ๋๊ณ , ์์คํ ์ ํด๋น ์ค๋ ๋๋ฅผ ์ฌ์ฉํด ๋ค๋ฅธ ์์ ์ ์ํํ ์ ์๋ค. ์ดํ ์์คํ ์ ๋ค๋ฅธ ํน์ ์ค๋ ๋ ์ ์ด๊ถ์ ์ ๋ฌํด์ suspension point ์ดํ ์ฝ๋๋ฅผ ์ํํ ์ ์๊ฒ ํ๋ค.
task
๋ค๋ฅธ Task์ ํจ๊ป ๋์์ ์คํ ํ ์ ์๋ ๋น๋๊ธฐ ์ปจํ ์คํธ๋ฅผ ์ ๊ณตํ๋ค. SwiftUI์์๋ task view modifier๋ฅผ ์ ๊ณตํ๊ณ ์๋ค.
@inlinable public func task(priority: TaskPriority = .userInitiated, _ action: @escaping @Sendable () async -> Void) -> some View
task modifier๋ ์๋์ ๊ฐ์ด ์ฌ์ฉํ๋ค.
VStack { ์๋ต }
.task {
do {
try await vm.getProductDetail(idx: 1)
} catch {
print("error!")
}
}
getProductDetail(idx:)๋ error๋ฅผ ๋ฐํ ํ ์ ์๋ async throw ์ด๊ธฐ ๋๋ฌธ์ task modifier ๋ด๋ถ์์ try catch๋ฅผ ์ฌ์ฉํ๋ค. ๋ง์ฝ task modifier๊ฐ ์๋ Button์ ๋๋ ์ ๋์ ๊ฐ์ ๋์ ์ดํ์ ์ํ๋์ผ ํ๋ค๋ฉด Task.init() ๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
@frozen public struct Task<Success, Failure> : Sendable where Success : Sendable, Failure : Error {
}
initializer๋ task modifier์ ๋์ผํ๋ค.
@discardableResult
public init(priority: TaskPriority? = nil, operation: @escaping @Sendable () async -> Success)
Button {
Task {
do {
try await vm.getProductDetail(idx: 1)
} catch {
}
}
} label: {
Text("๋ฒํผ์ผ๋ก ์คํ ํ ๊ฒฝ์ฐ")
}
Task๋ก ์์ฑ๋ ์์ ์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋์์ ์ฆ์ ์คํ๋๋ฉฐ await ํค์๋๋ก ํด๋น ์ง์ ์์ ์๋ฃ๋ ๊ฐ์ด ๋์์ฌ ๋๊น์ง ๊ธฐ๋ค๋ฆด ์ ์๋ค.
inout paramters
concurrency๋ ๊ด๋ จ๋๊ฑด ์๋์ง๋ง ์์ฃผ ๋์์ ์ ๋ฆฌํ์๋ฉด, inout์ Swift ๋ด์ ์ ์๋ ํค์๋.
ํจ์์ ๊ฐ์ ์ ๋ฌํ ๋ ๋งค๊ฐ๋ณ์๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋๋ฐ ์ด๋ ๋งค๊ฐ ๋ณ์์ ๊ฐ์ ๋ณ๊ฒฝํด๋ ์๋ ๋ณ์ ๊ฐ์ ์ํฅ์ ๋ฏธ์น์ง ์๋๋ค. inout์ ์ฌ์ฉํ๋ฉด ํจ์์ ํ๋ผ๋ฏธํฐ ๊ฐ ๋ณ๊ฒฝ์ฌํญ์ ์๋ณธ ๋ณ์์ ์ ์ฉํ ์ ์๋ค.
class ProductDetailViewModel: ObservableObject {
@Published var product: Product?
func removeCurrentProduct() {
self.removeProductRequest(product: &product)
}
private func removeProductRequest(product: inout Product?) {
product = nil
}
// product๋ฅผ fetchํ๋ ์ฝ๋๋ ์ ์ธ๋จ
}
SwiftUI์ View์์ removeCurenProduct๋ฅผ ํธ์ถํ๋ฉด viewModel ๋ด๋ถ์ ์ ์๋ private ํจ์์ธ removeProductRequest๋ฅผ ํธ์ถํ๋ค. ์ด๋ ํ๋ผ๋ฏธํฐ๋ก inout ๋งค๊ฐ๋ณ์๋ฅผ ์ ๋ฌํ๋ค. ์ ๋ฌ๋ฐ์ ๋งค๊ฐ๋ณ์์ ๊ฐ์ ๋ณ๊ฒฝํ๋ฉด viewModel์ ์๋ณธ ๋ณ์์ธ @Published๋ก ์ ์๋ product์ ๊ฐ์ด nil๋ก ๋ณ๊ฒฝ๋๋ค.
Button {
vm.removeCurrentProduct()
} label: {
Text("์ํ ์ ๋ณด ์ ๊ฑฐํ๊ธฐ")
}
.buttonStyle(.borderedProminent)
Text(vm.product?.title ?? "")
View ์ฝ๋๋ก ์๋ณธ product ๊ฐ์ ๋ณ๊ฒฝ์ ํ์ธํ ์ ์๋ค.
removeProductRequest(product: &product)
๋งค๊ฐ๋ณ์ ์ ๋ฌ์ &(ampersand)์ ์ฌ์ฉํ๋ค. ์ด๋ ๋งค๊ฐ๋ณ์๊ฐ inout์ผ๋ก ์ฌ์ฉ๋๊ณ ์์์ ๋ช ์์ ์ผ๋ก ์๋ ค์ค๋ค.
Isolated Actor
- ๋์์ฑ์ ์์ ํ๊ฒ ๊ด๋ฆฌํ๊ธฐ ์ํ ํ์
- Actor ๋ด์ ๋ฐ์ดํฐ๋ ๋ค๋ฅธ ์ค๋ ๋๋ ์ปจํ ์คํธ์์ ์ ๊ทผ ํ ์ ์์ง๋ง ํน์ ๋ฉ์๋๋ ํ๋กํผํฐ๋ฅผ isolated๋ก ํ์ํ๋ฉด ํด๋น actor์ ์ํ์ ์ ๊ทผํ๊ณ ์์ ํ ์ ์์
- ๋ชจ๋ Actor ํ์ ์ ์๋ฌต์ ์ผ๋ก Sendable์ ๋ฐ๋ฅธ๋ค. actor๋ mutable state์ ๋ํ isolation์ ๋ณด์ฅํ๋ค.
Sendable
- Sendable ํ์ ์ ์ค์ํ๋ ํ์ ์ ํด๋น ํ์ ์ ์ธ์คํด์ค๋ฅผ ์ค๋ ๋ ๊ฐ ์์ ํ๊ฒ ์ ๋ฌ ํ ์ ์์
- Value Type์ ๋ด๋ถ์ ์ผ๋ก Sendable ํ๋กํ ์ฝ์ ๋ฐ๋ฅด๋ฏ๋ก ์ค๋ ๋์ ์์ ์ ๋ณด์ฅํ๋ค.
- Reference Type์ final ํด๋์ค ํน์ ํด๋์ค ๋ด ์์ ํ์ ์ผ๋ก ์ ์ธํ์ฌ Sendable ํ๋กํ ์ฝ์ ์ฌ์ฉํ ์ ์๋ค.
(์ด๋์ ์ฌ์ฉ๋ ์ ์์์ง๋ ์ ๋ชจ๋ฅด๊ฒ ๋ค๐ค)
public static func run(
priority: TaskPriority? = nil,
operation: @escaping @Sendable (_ send: Send<Action>) async throws -> Void,
catch handler: (@Sendable (_ error: Error, _ send: Send<Action>) async -> Void)? = nil,
fileID: StaticString = #fileID,
line: UInt = #line
) -> Self
- TCA์์ ๋น๋๊ธฐ ์ฒ๋ฆฌ ์ดํ Effect๋ฅผ ๋ฐํํ๊ธฐ ์ํด .run์ ์ฌ์ฉํ๋๋ฐ ์ด๋ operation์ @Sendable์ด ๋ค์ด๊ฐ ์๋ค(TCAํ๋ค๊ฐ ์ฌ๊ธฐ๊น์ง ์๋น ใ ใ ใ ใ ใ )
struct SendableLibraryStruct: Sendable {
var name: String
var bookCount: Int
}
- ๊ฐ ํ์ ์ธ ๊ตฌ์กฐ์ฒด๋ Sendable์ ์ค์ํด์ค๋ค.
final class SendableLibraryClass: Sendable {
let name: String
let bookCount: Int
init(name: String, bookCount: Int) {
self.name = name
self.bookCount = bookCount
}
}
- ์ฐธ์กฐ ํ์ ์ธ class๋ final class์ฌ์ผ ํ๊ณ Sendable์ ์ค์ํด ์ค๋ค.
actor SendableTestActor {
private var libraryStruct = SendableLibraryStruct(name: "", bookCount: 0)
private var libraryClass = SendableLibraryClass(name: "", bookCount: 0)
func updateDataWithStruct(libraryInfo: SendableLibraryStruct) {
self.libraryStruct = libraryInfo
}
func updateDataWithClass(libraryInfo: SendableLibraryClass) {
self.libraryClass = libraryInfo
}
func getNameFromClass() async throws -> String {
try await Task.sleep(until: .now + .seconds(1), clock: .continuous)
return libraryClass.name
}
}
- actor๋ ๋์์ฑ ์ ๊ทผ์ ์์ ํ๊ฒ ๊ด๋ฆฌํ๋ค.
- ๋ด๋ถ์ ๋ฐ์ดํฐ๋ ๋ค๋ฅธ ์ค๋ ๋๋ ์คํ ์ปจํ ์คํธ์์ ์ง์ ์ ๊ทผํ ์ ์๋ค.
- ํน์ ๋ฉ์๋๋ ํ๋กํผํฐ๋ฅผ isolated๋ก ํ์ํ๋ฉด ํด๋น actor ์ํ์ ์์ ํ๊ฒ ์ ๊ทผํ๊ณ ์์ ํ ์ ์๋ค.
- ๋ฐ์ดํฐ์ ๋ฌด๊ฒฐ์ฑ์ ์ ์ง์ ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค.
class SendableTestViewModel: ObservableObject {
@Published var userName: String = ""
let testActor = SendableTestActor()
func updateStruct() async {
let libraryInfo = SendableLibraryStruct(name: "kim", bookCount: 19)
await testActor.updateDataWithStruct(libraryInfo: libraryInfo)
}
func updateClass() async {
let libraryInfo = SendableLibraryClass(name: "lee", bookCount: 20)
await testActor.updateDataWithClass(libraryInfo: libraryInfo)
}
@MainActor
func getName() async {
do {
self.userName = try await testActor.getNameFromClass()
} catch {
}
}
}
ViewModel์ actor์ธ SendableTestActor๋ฅผ ์ฌ์ฉํด์ ์ค๋ ๋ ๊ฐ์ ์์ ํ๊ฒ ๊ฐ์ ์ ๋ฌ ํ ์ ์๋ค. updateStruct()์ updateClass๋ ๊ฐ์ ์ ๋ฌ ๋ฐ์ ๋ด๋ถ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฒ ์์ ํ๋ค.
func getNameFromClass() async throws -> String {
try await Task.sleep(until: .now + .seconds(1), clock: .continuous)
return libraryClass.name
}
actor์ธ SendableTestActor์ ํ์ธํด ๋ณด๋ฉด ๋ด๋ถ ๋ฐ์ดํฐ ์ค ์ด๋ฆ์ ๋ฐํํ๋ ๋น๋๊ธฐ ํจ์๊ฐ ์๋ค. ์ด๋ฅผ viewModel์์ ์ ๋ฌ๋ฐ์ ์ฌ์ฉํ ์ ์๋ค.
@MainActor
func getName() async {
do {
self.userName = try await testActor.getNameFromClass()
} catch {
}
}
actor์ ์ฌ์ฉํ๋ฉด actor์ state์ ์์ ํ๊ฒ ์ ๊ทผํ๊ณ ์์ ํ๋ฉฐ ๊ฐ์ ๊ฐ์ ธ์ฌ ์ ์๊ธฐ ๋๋ฌธ์ ๋ฐ์ดํฐ์ ๋ฌด๊ฒฐ์ฑ์ ์ ์งํ๋ฉด์ ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐ ํ ์ ์๋ค.
References
https://www.hackingwithswift.com/sixty/5/10/inout-parameters
+) ChatGPT
์ดํ ๊ณต๋ถํ ๊ฒ
'iOS ๐ > Swift' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[SwiftUI/Swift] ์ค์ํํธ ํ๋กํผํฐ ๋ํผ Property wrapper ๊ธฐ์ด (0) | 2023.01.24 |
---|---|
[Swift] ์ค์ํํธ ๊ตฌ์กฐ์ฒด ์์๋ณด๊ธฐ (0) | 2023.01.24 |