Closure 톺아보기
클로저는 Named Closure와 Unnamed Closure로 나뉜다.
우리가 흔하게 사용하는 함수는 Named Closure다.
둘다 클로저에 포함되지만 우리가 통상적으로 부르는 클로저는 Unnamed Closure를 말한다.
// 1. Named Closure
func doSomthing() {
print("do something!!!")
}
// 2. UnnamedClosure
let doSomething = { print("do something!!!") }
결국 클로저와 함수는 그게 그것이기 때문에 1급객체의 성질을 가지고 있다.
즉 클로저도 아래의 성질을 갖고있다.
- 변수나 상수에 대입할 수 있다.
- 함수의 인자값으로 클로저를 전달할 수 있다.
- 함수의 반환값으로 클로저를 사용할 수 있다.
클로저 표현식
unnamed Closure , 익명함수 (이하 클로저로 설명) 는 헤드 부분과 바디부분으로 나누어진다.
이걸 구분하는게 in이라는 키워드이다.
클로저의 사용법
함수의 사용법
// 기본형
func doSomething(firstParam: String, secondParam: String) -> Void {
}
// 위 함수에서 Void 생략
func doSomething(firstParam: String, secondParam: String) {
}
// 파라미터가 없다면?
func doSomething() {
}
클로저의 사용법
파라미터와 리턴타입의 유무에 따라 조금씩 다르다.
// ParamX, ReturnTypeX
let doSomething = { () -> () in
print("someThing")
}
// 사용
domething()
// ParamO, ReturnTypeO
let doSomething = { (firstParam: String ) -> String in
return "Something is\(firstParam)"
}
// 사용
doSomething("work") // doSomething( firstParam: "work") //ERROR!!!!
// Something is work
1급객체의 특징
1. 변수나 상수에 대입할 수 있다.
클로저 자체를 변수에 대입하며 동시에 작성이 가능하다.
let closure = { () -> () in
print("Closure")
}
let closure2 = closure
2. 함수의 파라미터 값으로 클로저를 전달할 수 있다.
클로저의 타입이 () -> () 와 같을 때
파라미터의 타입을 명시하는 부분에 명시한다.
func doSomething(closure: () -> ()) {
closure()
}
위 경우처럼 함수를 파라미터로 받는 함수가 있다.
이걸 아래처럼 파라미터 안에 클로저를 넣어줘도 된다.
표시된 부분이 파라미터 안에 들어간 클로저다.
3. 함수의 반환값으로 클로저를 사용할 수 있다.
클로저의 타입이 () -> () 와 같을 때,
- 함수의 반환타입에 명시해 준다.
- return 에 클로저를 넣는다.
func doSomething() -> () -> () {
return { () -> () in
print("Hello world!")
}
}
클로저 실행하기
- 대입된 변수나 상수로 호출하기
let closure = { () -> String in
return "Hello world!"
}
closure()
클로저를 직접 호출하기
클로저 자체를 ()로 감싸고, 호출구문()를 한번더 추가한다.
( { () -> () in
print("Hello world!")
} )()
1. 후행클로저 Trailing Closure
- 함수의 마지막 파라미터가 클로저일 때, 이 클로저를 파라미터 값 형식이 아니라 함수 뒤에 붙여서 작성할 수 있다.
- Argument Label은 생략한다.
파라미터가 클로저 하나인 함수
func doSomething(closure: () -> ()) {
closure()
}
위 함수를 호출하려면 함수의 호출구문 ()안에
아래와 같이 해야했다.
위 함수를 호출할 때 아래와 같이 작성한다. 이렇게 파라미터 의 값형식으로 쓰인 클로저를 Inline Closure
라고 부른다.
// 함수의 파라미터 값안에 있는 클로저 Inline Closure
doSomething(closure: { () -> () in
print("Hello!")
})
각 괄호들 })
때문에 가독성이 떨어져서 해석이 쉽지않다.
이럴때, 클로저를 파라미터 값형식이 아니라 함수의 마지막에 꼬리처럼 덧붙여서 아래처럼 사용할 수 있다.
// 함수호출구문 안에 파라미터값으로 있던 클로저가 꼬리처럼 붙었다.
doSomething () { () -> () in
print("Hello!")
}
- 파라미터가 클로저 하나여서 후행클로저가 가능하다
- closure라는 Argument Label이 생략됐다.
여기서 파라미터가 클로저 하나일경우엔 호출구문도 생략가능하다
doSomething { () -> () in
print("Hello!")
}
파라미터가 여러개인 함수
아래와 같이 첫 번째 파라미터로 success라는 클로저를 받고,
두 번째 파라미터로 fail이라는 클로저를 받는 함수가 있다.
func fetchData(success: () -> (), fail: () -> ()) {
//do something...
}
위 함수를 Inline Closure로 호출하면 아래와 같다.
fetchData(success: { () -> () in
print("Success!")
}, fail: { () -> () in
print("Fail!")
})
역시 가독성이 훌륭하진않다. 이것을 탈출클로저로 작성한다면 마지막 클로저는 함수뒤로 옮길 수 있다.
fetchData(success: { () -> () in
print("Success!")
}) { () -> () in
print("Fail!")
}
그러면 success의 파라미터 값은 inlineClosure, fail의 파라미터 값은 후행클로저로 호출이 가능하다.
fetchData(success: { () -> () in
print("Success!")
}) { () -> () in
print("Fail!")
}
하지만 이렇게 파라미터가 여러개일 경우에는 파라미터를 넘겨줘야하기 때문에 함수 호출 구문()을 생략할 수 없다.
클로저 경량화
클로저는 헤드부분 () → () in
여러종류의 괄호때문에 살짝 가독성이 떨어진다. 그래서 어려워보인다. 그래서 문법을 최적화해서 클로저를 단순하게 쓸 때 사용하는 것이 경량화 문법이다.
func doSomething(closure: (Int, Int, Int) -> Int) {
closure(1, 2, 3)
}
위 함수는 파라미터값으로 클로저를 갖고 있다. 이걸 호출할 때, Inline Closure로 호출하면 아래처럼 작성할 수 있다.
doSomething(closure: { (a: Int, b: Int, c: Int) -> Int in
return a + b + c
})
이걸 경량문법으로 바꿔보자
1. 파라미터의 타입과 리턴타입을 생략할 수 있다.
위에서 파라미터에 쓰인 : Int
와 in 좌측에 있던 → Int
처럼 기입된 타입들을 아래와 같이 생략하여 작성가능하다.
doSomething(closure: { (a, b, c) in
return a + b + c
})
2. Parameter Name은 Shortand Argument Name으로 대체하고, 동시에 in키워드를 삭제한다.
Shortand Argument Name
이란 클로저 내에서 파라미터 이름대신 $를 사용해서 첫번째일경우 $0
, 두번째 파라미터일경우 $1
를 사용하는 것을 말한다.
위 1번 예제의 함수를 아래와 같이 바꿀 수 있다.
doSomething(closure: {
return $0 + $1 + $2
})
3. 단일 리턴문일 경우, return도 생략한다.
클로저 내부에 코드가 return 구문 하나뿐이라면 아래와 같이 생략가능하다.
doSomething(closure: {
$0 + $1 + $2
})
4. 마지막 파라미터 값이 클로저라면 후행클로저로 작성한다.
위의 예시는 단일 파라미터지만 마지막 파라미터의 값이 클로저라면 파라미터안에 있는 클로저를 뒤로 뺄 수 있다.
doSomething() {
$0 + $1 + $2
}
또한 이미 후행클로저는 함수의 파라미터가 클로저 하나라면 함수 호출구문() 도 생략가능하다.
doSomething {
$0 + $1 + $2
}
2. @autoClosure
파라미터로 전달된 일반구문 과 함수를 클로저로 래핑(wrapping)하는 것을 말한다.
@autoClosure 어노테이션은 파라미터의 함수타입정의 바로앞에 붙인다.
func doSomething(closure: @autoclosure () -> ()) {
}
위와 같이 작성하면 이제 closure라는 파라미터는 실제로 클로저를 전달받진 않지만 클로저처럼 사용할 수 있게된다.
클로저와 다른 점은 실제 클로저를 전달하는 것이 아니기 때문에 파라미터로 값을 넘기는 것처럼 ()를 통해 구문을 넘겨줄 수가 있다.
doSomething(closure: 1 > 2)
func doSomething(closure: @autoclosure () -> ()) {
closure()
}
이 때 주의할 점은 파라미터가 없어야만한다. 리턴타입은 상관없다.
autoClosure는 지연된 실행을 할 수 있다.
일반구문은 작성되자마자 실행된다. 하지만 autoClosure로 작성하면 함수 내에서 클로저를 실행할 때까지 구문이 실행되지않는다. 함수가 실행되는 시점에 구문을 클로저로 만들기 때문이다.
autoClosure는 아직 공부가 더필요하다.
다음에 보강할 것!!
3. 탈출클로저 @escaping
지금까지 사용한 클로저들은 대부분 함수내부에서 직접실행할 때 사용하는 클로저 들이었다.
중첩함수를 리턴할 수 없고 어쩌고 저쩌고 하는 이유들이 있지만. 가장 중요한것은
내가 원하는 시점에 원하는 로직을 사용할 수 있도록 조절할 수 있다는 것이다.
이것 만으로도 엄청난 매력을 가진 클로저다.
사용방법
클로저의 파라미터 타입 앞에 @escaping 을 달아준다.
// 구현
func doSomething(closure: @escaping () -> ()) {
}
// 사용
doSomething {
closure()
}
사용할 땐 내가 원하는 상황, 시점에 후행 클로저 안에서 @escaping을 붙인 파라미터를 호출하면된다.
iOS에서 흔하게 사용하는 클로저
후행클로저
Alert을 구현하다보면 마주치는 후행클로저이다.
confirmAction에 담긴 버튼을 클릭했을 때, 이후에 실행되는 클로저 이다.
let alert = UIAlertController(title: "", message: "종료하시겠습니까.", preferredStyle: .alert)
let confirmAction = UIAlertAction(title: "확인", style: .default) { _ in
self.dismiss(animated: true)
}
alert.addAction(confirmAction)
self.present(alert, animated: false, completion: nil)
completion / Completion handler
위와 똑같은 방법인데 특별한 시점을 위해 자주 사용하는 탈출클로저이다.
함수를 구현할 때, 파라미터명을 completion 이나 completion handler로 자주 작성한다.
주로 저녁함수를 빼서 사용하거나, API통신할 때 특정시점을 위해 사용한다.
아래와 같이 이미지를 다운로드 성공, 실패할 때 UIImage를 전달할 때도 사용한다.
public struct FileDownloader {
/// 이미지 다운로드 함수
/// - Parameters:
/// - url: 이미지 URL
/// - completionHandler: true : 이미지 다운로드 성공, false : 이미지 다운로드 실패(with UIImage())
static public func downloadImage(fromUrl url: String, completionHandler: @escaping (Bool, UIImage) -> Void) {
guard let imageUrl = URL(string: url),
let imageData = try? Data(contentsOf: imageUrl),
let image = UIImage(data: imageData) else {
ErrLog("Convert url to Data OR data to Image FAIL")
completionHandler(false, UIImage())
return
}
completionHandler(true, image)
}
}
아래와 같이 사용한다.
API를 이용할 때도 많이 사용한다.
성공이나 실패를 탈출클로저에 담아 사용가능하다.
func getDataReturnData(url: String, successHandler: @escaping (_ resultData: Data?)-> Void, errorHandler: @escaping (_ error: Error)-> Void) -> Void {
let session = URLSession.shared
if let reqUrl = URL(string: url) {
session.dataTask(with: reqUrl) { (data, response, error) in
if error != nil {
errorHandler(error!)
} else {
guard let resultdata = data else {
successHandler(nil)
return
}
successHandler(resultData)
}
}.resume()
+++++
정말 다양하게 사용이 가능하다.
작성하다보니 생각보다 별거 아니라는 생각이 들었는데
어렵다 ㅋㅋㅋ… 특히 autoClosure는… 저만 못 봤나요.. 언제쓰지….
'모바일앱 > Swift' 카테고리의 다른 글
Swift - 배열의 특정 두 요소를 교환하기 (0) | 2024.07.31 |
---|---|
Swift - 참조(Strong, weak, unowned) (0) | 2024.06.08 |
swift signum() 메서드 이용하기 (0) | 2024.05.30 |
swift Array - 빠르게 빈 배열 만들기 (0) | 2023.01.05 |
1급 객체 (0) | 2022.11.27 |
프로퍼티 옵저버(willSet / didSet) - 프로퍼티(2.5) (0) | 2022.11.25 |
연산프로퍼티 - 프로퍼티(2) (0) | 2022.11.22 |
저장프로퍼티 - 프로퍼티(1) (0) | 2022.11.21 |