iOS

[iOS] loadViewIfNeeded 이거 왜 쓰는거에요?

felix-mr 2021. 8. 26. 16:08

 

안녕하세요 🙇‍♂️

 

저는 요즘 TDD관련된 강의 영상을 보면서 공부하고 있습니다.

네 암튼 그렇다고요 ㅎㅎ..

 

그런데 코드를 보다가

 

... 생략 ...

let sut = ViewController()
_ = sut.view // ??

... 생략 ...

 

이런 코드를 보게 됐습니다.

 

UnderScore를 사용한 저 코드를 That's not real code라고 말씀하시더라고요.

 

그럼 Real Code도 아닌데 왜 작성하십니까! 깔깔!

강의 영상을 해주시는 분은 외국 유튜버이시면서 시니어 개발자분이셔서 분명! 어떤 이유가 있었을텐데 말이죠🤪

 

 

그래서 직접 물어봤습니다!

첫 유튜브 댓글을 달아본건데 굉장히 설렜습니다 ㅎㅎ..

 

저는 저 코드를 왜 쓰는지? 저 코드를 썼을 때 어떤 이점이 있는지에 대해 여쭤봤습니다.

 

다행히도 금방 댓글을 달아주셨는데,

 

뷰 컨트롤러는 뷰에 처음 액세스할 때 lazy하게 로드하고 뷰가 준비되면 'viewDidLoad' 메서드를 호출합니다. 이것이 컨트롤러를 테스트할 준비가 되도록 뷰를 로드하는 방법입니다. 또 다른 방법은 `sut.loadViewIfNeeded()`를 호출하는 것입니다.

 

정리해보면,

  1. ViewController의 view는 lazy하게 로드되고 viewDidLoad는 우리가 아는대로 view가 load된 이후에 호출된다.
  2. _ = sut.view와 sut.loadViewIfNeeded()는 같은 동작을 한다!

 

요렇게 말씀해주셨습니다!

 

그럼 같이 loadViewIfNeeded의 공식문서를 확인해볼까요?

 

loadViewIfNeeded()

ViewController의 view가 아직 로드되지 않은 경우 로드합니다.

 

이 메서드를 호출하면 Storyboard에서 ViewController의 view가 로드되거나 설정된 (규칙에 따라 필요에 따라)??? 뷰가 생성됩니다.

 

 

굉장히 짧고 무슨말인지 잘 모르겠어요.

그래서 유튜브 댓글과 같이 비교해서 간단한 실험을 좀 해봤습니다!

 

let firstViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "FirstViewController")
let secondViewController = SecondViewController()

print(firstViewController.isViewLoaded)
print(secondViewController.isViewLoaded)


// 결과값
// false
// false

 

오옹! storyboard와 initializer를 사용해서 ViewController를 만들었을 때,

view는 바로 로드되지 않는것을 확인할 수 있습니다!

 

그럼 유튜버분께서 알려주신 방법을 사용해 볼까요?

 

let firstViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "FirstViewController")
let secondViewController = SecondViewController()

_ = firstViewController.view
secondViewController.loadViewIfneeded()

print(firstViewController.isViewLoaded)
print(secondViewController.isViewLoaded)


// 결과값
// true
// true

 

오오옹!! 알려주신 방법을 사용하니까 view가 로드된것을 확인할 수 있습니다!

 

view가 lazy하게 생성되는 것은 또 처음 알았네요 ㅎㅎ

 

다른 궁금증이 생겨서 또 질문드려봤습니다!

시니어분과의 질문 시간은 귀하니까 잘 사용해야죠!🎉🎉

 

 

  1. 그럼 viewController를 일반적으로 사용할 때, _ = viewController.view 또는 viewController.loadViewIfNeeded()를 호출해줘야하나요?
  2. _ = viewController와 viewController.loadViewIfNeeded()의 차이가 있나요? 그냥 취향 차이인가요?

 

 

(1) 예, 뷰 컨트롤러를 테스트에서 사용하기 전에 뷰를 로드하는 방법 중 하나를 사용합니다(뷰가 로드되기 전에 뷰 컨트롤러를 사용하려고 하면 런타임 문제가 발생할 수 있습니다(예: 아웃렛이 아직 초기화되지 않은 경우). 그러나 프로덕션에서 뷰를 수동으로 로드할 필요는 없습니다.

(2) 두 방법 모두 동일한 작업을 수행합니다. 하지만 영상을 녹화할 때 loadViewIfNeeded라는 메서드는 아직 존재하지 않았었습니다. - iOS 9에 추가되었습니다. 요즘은 'loadViewIfNeeded'를 사용하는 것이 좋습니다.

 

자 답변을 보면,

(1) 테스트에서 ViewController를 테스트할 때, viewDidLoad가 호출되게 하기 위해서 사용한다고 말씀해주셨습니다. 하지만 테스트가 아닌 일반적인 환경에서는 굳이 view를 수동으로 로드할 필요가 없다고 합니다!

 

(2) _ = viewController.view 와 viewController.loadViewIfNeeded()는 같은 동작을 한다고 말씀해주셨습니다!

 

 

오오옹!

 

결과적으로 loadViewIfNeeded()는 ViewController를 테스트할 때 필요한 메서드였습니다. 깔깔!

 

그럼 어떤식으로 사용하고 언제 사용해야하는지 간단하게 알아봅시다!

 

class ViewController: UIViewController {
 
  let tableView = UITableView(frame: .zero)
 
  private var contents: [String] = []
 
  conveniece init(contents: [String]) {
  
  }
 
  override func viewDidLoad() {
    super.viewDidLoad()
	
    tableView.dataSource = self
  }
}

extension ViewController: UITableViewDataSource {
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return contents.count
  }
  
  .. 생략 ..
}

 

이런 ViewController 친구가 있고 convenience init을 통해 contents를 넘겼을 때

tableView의 numberOfRows가 잘 작동되는지에 대한 테스트 코드를 작성해보겠습니다!

(인자로 넣어주는 contents가 numberOfRowsInSection의 return값이 되니까요!)

 

class ViewControllerTest: XCTestCase {
  
  func test_viewDidLoad_withContents() {
    let sut = ViewController(contents: ["felix1", "felix2"])
    
    XCTAssertEqual(sut.tableView.numberOfRow(inSection: 0), 2)
  }
}


// test result
// XCTAssertEqual failed: ("0") is not equal to ("2")

 

이렇게 간단한 테스트 코드를 작성해 봤습니다.

contents의 count는 분명 2로 들어갔을텐데 테스트는 실패하게 됩니다.

 

테스트가 실패한 이유는 viewDidLoad가 호출되지 않았기 때문이에요.

 

그럼 위에서 배운 loadViewIfNeeded()를 호출해보겠습니다!

 

class ViewControllerTest: XCTestCase {
  
  func test_viewDidLoad_withContents() {
    let sut = ViewController(contents: ["felix1", "felix2"])
    
    sut.loadViewIfNeeded()
    
    XCTAssertEqual(sut.tableView.numberOfRow(inSection: 0), 2)
  }
}


// test result
// test Succeeded

 

네 이제는 테스트에 통과하게 됐습니다! 깔깔!

 

 

 

 

TDD 강의를 보면서 테스트의 중요성을 더욱 느끼고 있었는데

이렇게 하나하나 공부하다보면 저도 좋은 구조의 앱을 만들어볼 수 있겠죠?

 

 

네! 아무튼 짧지만 저에겐 유용했던 게시글이었던것 같습니다! ㅎㅎ...

 

두서없이 글을 작성한 것 같은데 이해가 잘 되시려나요...

글을 읽어보시다가 궁금한게점이나 잘못된 내용이라고 생각하시는게 있으시면 댓글로 질문 부탁드립니다!

 

 

고럼 바위!👋

 

 

Reference

https://developer.apple.com/documentation/uikit/uiviewcontroller/1621446-loadviewifneeded

 

Apple Developer Documentation

 

developer.apple.com

https://www.youtube.com/c/EssentialDeveloper

 

Essential Developer

Welcome to the Essential Developer channel! We help iOS developers become black-belt senior devs and be part of the highest-paid in the world. Here you will learn professional software engineering methodologies to improve your dev skills and take your iOS

www.youtube.com