TIL은 편한 말투로 작성됩니다~
오늘 학습한 내용 정리
1. 팀원 분들과 사용할 데일리 스크럼 공간 만들기
2. Todo 앱 만들면서 데이터 전달 공부
3. 추가로 공부한 내용들 (ETC..)
1. 데일리 스크럼 공간 생성
이 부분은 오늘 공부한 내용은 아니지만, 그냥 무엇을 했는지 기록할 겸 쓰게되었다. 😅
오늘은 팀원 분들과 얘기해서 데일리 스크럼을 만들어서 하루 할 일들을 기록해서 공유하자고 의견을 냈고, 좋게 반응해 주시는 분들이 있어서 Notion을 통해 데일리 스크럼 공간을 만들었다.
아래와 같은 식으로 사용하며 다 모이면 각자 할 일을 작성하고, 서로 어느정도 목표를 달성했는지 확인할 수 있도록 만들었다.
(표 옆에 그래프로 더 보기 쉽도록 만들었었지만, 무료 Notion을 사용 중이기에 표를 1개 이상 사용하지 못해서 삭제 엔딩..)
2. Todo 앱 만들며 이해하기
Todo 앱을 만들면서 기존에 Segue 방식이 아닌 다른 방식으로, 데이터를 다른 화면에서 가져와 저장하는 방식을 공부했다.
금방 끝낼 수 있을거라 생각했는데, 모르는 내용도 꽤나 나와서 이것저것 공부하다 보니 시간이 참 빨리갔다.. 🥲
아래 ViewController가 2개 있는데, 여기에서 편의성을 위해 왼쪽의 화면을 A(ViewController), 오른쪽의 화면을 B(AddTodoViewController) 이렇게 나눠서 얘기해 보겠다.
간단히 설명하면 처음에 A화면의 Cell에는 미리 넣어둔 데이터들이 나오고, "새 할 일 추가" 버튼을 누르면 화면 B로 화면 전환이 된다.
그리고, B화면에서 textField에 값을 넣어서 "저장" 버튼을 누르면 A화면으로 돌아오게되고, A화면의 Cell에 B 화면에서 textField에 넣은 값도 같이 나오게 된다.
작성한 코드와 공부하면서 이해하려고 적어둔 주석들을 접은 글로 같이 작성해 두었다.
A(ViewController) 코드
import UIKit
// UITableViewDataSource: "데이터를 제공하는 역할" (몇 개의 셀인지, 셀에 어떤 내용인지)
// UITableViewDelegate: "셀을 눌렀을 때, 셀의 높이, 액션 등 UI 동작을 관리" (didSelectRowAt 같은 것들)
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, AddTodoDelegate {
@IBOutlet weak var tableView: UITableView!
var todos = ["강의 보기", "알고리즘 문제 풀기" ,"TIL 올리기"]
override func viewDidLoad() {
super.viewDidLoad()
// UITableView는 스스로 데이터도 모르고, 셀도 생성하지 못함.
// 그렇기에 "= self" 를 통해서 ViewController가 대신 담당해줌
tableView.dataSource = self
tableView.delegate = self
}
// 아래 2가지 tableView의 "numberOfRowsInSection", "cellForRowAt"은 UITableViewDataSource 프로토콜에 꼭 있어야 하는 규약이다.
// numberOfRowsInSection: 하나의 섹션 안에 몇 개의 셀(row)이 있을지를 정하는 함수
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return todos.count
}
// cellForRowAt: 특정 indexPath(섹션, 행 위치)에 어떤 셀(Cell)을 표시할지 정하는 함수
// 주어진 indexPath에 맞는 셀을 생성(dequeue)하거나 재사용하고, 셀의 내용을 설정해서 반환함
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// "TodoCell" 이라는 식별자(identifier)를 가진 셀을 재사용하거나 새로 생성하여 사용
let cell = tableView.dequeueReusableCell(withIdentifier: "TodoCell", for: indexPath)
cell.textLabel?.text = todos[indexPath.row] // 해당 indexPath.row에 해당하는 todo를 셀에 표시함
return cell
}
// didSelectRowAt: 테이블 뷰에서 특정 셀(row)을 사용자가 터치했을 경우에 호출되는 함수
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// indexPath가 눌렸을 경우, 애니메이션을 넣어주는 코드 (false는 애니메이션 작동 x)
tableView.deselectRow(at: indexPath, animated: true)
}
// AddTodoViewController는 이 delegate를 통해 didAddNewTodo를 호출할 예정이기에 ViewController에서 이 함수 구현이 필요함
// 또한 ViewController는 AddTodoDelegate 프로토콜을 채택했기에, 프로토콜이 요구하는 didAddNewTodo 함수를 반드시 구현해야함
func didAddNewTodo(_ todo: String) {
todos.append(todo)
// 테이블뷰는 값을 리로드해서 갱신해줘야함
tableView.reloadData()
}
@IBAction func didTapAddTodo(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// 스토리보드 안에 "AddTodoViewController"이라는 Storyboard ID를 가진 ViewController 인스턴스를 생성
// as!를 통해 AddTodoViewController 타입으로 강제 형변환을 함. (내부의 delegate 같은 기능을 쓰기 위해서)
// 만약 as!를 통해 형 변환을 안했다면, 아래의 addVC.delegate = self에서 에러가 발생함
let addVC = storyboard.instantiateViewController(identifier: "AddTodoViewController") as! AddTodoViewController
// AddTodoViewController의 delegate를 ViewController(self)로 지정하여, 할 일 추가 요청을 전달받을 수 있도록 설정
addVC.delegate = self
present(addVC, animated: true)
}
}
B(AddTodoViewController) 코드
import UIKit
// protocol: 규칙의 목록. 이 규칙을 지키겠다고 하면 이 안에 함수를 반드시 구현해야함
// 할 일을 추가하는 방법은 모르기에, 그 일을 대신할 객체(ViewController)가 필요함
// 근데 그 객체 또한 어떤 함수를 정의해야 하는지 모르기에, 어떤 함수를 구현해야하는지 protocol을 통해 약속으로 알려주는 것
protocol AddTodoDelegate: AnyObject {
// 그렇기에 ViewController에서 AddTodoDelegate를 사용할 때,
// didAddNewTodo 함수를 정의하지 않으면 에러가 발생함
func didAddNewTodo(_ todo: String)
}
class AddTodoViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
// 순환 참조가 일어나지 않도록 약한 참조를 사용
weak var delegate: AddTodoDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func didTapSave(_ sender: Any) {
guard let text = textField.text,
!text.isEmpty else {
return
}
// 위에서 textField에 받은 text를 didAddNewTodo 함수를 호출하여 값을 넘겨줌
delegate?.didAddNewTodo(text)
// 화면 닫기
dismiss(animated: true)
}
}
3. 공부한 지식들!
여기에는 공부하면서 이해 안 갔던 내용들, 대충은 알았지만 누군가가 "이게 뭔지 설명해 봐."라고 하면 잘 대답 못했을 거라 생각되는 것들에 대해서 정리해보려 한다.
앞으로 작성하는 코드들은 위에 개발을 하면서 작성한 코드의 일부분들이다.
3-1. tableView.dataSource / .delegate = self? 넌 뭐니
todo 앱을 만들면서 아래와 같은 코드가 필요한 것을 알고 작성하니 잘 작동하지만, 왜 사용하는지 몰랐던 코드였다.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// ?
tableView.dataSource = self
tableView.delegate = self
}
}
UITableView는 어떤 데이터를 보여줄지, 셀을 어떻게 구상할지, 셀을 선택할 때 어떤 동작을 할지 등. 구체적인 동작을 "스스로 결정하지 못한다." 그래서 그 필요한 정보들을 다른 객체에 "위임(delegate)" 해야 한다.
즉, self를 지정했다는 것은, 현재 ViewController가"대리인"으로서 대신해주는 것이다.
-> "tableView야, 데이터 제공하는 것과 셀 구성 등 내가 담당해 줄게(ViewController)"라고 선언하는 것과 같다.
그럼 만약, 여기서 self로 정해주지 않는다면 어떻게 될까? 🤥
그러면 tableView는 아래 항목들에 대한 정보를 알 수 없기 때문에, 아무것도 표시하지 못하게 된다.
- 데이터가 몇 개 인지 (numberOfRowsInSection)
- 각 셀을 어떻게 만들어야 하는지 (cellForRowAt)
그렇기에 아무런 셀도 표시가 되지 않을 것이다.
또한, 공부하면서 가장 이해가 안 갔다랄까.. 왜 이렇게 해뒀을까? 싶은 부분이 있었는데, 바로 "에러가 발생하지 않고 잘 작동된다."
오히려 에러라도 발생하면 "아, 뭔가 설정을 빠뜨렸구나" 하고 금방 원인을 찾을 수 있을 텐데, 에러 없이 앱이 실행되다 보니 tableView 설정이 잘못되었는지 파악하기 어려울 수 있을 것 같다.
요약하자면~
- UITableView는 스스로 데이터를 모르고 결정도 못해서, 혼자서 할 수 있는 게 없다.
- 그래서 누군가 대신 도와줘야 하고, 그 역할을 ViewController가 맡겠다고 선언하는 것이 아래의 코드이다.
tableView.dataSource = self
tableView.delegate = self
3-2. weak는 왜 붙이는 걸까?
본론부터 말하자면, "순환 참조(Retain Cycle)" 때문이라고 하는데...
(오늘은 메모리 관리까지는 공부하기는 무리라서, 오늘은 간단히만 하고 담에 Deep 하게 메모리 관리 공부하는 걸로...)
양방향 참조를 할 때 서로가 strong(강한 참조)으로 참조하게 되면, ARC가 참조 카운트를 0으로 만들 수 없어서, 두 객체가 메모리에서 해제되지 않는다. 이걸 끊기 위해 한쪽을 weak(약한 참조)로 만들면, 참조 카운트가 올라가지 않아서 해제가 가능해진다고 한다.
정리하자면, weak는 순환 참조를 끊기 위해 사용하고, 그 결과로 메모리 누수를 방지하는 것이 목적이다.
기본은 강한 참조(strong reference)이다.
var a = SomeClass()
var b = a // -> 이게 strong reference
위의 코드에서는 a도 b도 같은 객체를 가리키고 있다.
weak를 사용하지 않고, 기본적으로 선언한 모든 참조는 강한 참조(strong reference)이다.
'🖋️ TIL Journal' 카테고리의 다른 글
05.07 (수) iOS 사전 캠프 (0) | 2025.05.07 |
---|---|
05.02 (금) iOS 사전 캠프 (1) | 2025.05.02 |
05.01 (목) iOS 사전 캠프 (0) | 2025.05.01 |
04.30 (수) iOS 사전 캠프 (0) | 2025.04.30 |
04.28 (월) iOS 사전 캠프 (1) | 2025.04.28 |