간단한 프로젝트를 진행하면서 TableView의 DataSource, Delegate, DataSourcePrefetching에 대한 개념과 알아보고
Cell이 어느 시점에 호출되는지 자세히 알아 보도록 하겠습니다.
먼저 아래와 같은 화면 모양의 간단한 프로젝트를 만들어 보도록 하겠습니다.
ViewController.Swift
import UIKit
class ViewController: UIViewController {
let data = Array(0..<40)
}
//MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: String(describing: DefaultCell.self),
for: indexPath)
cell.textLabel?.text = "Cell \(indexPath.row)"
print("cellForRowAt: \(indexPath.row)")
return cell
}
}
//MARK: - UITableViewDelegate
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView,
willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath) {
print("Will Display Cell : \(indexPath.row)")
}
func tableView(_ tableView: UITableView,
didEndDisplaying cell: UITableViewCell,
forRowAt indexPath: IndexPath) {
print("Did End Display Cell : \(indexPath.row)")
}
}
//MARK: - UITableViewDataSourcePrefetching
extension ViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView,
prefetchRowsAt indexPaths: [IndexPath]) {
print("prefetch : \(indexPaths)")
}
func tableView(_ tableView: UITableView,
cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
print("cancelPrefetch : \(indexPaths)")
}
}
cellForRowAt, willDisplay, didEndDisplaying, prefetchRowsAt, cancelPrefetchingForRowsAt 델리게이트 메서드들을 호출했습니다.
각 메서드들에 대한 설명은 밑에서 자세히 해보도록 하겠습니다.
DefaultCell.swift
import UIKit
class DefaultCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
print("\n-------------- [awakeFromNib] --------------\n")
}
override func prepareForReuse() {
super.prepareForReuse()
print("\n-------------- [prepareForReuse] --------------\n")
}
deinit {
print("\n-------------- [deinit] --------------\n")
}
}
DefaultCell이라는 CustomCell을 만들었고, awakeFromNib, prepareForReuse, deinit메서드을 정의 했습니다.
프로젝트를 실행해보면 아래와 같은 출력 로그가 나옵니다.
출력화면
-------------- [awakeFromNib] --------------
cellForRowAt: 0
Will Display Cell : 0
-------------- [awakeFromNib] --------------
cellForRowAt: 1
Will Display Cell : 1
-------------- [awakeFromNib] --------------
cellForRowAt: 2
Will Display Cell : 2
-------------- [awakeFromNib] --------------
cellForRowAt: 3
Will Display Cell : 3
-------------- [awakeFromNib] --------------
cellForRowAt: 4
Will Display Cell : 4
-------------- [awakeFromNib] --------------
cellForRowAt: 5
Will Display Cell : 5
-------------- [awakeFromNib] --------------
cellForRowAt: 6
Will Display Cell : 6
-------------- [awakeFromNib] --------------
cellForRowAt: 7
Will Display Cell : 7
-------------- [awakeFromNib] --------------
cellForRowAt: 8
Will Display Cell : 8
-------------- [awakeFromNib] --------------
cellForRowAt: 9
Will Display Cell : 9
-------------- [awakeFromNib] --------------
cellForRowAt: 10
Will Display Cell : 10
-------------- [awakeFromNib] --------------
cellForRowAt: 11
Will Display Cell : 11
-------------- [awakeFromNib] --------------
cellForRowAt: 12
Will Display Cell : 12
-------------- [awakeFromNib] --------------
cellForRowAt: 13
Will Display Cell : 13
-------------- [awakeFromNib] --------------
cellForRowAt: 14
Will Display Cell : 14
-------------- [awakeFromNib] --------------
cellForRowAt: 15
Will Display Cell : 15
-------------- [awakeFromNib] --------------
cellForRowAt: 16
Will Display Cell : 16
-------------- [awakeFromNib] --------------
cellForRowAt: 17
Will Display Cell : 17
-------------- [awakeFromNib] --------------
cellForRowAt: 18
Will Display Cell : 18
prefetch : [[0, 19], [0, 20], [0, 21], [0, 22], [0, 23], [0, 24], [0, 25], [0, 26], [0, 27], [0, 28]]
로그에 순서대로 찍힌 메서드들부터 천천히 알아보도록 하겠습니다.
먼저 awakeFromNib이라는 메서드가 호출되게 됩니다.
awakeFromNib
Interface Builder archive또는 nib 파일이 생성된 후 초기화 작업을 준비하는 곳.
MVC에서 ViewController를 상속받은 View가 아니기 때문에 viewDidload메서드가 없습니다.
Cell에서는 View의 역할을 하기 위해 awakeFromNib()가 존재하므로 여기서 초기화 작업을 진행할 수 있습니다.
그 다음 tableView(_:cellForRowAt:)이 호출이 됩니다.
tableView(_:cellForRowAt:)
TableView의 특정 위치에 삽입할 Cell에 대해 Data Source에 요청하는 메서드.
구현시 테이블뷰의 dequeueReusableCell(withIdentifier:for:)메서드를 통해서 지정된 indexPath로 적절하게 셀을 생성하고 구성해야 한다.
그 다음은 이름에서 알 수 있듯이 tableView(_:willDisplay:forRowAt:) 호출 된다.
tableView(_:willDisplay:forRowAt:)
테이블 뷰는 셀을 사용하여 행을 그리기 직전에 Delegate에게 이 메시지를 보낸다.
즉, Delegate가 셀 객체를 그리기 전에 사용자가 정의 할 수 있다.
지금 화면에는 Cell 0부터 Cell 18까지 19개의 화면이 보여지고 있습니다.
즉, 19개의 Cell 파일생성되면서 awakeFromNib을 호출하고, tableView(_:cellForRowAt:)에 의해 셀들이 생성(메모리에 로드됨) 되면서 호출되고, 그 이 후에 화면에 보이지기 직전에 작업을 하는 tableView(_:willDisplay:forRowAt:)가 호출되는 식으로 셀하나당 반복하게 되고 화면에 표시되는 셀의 갯수만 동작을 하게 된다!
마지막에 다 불리고 나서 prefetch가 불리는 것을 볼 수 있다.
UITableViewDataSourcePrefetching에 대해 알아보도록 하겠습니다.
UITableViewDataSourcePrefetching
TableView와 UICollectionView에 사용할 수 있는 UITableViewDataSourcePrefetching프로토콜은 iOS 10부터 사용 가능.
셀이 디스플레이에 보여지는 셀 이외의 셀의 정보를 미리 호출하여 데이터를 받아올 수 있다.
UITableViewDataSourcePrefetching은 두가지 프로토콜 메서드가 존재하고 dataSource, delegate처럼 prefetchDataSource 프로퍼티가 존재한다
public protocol UITableViewDataSourcePrefetching : NSObjectProtocol {
// indexPaths are ordered ascending by geometric distance from the table view
@available(iOS 2.0, *)
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath])
// indexPaths that previously were considered as candidates for pre-fetching, but were not actually used; may be a subset of the previous call to -tableView:prefetchRowsAtIndexPaths:
@available(iOS 2.0, *)
optional func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath])
}
tableView?.delegate = self
tableView?.dataSource = self
tableView?.prefetchDataSource = self
두가지 메서드에 대해서 자세히 살펴보면
func tableView(_ tableView: UITableView,
prefetchRowsAt indexPaths: [IndexPath]) {
print("prefetch : \(indexPaths)")
}
필수적으로 구현해야되고, [IndexPath]에 해당되는 셀에 필요한 데이터를 미리 받아오는 메서드 입니다.
주로 GCD나 Operation으로 비동기 처리 작업을 명시합니다.
10개의 셀을 미리 받아옵니다.
func tableView(_ tableView: UITableView,
cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
print("cancelPrefetch : \(indexPaths)")
}
prefetch가 필요치 않은 셀들에 대해 작업을 취소하는 메서드 입니다.
일반적으로 스크롤 방향이 바뀔때 필요에 따라 데이터 로딩이 되지 못한 것을 취소하는데 사용할 수 있고, 불필요한 작업을 취소하여 CPU 시간을 줄이는데 좋은 방법이 됩니다.
여기까지 화면을 스크롤 하지 않았을때 화면에 보이는 셀이 어떻게 호출되는지 로그를 확인했습니다.
이제 화면을 스크롤하면서 어떻게 불리는지 확인 해보겠습니다.
-------------- [awakeFromNib] --------------
cellForRowAt: 18
Will Display Cell : 18
prefetch : [[0, 19], [0, 20], [0, 21], [0, 22], [0, 23], [0, 24], [0, 25], [0, 26], [0, 27], [0, 28]]
-------------- [awakeFromNib] --------------
cellForRowAt: 19
Will Display Cell : 19
prefetch : [[0, 29]]
Did End Display Cell : 0
-------------- [prepareForReuse] --------------
cellForRowAt: 20
Will Display Cell : 20
prefetch : [[0, 30]]
Did End Display Cell : 1
-------------- [prepareForReuse] --------------
cellForRowAt: 21
Will Display Cell : 21
prefetch : [[0, 31]]
Did End Display Cell : 2
-------------- [prepareForReuse] --------------
cellForRowAt: 22
Will Display Cell : 22
prefetch : [[0, 32]]
Did End Display Cell : 3
-------------- [prepareForReuse] --------------
cellForRowAt: 23
Will Display Cell : 23
prefetch : [[0, 33]]
19번째 cellForRowAt을 확인해보면 tableView(_:didEndDisplaying:forRowAt:)호출되어Did End Display Cell이 로그에 찍힌걸 볼 수 있다.
tableView(_:didEndDisplaying:forRowAt:)
테이블뷰로부터 Cell이 화면에 사라지면 호출되는 메서드.
그 이후 20번째 셀을 보면 awakeFromNib이 호출이 안되고 prepareForReuse()이 계속 호출되는걸 볼 수 있다.
prepareForReuse()
테이블뷰 Delegate의해 재사용 가능한 셀을 준비하는 메서드
만약 셀이 재사용이 된다면 dequeueReusableCell(withIdentifier:)메서드가 리턴되기 전에 호출된다.
성능상의 이유로, 컨텐츠와 관련이 없는 셀의 속성만 재설정해야 한다.
예를 들어 alpha, editing, selection state 등
그 이유는 awakeFromNib에 의해 화면에 보이는 만큼 셀의 갯수를 생성했다. 그 이후는 큐에 넣어졌던 셀을 재사용하기때문에 더 이상 셀을 생성하지 않고 재사용하는 매카니즘을 사용한다.
즉, 재사용된 셀을 사용할때는 prepareForReuse()만 호출됩니다.
스크롤 해보면 알겠지만 스크롤 맨 마지막에 닿았을때는 지금까지 순서대로 진행됬던 로직이 반대로 진행되는걸 볼 수 있습니다.
프로젝트 소스가 있으니 복사하셔서 직접 로그를 확인하시면 좀 더 이해가 빠를거라고 생각됩니다 :]
'iOS' 카테고리의 다른 글
[iOS] Image Rendering Mode (0) | 2019.08.01 |
---|---|
[iOS] Build Configuration 설정 방법 (0) | 2019.07.31 |
[iOS] 코드사이닝, 인증서, 프로비저닝 프로파일이란? (1) | 2019.07.25 |
[iOS] 파일시스템(File System) (0) | 2019.07.25 |
[iOS] ContentOffset과 ContentInset (0) | 2019.07.25 |