How to manage strong & weak references with Async Await | Swift 동시성 #13

Sung Daegyu·2024년 5월 1일

Swift: How to manage strong & weak references with Async Await | Swift Concurrency #13

상황 설명.

StrongSelfDataService라는 final class가 있고, class method로 getData()가 있다.

final class StrongSelfDataService {
  func getData() async -> String {
    "Updated data!"
  }
}

StrongSelfBootcampViewModel라는 view model이 있고, 여기에는 @Published 변수인 var data: String이 있다. 또한 위의 데이터 서비스 클래스를 참조하는 변수 dataService가 있다.

final class StrongSelfBootcampViewModel: ObservableObject {
  @Published var data: String = "Some title"
  let dataService = StrongSelfDataService()
}

Strong self vs Weak self.

만약에 다음과 같은 함수가 있다고 하자.

func updateData() {
    Task {
      data = await dataService.getData()
    }
}
  • 이 함수는 dataService에서 데이터를 가져와서 view Model의 값을 변경하는 코드이다.

  • 만약에 사용자가 화면에 어떤 버튼을 클릭했을 때, ViewModel의 updateData()가 실행되고, 그 결과 (받아온 데이터)가 화면 위에 표시된다고 가정해보면.
  • 이 때, 사용자가 버튼을 누르고 (데이터가 받아오기 시작), 갑자기 이전 화면으로 나가거나 앱을 백그라운드로 돌리면 어떻게 될까?
    • 화면을 나갔음에도 데이터는 아직 받아오는 중일 것.
    • 이런 메모리/자원 누수 문제를 해결하기 위해 weak self가 등장했다.

Strong self의 다양한 표기.

여기서 다음 구문들은 모두 strong self를 사용한다.

// self, [self] 생략.
  func updateData1() {
    Task {
      data = await dataService.getData()
    }
  }
  
  // [self] 생략.
  func updateData2() {
    Task {
      self.data = await self.dataService.getData()
    }
  }
  
  // 생략 없음.
  func updateData3() {
    Task { [self] in
      self.data = await self.dataService.getData()
    }
  }

반면 weak self를 사용하려면 다음과 같다.

// This is a weak reference.
func updateData4() {
  Task { [weak self] in
    if let data = await self?.dataService.getData() {
      self?.data = data
    }
  }
}
  • weak self는 옵셔널 값(nil일 수도 있음)으로 옵셔널 추출을 해 줘야 한다.

그런데 왜 지금까지 배웠던 동시성 코드에는 weak self가 없을까?

→ 필요가 없기 때문!

→ Task를 통해 strong self 임에도 선택적으로 deinit 할 수 있다.

다음처럼 Task에 변수를 부여하면, Task.cancle()을 통해 선택적으로 실행을 종료할 수 있다.

private var someTask: Task<Void, Never>? = nil

func updateData5() {
  someTask = Task { [self] in
    self.data = await self.dataService.getData()
  }
}

// Task.cancel()을 통해, Task내의 구문들의 실행을 멈출 수 있다.
func cancelTasks() {
	someTask?.cancel()
	someTask = nil
}

// 에를 들어, 화면이 종료 된 경우 cancel()을 실행
SOME VIEW
.onDisappear {
   viewModel.cancelTasks()
}

Task에 변수를 지정하지 않고, 그대로 두어서 의도적으로 strong self를 사용할 수도 있다.

profile
대규의 개발로그

0개의 댓글