본문 바로가기
개발/iOS

[iOS/Swift] Closure에 대해 알아보자

by 마자용 2022. 2. 8.
아래 글들을 참고하며 정리했습니다
 - Closures
 - Dive in Closure

 


들어가며

클로저란 이른바 이름 없는 함수, “익명 함수"로 불리는 코드블럭입니다.

이번 게시글에서는 iOS 개발을 하며 정~말정말 많이 사용했던 문법인 클로저에 대해 탐구해보는 시간을 가지겠습니다 🤓

 


Swift 문법에서의 클로저

기본 형식은 아래와 같습니다.

{(parameters) -> ReperenceType in  // ✅ in ➜ 반환 타입과 바디를 분리
    body
}

 

함수와 비슷해보이죠?

하지만 함수는 func 키워드를 앞에 반드시 붙여줘야 하고, 이름도 가지고 있고, 저기 보이는 in 키워드도 없으므로 비슷하지만 다릅니다.
(클로저가 조금 더 포괄적)

 

함수와 구별되는 클로저의 특징을 더 살펴보도록 하겠습니다.

 1️⃣ 표현 시 축약을 많이 할 수 있습니다.

  - return, 매개변수의 이름 목록, 매개변수의 타입 등을 생략할 수 있습니다.

 2️⃣ 일급 객체입니다.

❓ 일급 객체란
  - 일급 시민이라고도 하며, 변수•상수 등으로 저장 및 전달, 매개변수로 전달이 가능한 타입
  - 반환값을 가질 수 있음

  ➡️ 따라서 함수의 마지막 전달 인자로 사용되기도 하며, 이를 “후행 클로저"라고 부릅니다. ( ⭐️)

 3️⃣ 참조형 타입입니다.

 - 값을 변화시킬 수 있습니다.

 - 참고: 클래스도 참조형 타입 (↔️ 구조체는 값 타입)

 

정리해보겠습니다 !

함수
- 독립적으로 실행됨
- 역할이 끝난 후에는 내부의 변수들도 사라짐

 ✔️ 클로저는 함수 안에 선언해놓은 것들을 함수가 끝난 후에도 사용할 수 있게 해줌

  ➡️ 클로저를 함수의 전달 인자로 활용하는 이유

 

 

구체적인 활용 예시를 보겠습니다 🤓


Completion handler

완료 + 담당자

의역하자면 어떠한 일이 끝났을 때 진행할 업무의 담당자

completion은 클로저를 함수의 전달 인자로 사용하는 대표적인 예입니다.

 

ex1) 화면전환 코드

firstVC.present(nextVC, animated: true, completion: nil)
// firstVC.present(nextVC, animated: true, completion: {print("화면전환 완료")})

이 코드 정말 많이 보셨죠?? (참고로 이 completion은 생략 가능합니다)

 

ex2) 데이터 전달 (⭐️)

클로저는 함수 종료 직후(= Called at the back)에 이벤트 처리를 할 수 있기 때문에,

Alamofire와 같은 통신 라이브러리의 서비스코드에서 정말 많이 활용됩니다.

아래는 제가 작성했던 서비스코드인데요! 적절한 예시인 것 같아 가져와 봤습니다.

import Foundation
import Alamofire

struct GetRecommendDataService
{
    static let shared = GetRecommendDataService()

    func getRecommendInfo(userId: Int,
                          completion: @escaping (NetworkResult) -> Void)
    /*
     escape closure 형태로 completion 정의
     이 함수가 종료되든 말든 상관 없이, 전달만 된다면 이후에 외부에서도 사용 가능하게 함.
     여기서 용도 ➡️ 해당 네트워크 작업이 끝날 때 completion 클로저에 결과를 담아서 호출
     그 결과는 VC에서 처리.
     */
    {
        let URL = "<https://asia-northeast3-socar-server-814e9.cloudfunctions.net/api/my/recommend>"
        let header: [String: Any] = [
            "Content-Type": "application/json",
            "userId": userId
        ]

        let dataRequest = AF.request(URL,
                                     method: .get,
                                     encoding: JSONEncoding.default,
                                     headers: header.toHTTPHeaders())

        dataRequest.responseData { dataResponse in
            // 통신이 완료 되면 클로저를 통해 dataResponse라는 이름으로 결과가 도착함

            switch dataResponse.result {    // 통신 결과물들

            case .success:
                guard let statusCode = dataRequest.response?.statusCode else {return}
                guard let value = dataResponse.value else {return}
                let networkResult = self.judgeStatus(by: statusCode, value)
                completion(networkResult)

            case .failure: completion(.pathErr)
            }
        }
    }

    private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult {
        // statusCode를 바탕으로 결과값을 어떻게 처리할지 정의하는 함수

        switch statusCode {
        case 200: return isValidData(data: data)    // 성공이라면 데이터를 가공해서 전달해야 하기 때문에 isValidData 함수로 넘겨줌

        // 나머지 -> NetworkResult형으로 반환.
        // completion 클로저에 넣어서 VC로 날리면 거기서 분기처리
        case 400: return .pathErr
        case 500: return .serverErr
        default: return .networkFail
        }
    }

    private func isValidData(data: Data) -> NetworkResult {

        let decoder = JSONDecoder()

        guard let decodedData = try? decoder.decode(RecommendCarDataModel.self, from: data) else {return .pathErr}
        return .success(decodedData)
    }
}

 

클로저는 보통 이런 식으로 활용됩니다.

 

그 외에도 화면 전환 시의 데이터 전달에도 사용될 수 있지만,

viewDidload()가 가장 먼저 화면에 그려진다는 점을 유의하며

데이터 전달 순서에 대해 잘 고려를 해준 뒤 사용하는 것이 좋을 것 같습니다 🤓


 

 

지금까지 클로저에 대해 알아보았습니다 📚

 

댓글