# 1급 객체 클로저 Closure
오늘의 글..아주 길다.. 아주 방대하다.. 하지만 Velog.. 글자에 칼라 하나 넣기 아주 번거로워.. 흑
🍫 노션으로 가기(강추ㅎ)
#클로저 Closure ?
함수와 메서드는 클로저에서 시작 한다.
func _____
로 정의되어 있는 함수와 메서드도 엄밀히 말하면 클로저이지만(named closure
),
보통 클로저라고 언급할 때는 이름이 없는, unnamed closure
를 말한다.
✔︎ closure
**이름 없는 함수 ⇒ 클로저**
- 이름 있는 함수 ⇒ 함수/메서드
따라서 클로저도 함수이기 때문에, 함수형 프로그래밍이 가능하다.
이는 스위프트 함수가 1급 객체(1st class object)의 특성을 가지고 있기 때문인데..
✔︎ **1급 객체
** ?
1등 시민처럼, 많은 권한을 부여 받은 것!
얼마나 많은 것을 할 수 있는데?
1. 변수 또는 상수에 `함수`를 담을 수 있다.
2. 인자(파라미터)로 `함수`를 전달할 수 있다.
3. 반환값(리턴벨류)으로 `함수`를 전달할 수 있다.
함수는, 클로저에서 시작한다. 따라서,
1. 변수 또는 상수에 `클로저`를 담을 수 있다.
2. 인자(파라미터)로 `클로저`를 전달할 수 있다.
3. 반환값(리턴벨류)으로 `클로저`를 전달할 수 있다.
라는 말이 된다.
# 1급객체 함수
👉 (String) -> Bool
은 Function Type
//(String) -> Bool
let tupleExample = (1, true, "dkd", 3.3)
tupleExample.0 //.을 통해서 접근
let tupleExample: (Int, Bool, String, Double)
이렇게 튜플 타입이 유동적인 것처럼, 함수의 타입도 유동적!
func hello(nickname: String) -> String {
return "저는 \(nickname)입니다."
}
hello
playground에 함수 이름만 입력하면, 함수의 타입을 알 수 있다
→ String) -> String
func hello(nickname: String) -> String {
return "저는 \(nickname)입니다."
}
// Function Type 2. (String, Int) -> String
func hello(nickname: String, userAge: Int) -> String {
return "저는 \(nickname), \(userAge)입니다."
}
// Function Type 2. () -> Void, () -> ()
func hello() -> Void {
print("안녕하세요, 반갑습니니다.")
}
let a = hello
hello가 너무 많아! 원하는 것을 골라서 사용하려면 어떻게 해야 할까?
# 함수 구별 하기
let a: (String)-> String = hello
let b: (String, Int) -> String = hello
이처럼 타입 어노테이션을 통해서 타입을 명확하게 이야기를 해주면, 같은 함수명을 가졌더라도 구별해서 원하는 것을 사용할 수 있다!
# 1. 변수 또는 상수에 함수
를 담을 수 있다.
👉 같은 이름을 가진 함수가 많을 때, 특정 함수를 이용하기 위해서는 함수의 식별자를 이용한다.
let tupleExample = (1, true, "dkd", 3.3)
tupleExample.0 //.을 통해서 접근
func hello(userName: String) -> String {
return "저는 \(nickname)입니다."
👉 타입 어노테이션을 생략해도 함수를 구별해서 사용할 수 있다.
let a: (String)-> String = hello(nickName:)
let b: (String, Int) -> String = hello(nickname: userAge:)
👉 상수 b에 함수hello를 담았기 때문에, 마치 hello처럼 b를 사용할 수 있다.
b("minsoo", 33)
#2. 함수의 반환 타입으로 함수
를 사용할 수 있다.
//2. 함수의 반환 타입으로 함수를 사용할 수 있다. 구조체 클래스 등 반환값으로 사용할 수 있음.
//()->String
func currentAccount() -> String {
return "계좌 있음"
}
func noCurrentAccount() -> String {
return "계좌 없음"
}
// 가장 왼쪽에 위치한 ->를 기준으로, 오른쪽에 놓인 모든 타입은 반환값을 의미.
// 반환값에 대해서 보통 괄호를 쓰지 않고 그냥 쓰는 편인다.
func checkBank(bank: String) -> () -> String {
let bankArray = ["우리", "KB", "신한"]
return bankArray.contains(bank) ? currentAccount : noCurrentAccount
}
let minsu = checkBank(bank: "농협")
지금까지는 함수 자체만을 대입한 상태, 실행한 것이 아니다.
실행하기 위해서는, 아래 처럼, 함수를 실행하는 연산자()
를 적어주어야 한다.
minsu()
#계산기 예제
(Int, Int) -> Int 타입의 계산기 함수를 만들어 보자!
func plus(a: Int, b: Int) -> Int {
return a + b
}
func minus(a: Int, b: Int) -> Int {
return a - b
}
func multiply(a: Int, b: Int) -> Int {
return a * b
}
func divide(a: Int, b: Int) -> Int {
return a / b
}
func calculate(operand: String) -> (Int, Int) -> Int{
switch operand {
case "+": return plus
case "-": return minus
case "*": return multiply
case "/": return divide
default: return plus
}
}
함수를 result에 대입하면,
let result = calculate(perand: "-")
이는 함수를 result라는 상수에 대입을 한 것이지, 실행 X
실행 하려면 직접 호출을 해주어야 한다
result(5, 3) //2
그리고 위의 두 가지를 한 번에 적어줄 수 있다. 하지만 가독성을 위해서 보통은 따로 적어주는 편.
let result = calculate(operand:"-")(4,3)
3. 함수의 인자값으로 함수
를 사용할 수 있다.
콜백함수로 자주 사용이 된다!
콜백함수
: 특정 구분의 실행이 끝나면 시스템이 호출하도록 처리된 함수
func oddNumber() {
print("홀수")
}
func evenNumber() {
print("짝수")
}
// ()->() : 함수의 타입을 적어준 것!
func resultNumber(base: Int, odd: ()->(), even: ()->()) {
return base.isMultiple(of: 2) ? odd() : even()
}
// 타입에 맞는 함수를 매개변수로 넣어 주었다!
resultNumber(base: 9, odd: oddNumber, even: evenNumber)
⇒ 매개변수로 함수를 전달할 수 있다!
문제
: 어떤 함수가 들어갔는지와 상관없이, 단지 타입만 잘 맞으면 된다.
이렇게 실질적인 연산은 인자값으로 받는 함수에 달려 있어서 중개 역할만 담당한다고 하여 **브로커**
라고 부른다고 한다.
func plusNumber() {
print("더하기")
}
func minusNumber() {
print("뺴기")
}
하지만 단 한 번의 호출을 위해서 함수를 많이 만드는 것은 바람직하지 않다.
→ 익명함수
사용(클로저의 등장,,)
: 함수를 많이 만들지 않아도 된다는 장점이 있음.
위와 같이 함수가 블록처리가 되었을 때 엔터
를 누르면 아래와 같이 익명함수 형태로 바뀐다.
이는 마지막 매개변수의 이름만 남기고 생략되기 때문에 **익명함수**
라고 부른다.
# 1급 객체 클로저
resultNumber(base: 9) {
// 첫번째 매개변수는 생략, 마지막 매개변수만 남는다.
print("Success")
} even: {
print("failed")
}
resultNumber(base: 9) {
// 첫번째 매개변수는 생략, 마지막 매개변수만 남는다.
print("Success")
} even: {
print("failed")
}
클로저: 닫혀 있다.
커다란 함수 내부에 클로저라는 작은 함수가 있고, 클로저는 겉에 있는 커다란 함수의 생명주기에 의해 영향을 받는다!
뽑기 게임을 예를 생각해 보자.
// 외부함수
func drawingGame(item: Int) -> String {
// 내부함수
func luckyNumber(number: Int) -> String {
return "\(item * number * Int.random(in: 1...5))"
}
let result = luckyNumber(number: item * 2)
return result
}
drawingGame(item: 10)
여기에서, drawingGame이라는 외부 함수의 생명 주기가 끝나면(반환이 되면)
→ luckyNumber라는 내부 함수의 생명주기 역시 끝난다. (**은닉성**
)
#내부 함수를 반환하는 외부 함수를 만들 수 있다.
func drawingGame2(item: Int)-> (Int)-> String {
func luckyNumber(number: Int) -> String {
return "\(item * number * Int.random(in: 1...5))"
}
return luckyNumber
}
let luckyNumber2 = drawingGame2(item: 10)
//외부함수는 생명 주기가 끝난 상태
luckyNumber2(2)
// 외부함수의 생명주기가 끝나는데, 내부함수를 반환했기 때문에 -> 내부 함수는 계속사용할 수 있는 문제 발생.
→ 은닉성이 있는 내부함수를 외부함수의 실행 결과로 반환하면서 내부함수를 외부에서도 접근 가능하게되었음.이제 얼마든지 호출가능. 이건 생명주기에도 영향을 미침. 외부함수가 종료되더라도 내부함수는 살아있음.
✔︎ 외부함수의 생명주기가 끝났다는 것은, item
과 같은 외부함수의 매개변수도 끝났다는 말을 의미한다.
→ 어디에서 가져오는 걸까?
→ **클로저 캡처**
#클로저 캡처
- 클로저에 의해 내부 함수 주변의 지역 변수나 상수도 함께 저장이 되는 것.
- 주변 환경에 포함된 변수나 상수의 타입이 자료형이나 구조체 자료형일 때 발생.
✔︎ Swift는 특히 이름이 없는 함수로 클로저를 사용하고 있고, 주변환경(내부 함수 주변의 변수나 상수)로부터 값을 캡처할 수 있는 **경량 문법**
으로 많이 사용하고 있다.
클로저를 함수를 다시 살펴보자!
1. 클로저
를 상수나 변수에 담을 수 있다.
func studyiOS() {
print("iOS 개발자를 위해 열공중")
}
let studyiOSHarder = {
print("iOS 개발자를 위해 열공중")
}
func
을 사용해서 작성한 studyiOS()
함수를 클로저라는 익명함수로 작성하면, 아래와 같다! 상수에 클로저를 담아 작성한 것. 필요한 것만 작성한 형태.
클로저 타입에 대해서 명확하게 작성해주면, 다음과 같다. 본격적인 함수의 내용은 in
이라는 키워드 뒤에 작성한다.
let studyiOSHarder2 = { () -> () in
print("iOS 개발자를 위해 열공중")
}
✔︎ () -> () in print("iOS 개발자를 위해 열공중")
in을 기준으로, () -> ()은 Head, print("iOS 개발자를 위해 열공중"은 Body라고 한다.
호출을 할 때에는 상수의 이름 자체를 적어준다.
studyiOSHarder2
클로저를 작성한 후, 바로 실행하고자 할 때에는 뒤에 호출연산자()
를 붙여준다.
let studyiOSHarder3 = { () -> () in
print("iOS 개발자를 위해 열공중")
}()
2. 클로저
를 파라미터에 담을 수 있다.(feat.경량문법)
👉 이번엔 클로저를 파라미터의 값 형식안에 담아 보자! 아래의 ()-> ()에 위에서 계속 사용했던 예시인, studyiOSHarder2
라는 상수에 넣었던 클로저를 담을 수 있다.
func getStudyWithMe(study: ()-> ()) {
study()
}
getStudyWithMe(study: { () -> () in
print("iOS 개발자를 위해 열공중")
})
✔︎ 이렇게, 매개변수 값의 자리에 클로저 구문이 들어간 것을 **인라인클로저**
라고 한다.
👉 파라미터 자리에 넣었던 클로저는, 바깥으로 빼주는 것도 가능하다.
**단, 함수의 마지막 파라미터가 클로저인 경우에!**
파라미터의 값 형식(위와 같이)이 아니라, 함수의 뒤에, 빼내서 적어주는 것으로 **트레일링 클로저(Trailing Closuer)**
라고 하고, 이 때 study와 같은 매개변수의 이름은 생략
한다.
getStudyWithMe(){ () -> () in
print("iOS 개발자를 위해 열공중")
}
✔︎ 정리하면, **트레일링 클로저**
는
-
마지막 파라미터가 클로저일 때(파라미터 1개면 당근 해당)
-
매개변수 이름 생략
✔︎ 그리고 위의 예제에서 처럼, 파라미터가 클로저 하나인 경우에는 함수를 호출하는 연산자인
()
도 생략할 수 있다. 즉, 아래처럼 가능.
getStudyWithMe { () -> () in
print("iOS 개발자를 위해 열공중")
}
👉 위에 우리가 담은 클로저는, 위에서 studyiOSHarder2
라는 상수에 담겨 있던 것이다. 클로저가 담겨있는 상수나 변수를 매개변수에 넣을 수도 있다.
getStudyWithMe(study: studyiOSHarder2)
#클로저 경량문법
위에서처럼 조금 더 간단하게, 그리고 읽기 쉽게 표현하는 것을 **경량화**
한다고 표현한다. 방금 말한 것 처럼, 복잡한 코드의 문법 구조를 줄여서 가독성을 높이기 위해서다.
지금부터 줄여보자‼️
줄일 대상:
func todayNumber(result: (Int) -> String) {
result(Int.random(in: 1...100))
}
todayNumber(result: { (number: Int) -> String in
return "행운의 숫자는 \(number)"
})
✔︎ 매개변수의 타입, 반환값의 타입 생략 가능
todayNumber(result: { (number~~: Int~~) -> ~~String~~ in
return "행운의 숫자는 \(number)"
})
todayNumber(result: { (number) in
return "행운의 숫자는 \(number)"
})
✔︎ 덩그러니 남아있는 매개변수의 이름 생략 할 수 있다. 매개변수 지우면 혼자 머쓱하게 남겨져 있는 in
도 지운다.
todayNumber(result: { ~~(number)~~ ~~in~~
return "행운의 숫자는 \(number)"
})
todayNumber(result: {
return "행운의 숫자는 \(number)" //ERROR
})
⛔️ 그런데, 위에서는 ERROR가 뜬다. 매개변수로도 받지 않았다면 내부에서 number라는 변수를 사용할 수 없다. 이 때에는 할당된 내부 상수 $0
을 사용할 수 있다.
todayNumber(result: {
return "행운의 숫자는 \($0)"
})
👉 마지막으로, 함수 내부에 리턴값만 존재할 때에는, return
도 생략 가능하다.
todayNumber(result: {
"행운의 숫자는 \($0)"
})
👉 스트링 군더더기를 없앤다면, 최종적으로는 아래와 같다.
todayNumber{"\($0)"}
🙌 이런 긴긴 과정을 거치면, 다음과 같이도 사용할 수 있게 된다.
todayNumber { value in
print("하하하하")
return "\(value)입니다."
}
🔖 참고
∙ SSAC iOS 개발자 데뷔 과정 강의와 강의 자료
∙ https://babbab2.tistory.com/82
Author And Source
이 문제에 관하여(# 1급 객체 클로저 Closure), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@yoogail/1급-객체-클로저-Closure저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)