본문 바로가기
iOS/Swift

[iOS/Swift] 클로저 표현식(Closure Expression)과 1급 객체 조건

by iosdevlime 2023. 1. 26.

지난 포스팅에 이어..

 

기존에 작성해 온 함수(전역함수 및 중첩함수)와 클로저 표현식과의 차이점,

1급 객체함수의 특징, 조건을 클로저를 활용한다면 어떤 방식일지

 

예시 코드와 함께 다뤄보도록 하겠습니다.

(관련된 예시 코드는, Swift 공식 가이드북에서 발췌)

 

클로저 (The Swift Programming Language)

 

클로저 (Closures) - Swift

다음은 Int 값의 배열을 String 값의 배열로 변환하기 위해 후행 클로저와 map(_:) 메서드를 어떻게 사용하는지 나타냅니다. 배열 [16, 58, 510] 은 새로운 배열 ["OneSix", "FiveEight", "FiveOneZero"] 을 생성하는

bbiguduk.gitbook.io

 

 

 

 


 

 

 

 

클로저 표현식(Closure Expression)

이름이 없는 간단하고, 집중적 구문으로서 함수와 유사한 짧은 버전

 

클로저란 개념에 대해 다시 한번 짚고 넘어가자면..

 

우선, 클로저는 3가지 형태를 띄고 있습니다.

 

  • 전역함수(Global Function) 
    • 이름을 가지고 있으며, 어떠한 값도 캡쳐하지 않는 클로저
  • 중첩함수(Nested Function)
    • 이름을 가지고 있으며, 둘러싸고 있는 함수로 부터 값을 캡쳐할 수 있는 클로저
  • 클로저 표현식(Closure Expression) 
    • 이름이 없으며, 주변 컨텍스트(혹은 Scope)에서 값을 캡쳐할 수 있는 클로저

 

전역함수와 중첩함수(함수 내부 또 다른 함수가 있는 형태)는 활용을 해 보았으니,

 

본격적으로 클로저 표현식 은 어떻게 사용되고, 선언할 수 있는지 살펴볼 시간입니다.

 

 

 

 

Sorted(by: ) 메서드를 활용한 비교 예시

  • 다음 예시는 전역함수를 활용한 알파벳 순으로 이름을 정렬하기 문제해결 과정입니다.
    • sorted(by:) 메서드는, 파라미터 값에 의해 오름, 혹은 내림차순으로 배열을 정렬합니다.
    • backward(_: _:) 함수와 name 이란 전역상수를 함께 활용하여 문제를 해결합니다.
  • 하지만, 아래 코드는 내림차순이란 문제를 해결하기 위한 다소 긴 방식의 코드입니다 😅
// 전역함수를 활용한 알파벳 순 이름정렬
let name = ["Lime", "Alex", "Kim", "Bucky", "Chris"]

// 첫번째 배열값이 두번째 배열값보다 크다면 true, 작다면 false를 반환함
// 문자열의 문자가 '더 크다'는 '알파벳 순으로 더 뒤에 위치한다' 란 의미와 같음
func backward(_ name1: String, _ name2: String) -> Bool {
    return name1 > name2
}

// 따라서, sorted 메서드에는 backward란 Bool값이 파라미터로 전달됨
var reversedName = name.sorted(by: backward)

reversedName // ["Lime", "Kim", "Chris", "Bucky", "Alex"]

 

  • 위 함수 표현식을 ➟ 클로저 표현식을 사용하여 아래와 같이 문제를 해결할 수 있습니다.
    • 문제해결을 위해, 별도의 전역함수를 선언할 필요가 없습니다.
    • 단 한 줄의 코드로서 가독성있고 효율적인 클로저 구문을 완성합니다.
    • 이와 같은 형태를 인라인 클로저(Inline Closure)라고 합니다.
// 클로저 표현식을 활용한 알파벳 순 이름정렬

let name = ["Lime", "Alex", "Kim", "Bucky", "Chris"]

var reversedNameClosure = name.sorted(by: { (name1: String, name2: String) -> Bool in
    return name1 > name2
})

reversedNameClosure // ["Lime", "Kim", "Chris", "Bucky", "Alex"]

 

 


 

 

 

클로저 표현구 (Closure Expression Syntax)

  • 클로저 표현구(표현식)는 다음과 같은 형태를 가지고 있습니다.
    • 이름이 없는 함수인 만큼, func 키워드는 생략됩니다.
    • in 키워드는 Closure head와 Closure body를 구분합니다.
  • 매개변수와 리턴타입이 없을 경우, 생략이 가능합니다.
    • 만약, 매개변수가 존재한다면 인자(Argument Label)는 사용되지 않습니다.

클로저 표현식의 일반적인 형태
클로저 표현식의 일반적인 형태

 

 

 


 

 

 

1급 객체로의 클로저(Closure)

클로저 또한, 1급 객체로의 3가지 조건을 만족한다

 

Swift의 함수는 1급 객체의 조건을 만족하므로,

 

클로저 또한 당연하게 대입이 가능합니다.

(1급 객체에 대해 가물가물 하다면, 아래 포스팅을 참고해주시길 바랍니다)

 

1급객체의 조건

 

[iOS/Swift] 1등 시민, 1급 객체함수 (First Class Citizen)

드디어, 함수 마지막 파트까지 도달했네요! 앞서 다룬 함수관련 포스팅의 내용 중... 함수의 표기법 (Function Notation) : 함수를 '명명'하는 일종의 규칙 함수의 타입(Function Type) : 함수 또한 일종의 '

iosdevlime.tistory.com

 

 

 

 

첫 번째, 변수 또는 상수에  할당할 수 있어야 한다 

  • 클로저를 변수 또는 상수에 대입(할당)하게 되면?
    • 해당 변수와 상수는 클로저의 기능을 활용(실행)할 수 있게 됩니다!
    • 대입된 변수나 상수는 ➟ 새로운 변수와 상수에 대입할 수도 있습니다.
// 특정 상수나 변수에 '클로저' 자체를 대입
let helloName = { (name: String) -> String in
    return "Hello, \(name)"
}

helloName("Lime") // "Hello, Lime"


// 기존 (클로저를 대입한) 변수와 상수를 새로운 변수에 상수에 대입
let callMyName = helloName
callMyName("Lime")

 

 

 

두 번째, 함수의 반환값(Return Value)로 클로저를 사용할 수 있어야 한다

  • 클로저를 특정 함수의 반환값(Return Value)로 활용하게 된다면?
    • 반환값인 클로저의 기능 자체를 함수에서 반환할 수 있습니다.
    • 클로저를 반환하기 위해, 해당 함수 반환타입에 클로저의 타입을 작성해야 합니다.
      (예시 코드의 경우, () -> () )
// return value를 클로저로 받는 helloName 함수
func helloName() -> () -> () {
    
    return { () -> () in
        print("Hello, Lime")
    }
}

let callMyName = helloName()

callMyName() // "Hello, Lime"

 

 

 

세 번째, 함수의 매개변수(Parameters)로 클로저를 사용할 수 있어야 한다

  • 클로저를 특정 함수의 매개변수로 활용하게 된다면?
    • 클로저 자체를 ➟ 함수의 매개변수 타입(Type) 으로서 전달할 수 있습니다.
    • 함수 포스팅에서 언급한 콜백 함수 자리에 직접 클로저를 작성할 수 있습니다.
// 임의의 'name' 함수를 하나 선언합니다.
// 해당 함수의 타입은 ? ()->()
func name() {
    print("Hello, Lime")
}

// 이후, '함수'를 매개변수 타입으로 받는 'helloName' 함수를 선언합니다.
func helloName(callback: () -> ()) {
    callback()
}

// 1. 콜백(Callback)함수로서, 함수 자체를 전달인자 값으로 대입하는 경우
helloName(callback: name) // "Hello, Lime"

// 2. 호출할 시점에서, 전달인자 자체를 클로저로서 대입하는 경우
helloName(callback: { () -> () in
    print("Hello, Lime")
}) // "Hello, Lime"

 

 


 

 

클로저(Closure)의 호출 및 실행

대입된 변수/상수의 호출, 그리고 직접 실행

 

클로저 표현방식, 그리고 1급 객체의 조건을 살펴보는 단계에서

클로저를 실행하는 방식을 이미 슬쩍 거쳐왔지만..

 

한번 더 간략하게 정의하고 넘어가도록 하겠습니다.

 

 

 

클로저가 대입된 변수/상수로 호출하기

  • 1급 객체함수로서의 조건 중, 클로저는 변수와 상수에 대입할 수 있었습니다. 
    • 따라서, 해당 변수/상수의 호출이 곧 클로저의 기능을 호출하는 것과 동일합니다.
    • 호출 구문인 괄호 ( ) 를 변수/상수 뒤에 붙여 호출할 수 있습니다. 
// 'name'이란 상수에 클로저가 대입됨
let name = { () -> String in
    return "Hello, Lime"
}

// 대입된 'name' 상수 뒤에 괄호()를 붙여 호출
name() // "Hello, Lime"

 

 

클로저 자체를 직접 실행

  • 변수/상수에 할당되지 않은 형태의 클로저 또한 직접 호출할 수 있습니다. 
    • 클로저 표현식에 따라 작성한 후, 클로저 전체를 괄호 ( )로 감싸줍니다.
    • 이후, 호출 구문인 괄호 ( )를 다시 한번 붙여 호출합니다.
// 변수/상수에 대입하지 않은 클로저 표현식
{() -> String in
    return "Hello, Lime"
}


// 1. 전체 클로저 구문을 괄호()로 감싸고,
// 2. 마지막에 호출 구문인 괄호()를 붙여서 호출
({() -> String in
    return "Hello, Lime"
})()

 

 


 

 

클로저의 기초내용에 대해 다뤄보았습니다.

 

다소 헷갈리고, 난해한 문법 표현으로 보일 수 있으나..

 

프로그래밍에 있어서 코드의 간결성효율성은 중요한 사안이므로

조금 더, 클로저와 친숙해 질 필요가 있을 것 같습니다.

 

다음 포스팅에서는

클로저의 경량문법에 대해 다뤄보도록 하겠습니다.

 

 

 

 

 

댓글