[iOS] Strong, Weak에 대하여

홍승현·2022년 10월 31일
0

Retain Cycle

목록 보기
1/1
post-thumbnail

Goal

  • Swift 문법 중 Strong과 Weak의 차이점을 이해한다.

소개

Swift 공부를 진행하면서 Interface Builder에 붙여놓은 객체를 뷰 컨트롤러 클래스의 프로퍼티와 연결하는 과정에서 Storage 설정에 있는 StrongWeak이 정확하게 어떤 역할을 하는지 궁금해졌다.

Outlet 어노테이션 객체에 대해 Connection할 때 변수를 strong과 weak으로 세팅하게 되면, weak은 키워드가 들어가지만 strong은 키워드 없이 선언되는 것을 볼 수 있다.
대체 어떤 효과를 지녔길래, 이렇게 두 가지 기능으로 분류해놓은걸까?

차이점

결론부터 말하자면 weak타입으로 선언한 변수는 strong타입과 달리 다른 곳에서 참조되고 있다 하더라도 메모리에서 제거될 수 있다.
반대로 말하자면, strong타입은 다른 곳에서 참조되고있다면 메모리가 회수되지 않는다는 뜻이다.

듣고보면 weak이 그다지 메리트가 없어보이겠지만, 메모리 누수에 있어서 weak은 효과적인 키워드가 될 수 있다.

Example

iOS 유튜버 iOS AcademyMemory Leak 영상 코드 일부를 차용했습니다.

FirstViewController(줄여서 FirstVC)가 있다고 하자

import UIKit

import Then

// 루트 뷰컨트롤러
final class FirstVC: UIViewController {

  private let button = UIButton().then {
    $0.setTitle("Go to other View", for: .normal)
    $0.setTitleColor(.tintColor, for: .normal)
  }

  override func viewDidLoad() {
    super.viewDidLoad()

    // 버튼 위치 설정
    button.frame = CGRect(x: 0, y: 0, width: 200, height: 50)
    button.center = view.center

    // 버튼 액션 설정
    button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)

    // 뷰에 버튼을 추가
    view.addSubview(button)
  }
  // 버튼 클릭 이벤트
  @objc private func didTapButton() {
    let vc = SecondVC()
    present(vc, animated: true)
  }
}

FirstVC의 코드를 간단하게 해석하자면, 버튼을 프로퍼티로 두고 있으며, 버튼을 탭할 시 SecondVC라는 ViewController를 띄우는 처리를 맡고있다.

SecondVC는 하기 코드와 같다.


//MARK: - CustomView

final class CustomView: UIView {

  let vc: UIViewController
  init(vc: UIViewController) {
    self.vc = vc
    super.init(frame: .zero)
  }

  required init?(coder: NSCoder) {
    fatalError()
  }
}

//MARK: - SecondVC

final class SecondVC: UIViewController {

  var myView: CustomView?

  override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .red
    self.myView = CustomView(vc: self)
  }
}

SecondVC는 myView라는 CustomView 프로퍼티를 갖고 있고, CustomView를 생성할 때 자기 자신(SecondVC)을 인자로 전달한다.
CustomView는 vc라는 ViewController프로퍼티를 갖고 있다.
따라서 SecondVC가 생성될 때 CustomView와 함께 상호참조하고있음을 알 수 있다.

백문이 불여일견, 한번 실행하여 결과물을 살펴보자

결과물을 볼 때는 전혀 문제가 없어보인다. 단지 버튼을 누르면 빨간 백그라운드를 하고있는 SecondVC가 튀어나오고, 내리면 사라지면서 다시 FirstVC로 초점이 맞춰진다.

하지만 여기에는 문제가있다. 방금 언급했다시피 SecondVCCustomView는 서로 상호참조하고있기 때문에 FirstVC로 다시 넘어갈 때 View가 dismiss되어 사라져도 메모리를 회수하지 못하게 된다. 계속 이러한 사이클을 유지한다면, 메모리 누수가 발생하게 된다.

Debuging

SecondVC 내부에 deinit 키워드와 viewDidLoad 내부에 print문을 작성하여 메모리에서 해제되는지를 확인해보자.

Note: 아래 코드에서 나오는 키워드를 모르는 분들을 위해
deinit은 인스턴스가 메모리에서 해제될 때 실행됩니다.
#function 키워드는 실행되고있는 주체의 이름을 출력합니다.


final class SecondVC: UIViewController {

  // codes ...
  // 이하 생략
  
  // deinit 키워드 추가
  deinit {
    print("SecondVC Deinit")
  }
  
  override func viewDidLoad() {
    super.viewDidLoad()
    print("SecondVC: \(#function)")
    // codes ... 생략
  }
}

Deinit이 출력되지 않는 모습이 보인다.

weak을 적절하게 사용하기

위 코드 중 MyView와 SecondVC를 다시 살펴보자


final class CustomView: UIView {

  let vc: UIViewController
  init(vc: UIViewController) {
    self.vc = vc
    super.init(frame: .zero)
  }

  required init?(coder: NSCoder) {
    fatalError()
  }
}

final class SecondVC: UIViewController {

  var myView: CustomView?

  override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .red
    self.myView = CustomView(vc: self)
  }
}

우리는 이제 두 클래스가 서로 상호참조를 하기 때문에 메모리 누수가 발생하고있다는 것을 인지하고있다. 이를 해결하기 위해 이제 weak 키워드를 넣을 차례이다. 상호참조하고 있는 프로퍼티 중 한 곳에 weak이라는 키워드를 추가해보자.

// var myView: CustomView?
weak var myView: CustomView?

// or
// let vc: UIViewController
weak var vc: UIViewController?

참고로 weak을 선언하게 되면 메모리가 회수될 수 있기 때문에, letvar로, 타입은 옵셔널 형태로 정의해주어야한다.

class MyView: UIView {

    weak var vc: UIViewController? // weak으로 설정, 옵셔널 형태로 정의
    init(vc: UIViewController) {
        self.vc = vc
        super.init(frame: .zero)
    }
    // more codes...
}

영원히 메모리에서 제거되지 않던 코드가 weak 설정을 해주었다는 이유로 시스템에 의해 임의로 제거가 되어 상호 참조로부터 벗어날 수 있게 되었다.
그럼 weak가 어떠한 역할을 하기에 상호참조를 벗어날 수 있는 것인지, 다음 글에 좀 더 자세하게 살펴보도록 하겠다.

profile
블로그 이전: https://www.whitehyun.com

0개의 댓글