티스토리 뷰

원본 출처 : https://medium.com/flawless-app-stories/you-dont-always-need-weak-self-a778bec505ef

 

You don’t (always) need [weak self]

We will talk about weak self inside of Swift closures to avoid retain cycles & explore cases where it may not be necessary to capture self weakly.

medium.com

 

[weak self]를 사용할 때, self?.대신 guard let self = self를 사용하면 사이드 이펙트가 생깁니다.

 

클로저는 비용이 높은 serial 작업, 세마포어 같은 스레드 블락 때문에 delay deallocation(지연 메모리 해제)를 할 수 있는데, guard let self를 사용하면 이 deallocation delay를 방지할 수 없습니다.

 

비용이 높은 여러 작업을 하는 이미지 작업을 예로 들어보겠습니다.

 

func process(image: UIImage, completion: @escaping (UIImage?) -> Void) {
    DispatchQueue.global(qos: .userInteractive).async { [weak self] in
        guard let self = self else { return }
        // perform expensive sequential work on the image
        let rotated = self.rotate(image: image)
        let cropped = self.crop(image: rotated)
        let scaled = self.scale(image: cropped)
        let processedImage = self.filter(image: scaled)
        completion(processedImage)
    }
}

 

[weak self]와 guard let self를 사용하고 있습니다.

gaurd let 은 self 가 nil인지 확인하고 nil이 아니라면, 스코프를 위한 임시 강한 참조인 self를 생성합니다.

 

5개의 고비용 작업을 할 때까지 우리가 이미 만들어놓은 강한 참조인 self는 클로저 스코프의 끝에 도달할 때까지 deallocation되지 않습니다. 다르게 말하면 guard let은 클로저의 생명 주기동안 계속해서 살아있다는 것입니다.

 

만약에 guard let을 사용하지 않고 옵셔널 채이닝으로 self?.를 사용하면 시작할 때 강한 참조를 만들지 않고 nil체크를 모든 메서드 call마다 하게 됩니다. 이 말은 즉 어느 포인트에서 nil이 될 수 있다는 것이고, 해당하는 메서드는 불리지않고 다음 줄로 넘어가게 됩니다.

 

다소 미묘한 차이이지만 뷰 컨트롤러를 해제한 후 불필요한 작업을 피하는 경우나 반대로 객체 할당이 해제되기 전에 모든 작업을 완료하려는 경우(예: 데이터 손상을 방지하기 위해서)에는 지적할만하다고 생각합니다.

 

Grand Central Dispatch

GCD는 reference cycles의 위험이 없습니다.

weak self 가 없어도 메모리 누수가 발생하지 않는데, gcd는 바로 실행되기 때문입니다.

func nonLeakyDispatchQueue() {
    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
        self.view.backgroundColor = .red
    }

    DispatchQueue.main.async {
        self.view.backgroundColor = .red
    }

    DispatchQueue.global(qos: .background).async {
        print(self.navigationItem.description)
    }
}

 

하지만 DispatchWorkItem은 누수가 발생합니다. 왜냐하면 로컬프로퍼티에 저장하기 때문입니다.

func leakyDispatchQueue() {
    let workItem = DispatchWorkItem { self.view.backgroundColor = .red }
    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: workItem)
    self.workItem = workItem // stored in a property
}

 

UIView.Animate and UIViewPropertyAnimator

gcd처럼 안전합니다.

func animteToRed() {
    UIView.animate(withDuration: 3.0) { 
        self.view.backgroundColor = .red 
    }
}
func setupAnimation() {
    let anim = UIViewPropertyAnimator(duration: 2.0, curve: .linear) { 
        self.view.backgroundColor = .red 
    }
    anim.addCompletion { _ in 
        self.view.backgroundColor = .white 
    }
    anim.startAnimation()
}

하지만 애니메이션을 나중에 쓰려고 저장하는 경우 강한 참조가 발생합니다.

func setupAnimation() {
    let anim = UIViewPropertyAnimator(duration: 2.0, curve: .linear) {
        self.view.backgroundColor = .red
    }
    anim.addCompletion { _ in
        self.view.backgroundColor = .white
    }
    self.animationStorage = anim
}

 

Storing a function in a property

잘 보이지 않지만 조심할 것

class PresentedController: UIViewController {
  var closure: (() -> Void)?
}

 

class MainViewController: UIViewController {
  
  var presented = PresentedController()

  func setupClosure() {
    presented.closure = printer
  }

  func printer() {
    print(self.view.description) 
  }
}

여기에서 self.를 쓰지 않았지만 강한 참조가 생깁니다.

클로저가 self.printer를 강한참조합니다.

 

func setupClosure() {
  self.presented.closure = { [weak self] in 
    self?.printer() 
  }
}

이렇게 해주시면됩니다.

 

Timers

타이머는 프로퍼티에 저장하지 않아도 이슈가 생길 수 있습니다.

func leakyTimer() {
    let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
        let currentColor = self.view.backgroundColor
        self.view.backgroundColor = currentColor == .red ? .blue : .red
    }
    timer.tolerance = 0.1
    RunLoop.current.add(timer, forMode: RunLoop.Mode.common)
}

1. 타이머가 반복합니다.

2. self.는 클로저에서 weak self없이 참조됩니다.

 

두 상태가 만나면  타이머는 참조된 컨트롤러/오브젝트를 해제하지 않습니다.

메모리 누수라기보다는 지연된 할당입니다.

이 지연이 무한히 발생합니다.

댓글
공지사항