Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 클린 코드
- Xcode
- 리팩토링
- map
- Protocol
- rxswift
- 리펙토링
- 리펙터링
- ribs
- clean architecture
- Human interface guide
- uitableview
- MVVM
- 스위프트
- swift documentation
- swiftUI
- UITextView
- ios
- HIG
- SWIFT
- collectionview
- Refactoring
- combine
- UICollectionView
- 애니메이션
- RxCocoa
- Clean Code
- Observable
- tableView
- uiscrollview
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] 2. 추상화 - 제네릭스로 추상화하기 (#GenericTableView) 본문
* 추상화하기 목차: https://ios-development.tistory.com/1627
제네릭스의 목표
- 공통화, 추상화, 코드의 유연성
- 구체적인 내용은 이전 포스팅 글 참고
제네릭스 훑어보기 - 함수에 적용
- 함수에 적용 - 함수 이름 오른쪽에 꺽쇠를 사용하여 타입 표현
before)
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
let temporaryA = a
a = b
b = temporaryA
}
after)
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
제네릭스 훑어보기 - extension에 활용
ex) swift 내부에 정의된 Optional 타입
@frozen public enum Optional<Wrapped> : ExpressibleByNilLiteral {
case none
case some(Wrapped)
...
}
extension에서 제네릭스 parameter에 받는 경우)
extension Optional where Wrapped : Equatable {
public static func != (lhs: Optional<Wrapped>, rhs: Optional<Wrapped>) -> Bool
}
extension에서 제네릭스와 함께 computed property 구현)
extension Optional where Wrapped == Bool {
var isNilOrFalse: Bool {
return !(self ?? false)
}
}
제네릭스로 추상화하기
- 제네릭스는 클래스, 구조체, 함수 등에 사용이 가능
ex) UITableView에 5개의 데이터가 있는 예제 코드
제네릭스를 안쓰는 경우 보통 아래처럼 구현)
class CustomCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .lightGray
textLabel?.font = UIFont.boldSystemFont(ofSize: 18)
textLabel?.textColor = .blue
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ViewController: UIViewController {
let tableView = UITableView()
let data = [1, 2, 3, 4, 5]
override func viewDidLoad() {
super.viewDidLoad()
tableView.frame = view.bounds
tableView.dataSource = self
tableView.register(CustomCell.self, forCellReuseIdentifier: "Cell")
view.addSubview(tableView)
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
let number = data[indexPath.row]
cell.textLabel?.text = "\(number)"
return cell
}
}
- 제네릭스를 사용하여 추상화하고 사용하는곳에서는 간편하게 사용하게끔 구현하면?
- 아래처럼 GenericTableView를 구현한 경우, Cell을 register하고 cellForRowAt같은 dataSource를 따로 구현하지 않고 단순하게 사용이 가능 (+재활용성 높은 코드)
class ViewController: UIViewController {
let tableView = GenericTableView<CustomCell2>()
let data = [1, 2, 3, 4, 5].map(String.init)
override func viewDidLoad() {
super.viewDidLoad()
tableView.frame = view.bounds
view.addSubview(tableView)
tableView.updateData(data)
}
}
GenericTableView 구현
- GenericTableView에서는 Cell데이터에 관한 중복로직인 register(_:forCellReuseIdentifier:), numberOfRowsInSection, cellForRowAt과 같은 함수들을 내부에서 처리하도록 할 것
(불필요하게 tableView를 쓸때마다 중복으로 많이 들어가있는 코드)
tableView.register(CustomCell.self, forCellReuseIdentifier: "Cell")
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
let number = data[indexPath.row]
cell.textLabel?.text = "\(number)"
return cell
}
}
- 이 코드를 내부에서 처리하기 위해서 CellType을 제네릭스로 만들고 셀 안에서 필요한 데이터 역시도 제네릭스로 구현
- 셀의 가장 기본 상태와 기능은 셀이 표출할 데이터 타입과 해당 데이터 타입을 갱신해주는 configure 함수를 프로토콜로 표현
protocol TableViewCellable where Self: UITableViewCell {
associatedtype D
func configure(_ value: D)
}
- 테이블 뷰에서는 이 타입을 제네릭스로 받으면 Cell 처리가 가능
final class GenericTableView<CellType: TableViewCellable>: UITableView, UITableViewDataSource {
}
- 내부적으로 필요한 것들
- register할때 필요한 CellType의 타입값: CellType.self
- 셀에 표출할 데이터소스: CellType.D
- register할때 필요한 cellID
private var cellType = CellType.self
private var cellDatas = [CellType.D]()
private var cellID: String {
String(describing: cellType)
}
- 이 값을을 활용하여 tableView DataSource처리하여 구현
final class GenericTableView<CellType: TableViewCellable>: UITableView, UITableViewDataSource {
private var cellType = CellType.self
private var cellDatas = [CellType.D]()
private var cellID: String {
String(describing: cellType)
}
init() {
super.init(frame: .zero, style: .plain)
dataSource = self
register(cellType, forCellReuseIdentifier: cellID)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func updateData(_ datas: [CellType.D]) {
cellDatas = datas
reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
cellDatas.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as? CellType
else { return UITableViewCell() }
cell.configure(cellDatas[indexPath.row])
return cell
}
}
- 이 tableView를 사용하는 커스텀 셀을 구현할 경우 단순히 TableViewCellable만 conform하여 구현
class CustomCell2: UITableViewCell, TableViewCellable {
typealias D = String
private let titleLabel = {
let l = UILabel()
l.textColor = .black
l.font = .systemFont(ofSize: 24)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
func configure(_ value: String) {
titleLabel.text = value
}
...
}
- 사용하는쪽)
class ViewController: UIViewController {
let tableView = GenericTableView<CustomCell2>()
let data = [1, 2, 3, 4, 5].map(String.init)
override func viewDidLoad() {
super.viewDidLoad()
tableView.frame = view.bounds
view.addSubview(tableView)
tableView.updateData(data)
}
}
완료)
읽어보면 좋은 글) 프로토콜에 제네릭스 사용하여 추상화하기 포스팅 글
* 전체코드: https://github.com/JK0369/ExAbstract_1
* 참고
- https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/
'iOS 응용 (swift)' 카테고리의 다른 글
[iOS - swift] any vs some 키워드 (dynamic dispatch, static dispatch, type erase) (1) | 2024.02.02 |
---|---|
[iOS - swift] 3. 추상화 - 프로토콜에 제네릭스 사용하는 추상화 이해하기 (1) | 2024.02.01 |
[iOS - swift] protocol에 사용되는 any 키워드 개념 (existential any, existential type) (2) | 2024.01.30 |
[iOS - swift] 1. 추상화 - 개념과 목적 (1) | 2024.01.29 |
[iOS - swift] 추상화하기 목차 (0) | 2024.01.28 |
Comments