ios 23일차

bin·2026년 1월 29일

회고

기본적인 요구사항만을 충족하는 프로그램은 누구나 구현할 수 있다. 그러나 사이드 이펙트를 방지하고, 최적화하며 누가 보더라도 쉽게 이해할 수 있는 코드를 구성하는 것은 매우 어렵다. 일찍 과제를 시작하여 빠르게 작업했지만, 요구사항만을 충족했다고 만족하지 않고 수정이 필요한 부분을 찾고 보완하는 것에 집중을 하고있다.

과제에 관한 기록

어제와 마찬가지로 오늘도 과제에 관한 수정, 생각해봐야할 점들에 대해 정리하는 시간을 가져보자.

11. BookChapterStackView 리팩

기존에 사용하던 로직은 버튼을 사용하여 다른 책을 표시하고자 할때 ChapterStackView를 제거하고 다시 생성했다. 이는 뷰를 삭제, 재생성 하는 것에 많은 비용을 요구받는 문제점이 있다.
그렇다면 어떻게 수정해야할까 ?

  • 구상안

    그렇다면, 뷰를 재사용해보자. ( UILabel의 개수를 비교하여, 현재의 개수보다 다음 번의 개수가 많으면 더 생성, 다음 개수가 더 적으면 isHidden을 이용하여 숨기는 방식으로 구현)

    1. 현재 뷰의 개수를 확인
    2. 데이터의 개수만큼 반복을 돌리며 현재의 개수보다 적을 경우 UILabel만 변경
    3. 데이터의 개수가 현재의 개수보다 클 경우 UILabel 새로 생성 해서 넣기
    4. 데이터가 개수가 다음 개수보다 많으면 isHidden
  • 코드
    func config(with chapters: [Chapter]) { // 수정 : 뷰를 삭제, 생성하는 것에는 많은 비용이 발생한다.
           
           // 현재 뷰의 개수, 새로 받아올 뷰의 개수
           let currentViewCount = chapterListStackView.arrangedSubviews.count
           let newViewCount = chapters.count
           
           for i in 0..<newViewCount {
               if i < currentViewCount {
                   if let label = chapterListStackView.arrangedSubviews[i] as? UILabel {
                       label.text = chapters[i].title
                       label.isHidden = false
                   }
               } else {
                   let label = UILabel()
                   label.text = chapters[i].title
                   label.textColor = .darkGray
                   label.font = .systemFont(ofSize: 14)
                   chapterListStackView.addArrangedSubview(label)
               }
           }
           
           if newViewCount < currentViewCount {
               for i in newViewCount..<currentViewCount {
                   chapterListStackView.arrangedSubviews[i].isHidden = true
               }
           }

12. 네이밍 컨벤션

  1. 함수의 네이밍은 동사부터 시작하는 것이 좋다.
func infoUpdate(with book: Book, idx: Int) // 수정 전
func updateInfo(with book: Book, at index: Int) // 수정 후
  1. delegate 메서드에도 일관된 네이밍 컨벤션이 있다.(일반적으로 이벤트를 발생시키는 타입의 이름을 prfix로 사용한다.)
func onTapExtraButton(isFolded: Bool) // 수정 전
func bookSummaryStackViewDidTapExtraButton(isFolded: Bool) // 수정 후

13. weak self

ViewController.swift의 loadbooks()라는 메소드가 존재한다. 이 메서드는 dataService.loadBooks를 호출하여 사용하는데 weak self를 사용하지 않아도 순환 참조가 발생하지 않는다고 한다.
이유가 무엇일까 ?

ViewController가 저장하고 사용하는건 DataService에서 넘겨준 데이터이지, 클로저 자체가 아니다
DataService에서 클로저를 통해 만들어낸 결과물을 클래스 내부의 변수에 담아서 ViewController에 가져다주면 그 변수를 사용해서 loadbooks를 할 경우에는 순환참조가 발생하는데,(왜냐하면 DataService의 loadbooks는 끝났는데 결과를 저장한 변수를 ViewController에서 계속 사용하고자 하니까) 현재 상황에서 ViewController는 DataService의 loadbooks에서 만들어진 결과를 받아서 따로 저장하여 사용하니까 순환참조가 발생하지 않는다.
쉽게 말해, ViewController가 저장하는 것은 클로저가 아니라 DataService가 준 데이터이다. completion의 생명은 함수가 종료되면 죽는다.

14. @escaping

DataService.swift의 loadBooks 메소드에서 completion 클로저를 매개변수로 사용하는데 초기에는 @escaping이 선언되어있지만, @escaping을 선언해주지 않아도 문제가 발생하지 않는다.
이유가 무엇일까 ?

completion 클로저가 함수가 실행되는 동안 실행되고 끝이난다. 동기적으로 함수가 실행된다는 말이다.

0개의 댓글