아래 글들을 참고하며 정리했습니다
- 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()가 가장 먼저 화면에 그려진다는 점을 유의하며
데이터 전달 순서에 대해 잘 고려를 해준 뒤 사용하는 것이 좋을 것 같습니다 🤓
지금까지 클로저에 대해 알아보았습니다 📚
'개발 > iOS' 카테고리의 다른 글
[iOS] loadView() 오류: Could not load NIB in bundle '~ (loaded)' with name '~ViewController' (0) | 2022.02.17 |
---|---|
[iOS] UITableViewCell에 유동적인 행 높이 지정하기 (동적으로 높이 조정하기) (0) | 2022.02.12 |
[iOS] GCD에 대해 알아보자 (4) | 2022.02.04 |
[iOS] Xcode의 시뮬레이터가 매우 느리게 동작할 때 해결법 (1) | 2022.01.30 |
스토리보드 없이 ViewController 불러오기 (0) | 2021.12.26 |
댓글