조금이라도 더 깔끔하게 Cell 등록, 초기화하기

RiverWindowHyeok·2024년 3월 20일

안녕하세요 WindowHyeok입니다. 오늘은 Protocol과 extension을 활용해서 깔끔하게 Cell을 재사용하는 방법에 대해 간단히 설명드리고자 합니다.

일반적인 Cell 사용 방식

  1. 뷰(table, collection)에서 register(_:forCellReuseIdentifier: 메서드를 사용하여 Cell 등록

collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "등록할 cell을 구별할수 있는 식별자 이름")
  1. delegate의 cellforRowAt: 메서드에서 dequeueReusableCell(withIdentifier:for:) 메서드 사용하여 cell 가져와서 사용
extension MainReviewViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: ReviewListTableViewCell.identifier, for: indexPath) as! ReviewListTableViewCell
        return cell
    }
}

기존 방식의 문제점

일반적으로 뷰(table, collection)에서 우리는 보통 위의 코드와 같은 방식으로 cell을 초기화 하여 사용합니다.
이러한 방식으로 사용하게 될 경우
1. cell의 reuseIdentifier를 설정하는 과정에서 휴먼 에러가 자주 발생
2. 개인적으론 자주 똑같은 패턴으로 쓰다 보니 위처럼 작성해서 쓰는것도 매우 번거롭게 느껴짐..(지극히 개인적)
3. 여러 팀들의 코딩 컨벤션을 참고하여 한줄당 글자수 120개를 지키고 있는데, 기존 방식의 경우 글자수를 지키지 못해 강제로 줄을 넘겨 띄어쓰기를 하게 되면서 코드 가독성이 떨어짐.

  • 글자수를 지키기 위한 강제적인 줄바꿈..
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(
            withIdentifier: ReviewListTableViewCell.identifier,
            for: indexPath
        ) as! ReviewListTableViewCell
        return cell
    }

문제점을 해결한 새로운 방식

Protocol과 Extension을 활용하여 Cell 등록시 휴먼 에러 방지

protocol Reusable {
    static var reuseIdentifier: String { get }
}

extension Reusable {
    static var reuseIdentifier: String {
            return String(describing: self)
        }
}

extension UITableViewCell: Reusable {}

extension UICollectionViewCell: Reusable {}
  • Protocol을 사용하여 공통적으로 사용할 reuseIdentifier 변수를 만들기
  • Protocol 과 Extension을 같이 활용하여 기본 구현을 작성하여 사용하는 Cell에서 일일히 구현할 필요 없도록 작성.
  • String(:describing) 메서드를 활용하여 Reusable Protocol을 채택한 클래스 타입 이름으로 만들어진 식별자 생성.
  • 같은 이름의 클래스 타입 생성은 불가능하기 때문에 reuseIdentifier를 사용하여 Cell을 등록하는 과정에서 발생하는 휴먼 에러 방지
  • 기본적으로 쓰이는 Cell 타입들이 Reusable Protocol을 채택하게 함으로써 추가적인 구현 필요 없이 바로 접근하여 사용 가능.(사용시 매우 간편!)

reuseIdentifier를 사용하여 간단하게 Cell 등록 및 사용

extension UITableView {
    
    func register<T: UITableViewCell>(_: T.Type) {
        register(T.self, forCellReuseIdentifier: T.reuseIdentifier)
    }
    
    func cell<T: UITableViewCell>(_: T.Type, indexPath: IndexPath) -> T {
        guard let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
            print("cell이 정상적으로 생성되지 않았습니다. register 부분을 확인해야함")
            return T()
        }
        return cell
    }
}

기존의 문제점을 해결한 간단하게 Cell 을 재사용할수있도록 익스텐션과 제너릭을 사용해서 작성해본 예시.

  • 파라미터에 이름에 와일드 카드 패턴을 사용하여 호출시 매개변수 이름을 작성할 필요 없이 인자값만 넣어줌으로써 작성되는 글자수를 줄여 간단하게 Cell을 등록하고 사용.
  • Cell 생성 실패시 print를 사용하여 로그에 찍혀 알수 있도록 구현.
  • 기존 사용방식은 메서드 사용시 리턴값 형식이 UITableViewCell이라 as 연산자를 활용한 타입캐스팅이 필요하지만,
    이 경우 타입 파라미터인 T의 타입으로 바로 Cell을 리턴해주기 때문에 따로 타입캐스팅 할 필요가 없어 더욱더 간단하게 사용 가능.

코드 정리

//1. 식별자 사용을 위한 Reusable Protocol
protocol Reusable {
    static var reuseIdentifier: String { get }
}

extension Reusable {
    static var reuseIdentifier: String {
            return String(describing: self)
        }
}
//2. UITableViewCell 이 Reusable Protocol 채택
extension UITableViewCell: Reusable {}

//3. UITableView에서 Reusable Protocol을 채택한 UITableViewCell의 reuseIdentifier를 활용하여 Cell을 간단하게 사용할수 있는 register, cell 메서드 작성
extension UITableView {
    
    func register<T: UITableViewCell>(_: T.Type) {
        register(T.self, forCellReuseIdentifier: T.reuseIdentifier)
    }
    
    func cell<T: UITableViewCell>(_: T.Type, indexPath: IndexPath) -> T {
        guard let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
            print("cell이 정상적으로 생성되지 않았습니다. register 부분을 확인해야함")
            return T()
        }
        return cell
    }
}

사용되었던 코드의 흐름을 정리해보았습니다.
UICollectionView도 거의 똑같은 방식으로 구현하여 사용하고 있습니다.

비교

// 기존 Cell 사용 방식
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: ReviewListTableViewCell.identifier, for: indexPath) as! ReviewListTableViewCell
        return cell
    }
    
// 더욱더 간단하게 사용 가능. 코드가 많이 줄었습니다 😀
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.cell(ReviewListTableViewCell.self, indexPath: indexPath)
        return cell
    }

이런 단순 반복되는 코드들을 더 간단하고 효율적으로 작성될때 뭔가 뿌듯함을 많이 느낄수 있는거 같아요.
Protocol과 제네릭을 사용하니 아주 여러개의 타입에 대해서하나의 메서드로 유용하게 대응할수 있는 점을 몸으로 확실히 느낀거 같습니다.
관련 공부도 많이 되서 좋은 경험인거 같습니다. 감사합니다.

profile
기본에 충실하기

0개의 댓글