티스토리 뷰

 

 

 

TableView에서 커스텀 셀을 다룰 때 awakeFromNib, init, prepareForReuse의 역할이 비슷해서 언제 써야 하는지 헷갈릴 때가 많았다. 따라서 오늘은 해당 개념들을 다시 정리하고 왜 이런 구조가 필요한지 분석해보고자 한다.

 

(제가 공부하기 위해 작성한 것이므로 여러 개념들이 나올 수 있으며 틀린 내용이 있다면 알려주세요!)

 


✏️ 재사용 메커니즘

TableView에서 1000개의 데이터를 보여주는 리스트가 있다고 가정했을 때 이 1000개의 셀이 모두 메모리에 생성되는 방식일까? 

 

 

 

 

디자인에 따라 다르겠지만 아이폰 설정 창의 셀 크기를 참고해보면 화면에 보이는 셀은 10개 정도의 셀인데, 나머지 셀을 미리 만들어두는 건 메모리 낭비를 발생한다.

 

심지어 몇만개의 셀이 존재할 수도 있는데 스크롤할 때마다 새로운 셀을 생성하게 된다면 성능 저하로 인해 앱이 강제종료될 가능성도 있다.

 

따라서 UIKit은 화면에서 나타나지 않는 셀을 버리는게 아니라 재사용 큐에 보관했다가 이 셀들을 컨베이어 벨트처럼 재사용하는 구조로 활용하고 있다. 이게 재사용 메커니즘이다!

 

 

전체 흐름을 보자면 최초로 화면에 셀이 8개 정도 표시되고 있을 때 사용자가 스크롤하면 최초 셀은 위로 넘어간다. 이때 없어지는 게 아니라 재사용 큐에 보관이 되어 있고 다시 화면 아래에서 해당 큐에 보관이 되어 있던 셀들이 데이터만 교체한 상태로 다시 등장하게 되는 것이다.

 

이때 화면에 8~9개의 셀이 표시되더라도 사용자가 스크롤 했을 경우를 대비해 재사용 가능한 셀이 화면에 표시되는 셀 9개 + a로 추가적으로 셀 몇 개가 더 생성된다. 해당 포스팅에서는 13개의 셀이 재사용된다고 가정해 보겠다.

 

그러면 1000개의 셀을 만들지 않아도 13개 정도의 셀만으로도 무한스크롤이 아무런 성능 저하 없이도 가능하게 된다!

 

 

 

 

우선 스토리보드 기반으로 테이블뷰에 재사용 가능한 셀을 만들려면 XIB 파일을 선택해야 한다.

 

1️⃣ XIB란?

 XML Interface Builder의 약자이며 Xcode에서 제공하는 XML 기반의 유저 인터페이스 파일

 

 

커스텀 셀을 만들고자 할 때 XIB 파일을 선택하면 awakeFromNib이라는 메서드가 자동으로 코드에 추가된 형태로 파일이 생성된다.

분명 XIB 파일이 실행되어야 하는데 왜 awakeFromXib가 아니라 awakeFromNib 일까?

 

 

🎯 XIB와 NIB의 차이

 

NIB과 XIB의 차이점에 대해서 먼저 이해를 해보고자 한다.

 

XIB는 앞서 말했듯이 xml interface Builder의 축약어이며 NIB은 Next Interface Builder의 약자이다. 둘 다 모두 유저 인터페이스를 정의하기 위한 파일이지만 저장되는 방식이 조금 다르다. 

 

XIB는 개발자가 직접 생성하고 편집할 수 있는 XML 기반 설계 파일이지만 NIB은 바이너리 형태로 저장되는 파일이다. 따라서 Nib파일은 사람이 직접 수정할 수 없다.

 

컴퓨터는 이진수를 이해하기 때문에 이 XML기반 형태의 파일을 직접 사용하기엔 효율적이지 않다. 그래서 XIB 파일이 빌드되거나 앱을 배포할 때는 바이너리 형태인 Nib 파일로 변환된다.

 

즉 iOS 앱은 데이터를 가져올 때 Xib를 직접 로딩하는 게 아니라 nib 파일을 로딩하기 때문에 메서드 이름도 awakeFromNib이다!

 

    
    // SearchTableViewCell
    override func awakeFromNib() {
        super.awakeFromNib()
    }
    
    ...
    
    // -TableViewController        
    override func viewDidLoad() {
        super.viewDidLoad()
        // 테이블 뷰 컨트롤러에 셀이 없고, XIB로 셀 구성된 걸 가져올 때 필요한 코드
        tableView.register(UINib(nibName: "SearchTableViewCell", bundle: nil), forCellReuseIdentifier: "SearchTableViewCell")
    }

 

해당 코드를 실제 테이블뷰에서 어떻게 사용되는지 확인해 보자.

 

XIB로 셀을 구성할 때는 테이블 뷰에서 해당 셀을 등록하는 과정이 필요하다. 이때 UINib 타입의 코드를 사용해서 XIB 파일이 빌드 시점에 NIB 파일로 변환되고 테이블 뷰에서 객체를 가져올 때 nib 파일에 있는 identifier를 가져오겠다고 선언한다는 뜻이다.

 

 

그럼 awakeFromNib 메서드가 어떻게 동작하고 어떤 역할을 하는걸까?

 

2️⃣ awakeFromNib

nib파일을 불러올 때 최초 한번 호출되는 초기 설정 단계
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func prepareForReuse() {
        
    }

 

awakeFromNib은 NIB파일로 저장된 정보들을 읽어서 실제 객체들을 메모리에 생성하고 서로 연결할 때 호출되는 메서드이며 내부적으로 init(coder: )가 호출되고 내부 속성이 초기화된다. 이후에 awakeFromNib이 실행되어 nib 파일 내의 다른 객체들과 모든 연결이 outlet으로 설정되었다는 것을 보장하면서 셀 초기화 작업을 수행한다.

 

 

📌  awakeFromNib도 결국 셀이 생성되기 전에 초기화를 시켜주는 역할이면 init이랑 비슷한데 왜 init을 사용하지 않고 awakeFromNib을 사용할까?

 

그건 둘 다 초기화된다는 점은 같지만 실행 시점이 다르기 때문이다.

init은 메모리에 객체를 생성해서 객체가 존재할 수 있는 최소 조건을 만드는데 awakeFromNib은 nib을 불러오고 IBOutlet 연결이 된 상태에서 마무리 단계로 생성되기 때문에 객체가 처음 생성될 때 딱 한 번만 호출되고 스크롤로 셀이 재사용될 때는 재사용 메커니즘에 따라 awakeFromNib이 더 이상 호출되지 않는다. 위의 예제로 보면 13개의 셀에서만 awakeFromNib이 실행된다.

 

📍그럼 코드베이스로 셀을 작성할 때는 Xib파일을 사용하지 않는데 awakeFromNib이 필요할까?
: 정답은 아니다!
xib파일이 빌드 시점에 nib파일로 변환된다고 했는데 awakeFromNib은 바이너리 형태인 nib파일에 저장된 걸 꺼내보는 시점에서 생성된다. 하지만 코드베이스 형태에선 XIB와 NIB이 존재하지 않게 되기 때문에 awakeFromNib은 호출되지도 않고 필요하지도 않다.

따라서 이런 코드베이스 UI에서는 객체 설정과 UI 설정 모두 init 단계에서 처리한다.

 

3️⃣ prepareForReuse

셀이 재사용되기 전에 반복 호출되는 초기화 단계

 

앞서 설명했듯이 awakeFromNib은 셀이 화면에서 보이는 만큼 실행되고 특정 시점에서는 더 이상 실행되지 않는다. 13개의 셀에서만 awakeFromNib이 실행된다면 이미지 다운로드처럼 비동기 작업이 필요한 시점에서는 셀 데이터가 꼬이지 않도록 어떻게 제어할 수 있을까?

 

그때 사용하는 게 prepareForReuse이다!

 

override func prepareForReuse() {
    self.prepareForReuse()
    
    // 셀 초기화
    titleLabel.text = nil
    imageView.image = nil
    ...
}

 

셀이 100개가 있든 1000개가 있든 계속해서 셀이 재사용될 때마다 항상 prepareForReuse가 호출된다.

그래서 보통 초기화 코드를 prepareForReuse에 작성하여 셀이 재사용되기 전에 이전 데이터를 정리하고 깔끔한 상태로 재사용될 수 있도록 구성이 된다.

 

🤔 Configure 과의 차이점

 

테이블 뷰나 컬렉션 뷰에 대해서 공부를 많이 해보았다면 Configure과의 차이점이 궁금할 수 있다. Configure는 개발자들이 셀에 데이터를 주입하고 UI를 정의하기 위한 메서드로 주로 활용한다.

 

    // CityCollectionViewCell
    func configure(_ row: City) {
        cityNameLabel.text = row.city_name
        subNameLabel.text = row.city_explain
        cityImage.setImage(url: row.city_image)
    }
    ...
    
    // CityViewController
        func collectionView(
        _ collectionView: UICollectionView,
        cellForItemAt indexPath: IndexPath
    ) -> UICollectionViewCell {
        let cell =
            collectionView.dequeueReusableCell(
                withReuseIdentifier: CityCollectionViewCell.identifier,
                for: indexPath
            ) as! CityCollectionViewCell

        cell.backgroundColor = .white
        let row = filterInfoCity[indexPath.item]
        cell.configure(row)

        return cell
    }

 

코드를 통해서 살펴보자!

 

configure를 사용하는 이유는 결국 외부 데이터를 셀 안에 전달해주고자 할 때 사용된다. 따라서 ViewController 안에 셀을 지정해주는 코드에 cell.configure() 를 통해서 데이터를 주입할 수 있다. 

 

물론 cellForRowAt 메서드에서 UI 속성을 다 지정하고 데이터를 넘겨줄 수 있지만 Configure를 통해 코드를 분리함으로써 CityViewController는 데이터를 선택만 하고 안에 내용이 어떻게 보이는지에 대해서는 모르기 때문에 조금 더 역할과 책임에 대해 나눠지게 된다.

 

그래서 어떻게 보면 configure도 결국 셀이 세팅되기 전에 UI를 그리고 데이터를 표시해 주는 함수고, prepareForReuse도 셀이 재사용되기 전에 초기화를 해주는 메서드이기 때문에 역할이 혼동되기 쉽다.

 

configure는 결국 cellForRowAt에서 데이터를 주입받아서 표시해 주기 때문에 보통 셀을 완성하는 단계에서 실행된다. 반면 prepareForReuse는 셀이 재사용될 때 다음 재사용될 때를 대비해서 셀을 초기화해주는 역할이다.

 

따라서 셀이 재사용될 때 prepareForReuse로 초기화 된 셀을 configure가 덮어씌우는 구조! 라고 생각하면 이해하기 쉬울 거 같다.

 

📍 주의할 점!
항상 prepareForReuse가 실행되고 configure가 실행되는 것은 아니다! (실행시점이 고정된 게 아님!)

처음에 빌드를 하고 나서는 셀이 스크롤되지 않은 상태이기 때문에 prepareForReuse가 호출되지 않고 configure가 호출된다. 그러다가 사용자가 스크롤을 하면 셀이 재사용 큐에 들어가게 되는데 이 시점부터는 prepareForReuse가 실행되고 나서 configure가 실행되게 된다!

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함