AlertController는 사용자에게 몇몇 선택지를 제공하는데, 각각의 선택지를 AlertAction이라 한다.
AlertAction Style
AlertController Style
[입력하기] 버튼을 누르면 이메일 혹은 비밀번호 TextField에 커서가 띄워지도록 이렇게 만들어줄 수도 있다. UIAlertAction의 마지막 매개변수인 handler 클로저에는 사용자가 이 action을 선택했을 때 실행할 코드 묶음이 들어간다.
toBeFirstResponder 매개변수로 컨트롤을 받아오므로 이 컨트롤을 FirstResponder로 만들어주는 코드를 클로저에 넣어주면 된다.
일단 우리를 괴롭게 하는 [weak toBeFirstResponder]를 빼고 코드를 보자.
let okAction: UIAlertAction
= UIAlertAction(title: "입력하기",
style: UIAlertAction.Style.default) {(action: UIAlertAction) in
toBeFirstResponder?.becomeFirstResponder()}
UIAlertAction의 마지막 매개변수인 handler가
UIAlertAction을 매개변수로 받고 리턴타입이 Void인 클로저인 것이다.
여기서는 리턴타입이 생략된 후행클로저로 구현되어 있다.
UIAlertAction의 정의부로 이동해서 확인해보면 handler의 정의가 우리가 추측한 것이 맞다는 것을 알 수 있다.
입력하기
버튼을 누르면 이 okAction 자기자신이 handler의 action으로 넘어간다. 하지만 우리의 코드는 handler 안에서 이 action을 사용하진 않는다. toBeFirstResponder로 받아온 컨트롤을 FirstResponder로 만들어줄 뿐이다.
그럼 okAction 자기자신이 진짜 action으로 넘어오는 게 맞을까? 확인해보자. handler를 따로 빼서 함수 alertHandler로 만들어주고, 이 함수 안에서 action의 title을 로그로 확인해본다.
okAction과 cancelAction의 handler에 이 함수를 넘겨주어서 코드를 재사용했고, 호출은 self.alertHandler(_:)로 해주든 alertHandler로 해주든 상관 없이 정상동작한다. << 왜 ????
func alertHandler(_ alertAction: UIAlertAction) {
guard let actionTitle = alertAction.title else {
return
}
print("alertHandler >> \(actionTitle)")
}
private func showAlert(message: String, control toBeFirstResponder: UIControl?) {
let alert: UIAlertController = UIAlertController(title: "알림",
message: message,
preferredStyle: UIAlertController.Style.alert)
let okAction: UIAlertAction = UIAlertAction(title: "입력하기",
style: UIAlertAction.Style.default,
handler: self.alertHandler(_:))
let cancelAction: UIAlertAction = UIAlertAction(title: "취소",
style: UIAlertAction.Style.cancel,
handler: alertHandler)
alert.addAction(okAction)
alert.addAction(cancelAction)
self.present(alert, animated: true) {
print("얼럿 화면에 보여짐")
}
}
입력하기
버튼과 취소
버튼을 차례로 눌러주었고, 로그가 제대로 찍히는 모습이다.
이제 그럼 [weak toBeFirstResponder]를 추가한 원래 코드로 돌아와보자.
let okAction: UIAlertAction
= UIAlertAction(title: "입력하기",
style: UIAlertAction.Style.default) { [weak toBeFirstResponder] (action: UIAlertAction) in
toBeFirstResponder?.becomeFirstResponder() }
weak은 왜 쓰는 걸까? 순환참조에 대해 미리 알 필요가 있다.
ARC(Automatic Reference Counting)
순환참조
weak
>> https://babbab2.tistory.com/83 요기 글 정독해보기 !! <<
결국 [weak toBeFirstResponder]는, 강한 참조 순환을 방지하기 위한 클로저의 캡쳐 리스트이다.
얘기가 많이 샜다. 다시 첫 코드캡쳐로 돌아가자. 마지막 줄에 present 메소드도 후행 클로저를 사용하는데, completion이라는 이름을 갖는 매개변수에 클로저를 넣어준 것이다. 마찬가지로 이 모달 애니메이션을 띄운 후에 실행할 코드 묶음이 들어간다.
매개변수 이름이 handler
, completion
인 것들은 대부분 클로저를 전달받는다.
JSON 파일을 Asset에 등록하여 사용할 때 필요한 클래스이다.
이렇게 생긴 Questions.json
파일을 가져와보자.
프로젝트에 JSON 파일을 Asset에 등록할 때, dataset 확장자로 삽입해야 한다. 우리의 Questions.dataset
에는 Contents.json
과 Questions.json
이 들어있다. NSDataAsset 클래스를 이용하면 이 dataset을 코드에서 가져와 사용할 수 있다.
Question.swift
import Foundation
import UIKit
struct Question: Codable {
let d: String
let i: String
let s: String
let c: String
}
extension Question {
static var all: [Question] = {
guard let dataAsset: NSDataAsset = NSDataAsset(name: "Questions") else {
return []
}
let jsonDecoder: JSONDecoder = JSONDecoder()
do {
return try jsonDecoder.decode([Question].self, from: dataAsset.data)
} catch {
return []
}
}()
}
all 변수에는 [Question] 배열을 리턴하는 함수를 저장한다. 이 함수를 들여다보자.
Asset을 가져올 때는 파일이 없을 수도 있기 때문에 옵셔널 바인딩을 해 주는 것이 안전하다. guard let 구문
으로 이를 수행했다. Asset을 무사히 가져왔다면 JSONDecoder 인스턴스를 만들어서 decode의 from 인자에 넣어준다. decode 정의부를 보면 throws로 구현되어 있다. 디코딩 도중에 에러가 발생할 가능성을 내포한 메소드이므로 decode를 사용할 때 try를 반드시 같이 써주어야 한다.
JSON Data의 Key값(d, i, s, c)이 Codable을 따르는 구조체 Question의 프로퍼티명(d, i, s, c)과 동일하면 그 변수의 값에 Value를 파싱한다. 이렇게 1대1 매칭이 되어야만 에러 없이 사용할 수 있다.
CodingKey
하지만 프로퍼티명을 Key값과 다르게 사용하고 싶을 경우, 방법은 있다.
struct Coffee: Codable {
var drink: String
var price: Double
var brand: String
enum CodingKeys: String, CodingKey {
case drink = "nameOfBeverage"
case price
case brand = "nameOfBrand"
}
}
Codable 타입은 CodingKeys
라는 이름을 가진 nested 열거형을 정의할 수 있다.
CodingKey
프로토콜을 채택해야 한다.case drink = "nameOfBeverage"
처럼 key값을 String 타입으로 매칭해주면 된다.