본문 바로가기

iOS/STUDY

[Swift] @escaping

iOS App Dev tutorial 을 따라하던 도중에 @escaping 이 나와서 개념을 정리해보려고 합니다.

 

@escaping을 사용한 전체 코드를 보면

ReminderListCell.swift 의 configure 함수의 파라미터에서 @escaping annotation을 사용했고,

이 configure 함수를 ReminderListViewController.swift에서 호출하고 있습니다.

 

ReminderListCell.swift

import UIKit

class ReminderListCell: UITableViewCell {
    
    // like reference
    typealias DoneButtonAction = () -> Void
    
    @IBOutlet var titleLabel: UILabel!
    @IBOutlet var dateLabel: UILabel!
    @IBOutlet var doneButton: UIButton!
    
    // private property prevents objects outside this class
    // from executing this code
    private var doneButtonAction: DoneButtonAction?
    
    @IBAction func doneButtonTriggered(_ sender: UIButton) {
        doneButtonAction?()
    }
    
    // a closure parameter needs an @escaping annotation
    // when the closure is stored and executed after the function returns.
    func configure(title: String, dateText: String, isDone: Bool, doneButtonAction: @escaping DoneButtonAction) {
        titleLabel.text = title
        dateLabel.text = dateText
        let image = isDone ? UIImage(systemName: "circle.fill") : UIImage(systemName: "circle")
        doneButton.setBackgroundImage(image, for: .normal)
        self.doneButtonAction = doneButtonAction
    }
}

 

 

ReminderListViewController.swift

import UIKit

class ReminderListViewController: UITableViewController {
    
    static let showDetailSegueIdentifier = "ShowReminderDetailSegue"
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
        if segue.identifier == Self.showDetailSegueIdentifier,
           let destination = segue.destination as? ReminderDetailViewController,
           let cell = sender as? UITableViewCell,
           let indexPath = tableView.indexPath(for: cell) {
            let reminder = Reminder.testData[indexPath.row]
            destination.configure(with: reminder)
        }
    }
}

extension ReminderListViewController {
    
    static let reminderListCellIdentifier = "ReminderListCell"
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return Reminder.testData.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: Self.reminderListCellIdentifier, for: indexPath) as? ReminderListCell else {
            fatalError("Unable")
        }
        
        let reminder = Reminder.testData[indexPath.row]
        cell.configure(title: reminder.title,
                       dateText: reminder.dueDate.description,
                       isDone: reminder.isComplete) {
            Reminder.testData[indexPath.row].isComplete.toggle()
            tableView.reloadRows(at: [indexPath], with: .fade)
        }
        
        return cell
    }
}

 

 

@escaping 을 넣어주지 않으면

closure 타입 변수인 doneButtonAction 에 파라미터로 들어온 closure 타입 변수를 넣는 곳에서 에러가 발생합니다.

Assigning non-escaping parameter 'doneButtonAction' to an @escaping closure.
Parameter 'doneButtonAction' is implicitly non-escaping.

@escaping 클로저에 non-escaping 매개변수 'doneButtonAction'을 할당합니다.
매개변수 'doneButtonAction'은 암시적으로 escape 되지 않았습니다.

 

이 뜻을 알아보자면

typealias DoneButtonAction = () -> Void

/// 생략

private var doneButtonAction: DoneButtonAction?

/// 생략

func configure(title: String, dateText: String, isDone: Bool, doneButtonAction: @escaping DoneButtonAction) {
    titleLabel.text = title
    dateLabel.text = dateText
    let image = isDone ? UIImage(systemName: "circle.fill") : UIImage(systemName: "circle")
    doneButton.setBackgroundImage(image, for: .normal)
    self.doneButtonAction = doneButtonAction
}

ReminderListCell.swift 에서 doneButtonAction 이라는 클로저를 담는 변수를 만들어 놓았습니다.

그런데 Swift 에서 함수의 파라미터로 전달된 클로저는 기본적으로 "함수 내부 안에서만" 사용이 가능하다고 합니다.

 

따라서 함수 내부에서만 사용이 가능한데, configure 함수 외부에 있는 doneButtonAction 에 클로저 값을 저장하려고 하니 에러가 발생한 것입니다.

 

위의 오류를 다시 살펴보면

Parameter 'doneButtonAction' is implicitly non-escaping.

non-escaping 이 외부에 값을 저장할 수 없다는 뜻이니 @escaping annotation 을 붙여서 외부 변수에 저장할 수 있도록 만들라는 오류였던 것입니다.

 

이렇게되면 해당 함수가 끝나도 이 클로저는 외부 변수에 남아서 실행이 가능합니다.