CLOSE SEARCH

Swift – showMeTheFunction(language: “Swift”)

Objective-C를 처음 접했을 때 가장 혼란스러웠던 점은 바로 함수를 정의하고 호출하는 방식이었습니다. helloworld();에 익숙했던 저는 [self helloworld];라는 문법에 적응하는데 많은 시간이 걸렸습니다. Swift의 함수를 공부하면서 그 때의 기억이 다시 떠오르는건 왜일까요? 하지만 모든 어려움과 생소함은 시간과 노력이 해결해 주고, 특히 이전에 아쉬웠던 점들이 많이 개선되었기 때문에 Objective-C를 공부할 때보다는 더 많은 기대를 가지고 공부를 할 수 있을 것 같습니다.

함수의 정의

Swift의 함수는 Objective-C나 다른 C 스타일의 언어와는 상당히 다른 구조를 가지고 있습니다. C 스타일의 언어에만 익숙한 경우라면 리턴값을 지정하는 방법을 유심히 볼 필요가 있습니다.

 

코드 1-1과 같이 Swift의 함수 정의는 func 키워드로 시작합니다. 그 뒤에는 함수의 이름이 오고 Objective-C와 마찬가지로 camelBack 방식을 주로 사용합니다. 원한다면 CamelCase나 snake_back 방식을 사용해서 함수 이름을 정의할 수도 있지만, 일관성을 유지하기 위해서는 camelBack 방식을 사용해서 이름을 정의하는 것이 좋습니다.
함수 이름 뒤에는 괄호 ( ) 가 오고, 이 괄호 속에는 함수로 전달될 파라미터가 나열됩니다. 파라미터의 자료형과 이름이 괄호로 구분되었던 Objective-C와 달리 콜론(:)으로 구분되며 자료형과 이름의 순서도 반대입니다. 코드 1-1에서 getAge는 하나의 파라미터를 가지고 있고, 이 파라미터의 이름은 userId, 자료형은 String입니다. 콜론(:)과 자료형 사이의 공백은 필수가 아니므로 원한다면 userId:String과 같이 붙여 쓸수도 있습니다. 함수의 리턴형은 파라미터 선언 뒤에 리턴 화살표(->, return arrow) 적고 선언해 줍니다. 만약 리턴값이 없는 함수라면 -> Int 부분 전체를 생략할수도 있습니다. 대괄호 { }는 이전과 같이 함수의 본문을 감싸는 역할을 합니다.
파라미터를 선언할 때 자료형과 이름의 순서, 리턴형을 선언하는 방식을 이해하는 것이 Swift의 함수를 이해하는데 필요한 기본입니다. 처음에는 이상한(?) 구조에 거부감이 생길수도 있지만, 찬찬히 뜯어보면 전혀 어려운 점이 없고 -> 화살표가 값의 흐름을 표현해 준다고 본다면 시각적으로도 조금 더 직관적이라고 생각됩니다.

 

함수의 호출

함수를 호출하는 문법은 C와 동일하기 때문에 자세하게 설명할 부분은 없습니다. 세미콜론(;)을 적지 않는 문법에 익숙해지기만 하면 됩니다.

 

다중 값 리턴

Swift는 튜플을 통해 여러개의 값을 리턴할 수 있습니다. Objective-C나 C에서도 구조체나 클래스를 사용해서 이러한 기능을 구현할 수 있었지만 새로운 구조체나 클래스를 선언해야 한다는 점에서 불편했던 것도 사실입니다.

리턴값을 튜플로 선언하는 방식은 여러개의 파라미터를 선언하는 방식과 동일합니다. 단일 값을 리턴하는 경우와 달리 리턴값을 전달받는 쪽에서 튜플에 포함된 개별 값에 접근할 수 있어야 하기 때문에 자료형과 이름을 모두 지정해 주는 것입니다. 즉, 코드 3-1의 리턴값 튜플에 있는 name, age, address는 리턴된 튜플의 각 요소에 접근하는데 사용되는 이름입니다.

각 요소에 접근할 때는 코드 3-2 같이 . 문법을 사용합니다. 이러한 문법은 구조체의 멤버에 접근하는 문법과 동일하기 때문에, 리턴된 튜플은 구조체라고 생각하면 이해하기 수월합니다.

 

파라미터

함수 선언에서 파라미터 목록에 선언된 파라미터 이름은 함수 내부에서만 사용되는 이름입니다. 이러한 이름을 Local Parameter Name이라고 합니다. 코드 3-1에서 userId가 바로 Local Parameter Name 입니다. Local Parameter Name은 앞서 설명한 것과 같이 함수 내부에서만 사용할 수 있기 때문에 함수를 호출할 때 getMemberInfo(userId: “123″)와 같이 함수 외부에서 파라미터 이름을 사용하는 것은 오류입니다.

함수의 파라미터가 하나인 경우에는 함수의 이름을 통해서 파라미터의 역할에 대한 힌트를 줄 수 있지만, 두개 이상인 경우에는 한계가 있습니다. 물론 코드 문서화가 잘 되어 있는 경우라면 큰 문제는 없지만, 코드 자체의 가독성을 위해서라도 파라미터의 역할을 설명할 수 있는 수단이 필요합니다. 이런 면에서 Objective-C의 함수들은 이름이 지나치게 서술적이라는 평가도 있지만 가독성 측면에서는 상당히 뛰어나다고 생각합니다. Swift에서는 External Parameter Name이라는 기능을 통해서 가독성을 향상시킬수 있는 수단을 제공합니다.

코드 4-1의 함수는 iOS 개발자라면 상당히 익숙한 함수일 것입니다. 만약, 이 함수를 호출한다고 가정한다면 Swift에서는 두번째 파라미터의 역할을 직관적으로 판단하기가 어렵습니다. Objective-C의 didFinishLaunchingWithOptions와 같이 두번째 파라미터의 역할을 서술할 수단이 없기 때문입니다. 바로 이럴때 필요한 것이 External Parameter Name입니다. 실제로 코드 4-1의 application 함수의 구현은 다음과 같습니다.

두번째 파라미터 이름(launchOptions) 앞에 있는 didFinishLaunchingWithOptions가 바로 external parameter name입니다. 이 함수를 호출할 때는 4번 라인과 같이 파라미터 앞에 콜론(:)을 붙여 적어주면 됩니다. 파라미터를 선언하는 문법에서 콜론(:) 뒤의 자료형 대신 실제 파라미터 값을 적어주는 방식이라고 기억하시면 됩니다. 한가지 주의할 점은 external parameter name을 제공한 경우에는 호출시에 생략할 수 없다는 것입니다. 즉, 위의 함수를 application(app, info)와 같이 호출하는 것은 오류이므로 반드시 External Parameter Name을 함께 적어주어야 합니다.

함수의 직관성을 높여준다는 것은 장점이지만 파라미터의 이름을 두개나 지어야 한다는 것은 분명 부담입니다. 그래서 Swift는 Shorthand External Parameter Names라는 기능을 제공합니다. 이 기능은 하나의 파라미터 이름을 함수 외부와 내부에서 모두 사용할 수 있도록 해줍니다. 코드 4-3과 같이 내부 파라미터 이름에 #을 붙여주면 launchOptions라는 이름을 함수 외부에서도 사용할 수 있게 됩니다.

Swift에서는 파라미터에 기본값을 설정할 수 있습니다. 함수 선언에서 두개 이상의 파라미터가 존재하는 경우에는 기본값을 가지는 파라미터를 파라미터 목록의 마지막에 두어야 합니다. 기본값을 가진 파라미터는 함수 호출시 생략할 수도 있습니다.

getMemberInfo 함수를 파라미터 없이 호출할 경우 함수 내부에는 기본값인 all이 전달됩니다. 만약, 파라미터를 사용해서 getMemberInfo(“123”)과 같이 호출하려면 오류가 발생합니다. Swift는 기본값이 설정된 파라미터의 이름을 앞서 설명한 external parameter name으로 인식하기 때문에 반드시 8번 라인과 같이 파라미터 이름을 명시적으로 지정해서 호출해야 합니다.

이번에는 가변 인자 파라미터(Variadic Parameter)에 대해 살펴보겠습니다. C에서는 함수 본문에서 va_start, va_arg, va_end 함수를 통해 가변 인자 파라미터를 처리했었습니다. Swift에서는 이러한 함수를 사용해서 별도의 초기화나 정리 작업을 할 필요없이 배열 형태로 가변 인자 파라미터를 처리할 수 있는 기능을 제공합니다.

코드 4-5의 함수를 코드 3-1의 함수와 비교해 보면, userId 파라미터의 선언이 조금 다르다는 것을 발견할 수 있을 것입니다. 파라미터의 자료형 뒤에 추가된 …이 바로 해당 파라미터를 가변 인자 파라미터로 선언하는 문법입니다. 가변 인자 파라미터는 파라미터를 받지 않을 수도 있고, 2개 이상의 파라미터를 받을 수도 있기 때문에 다음과 같은 호출문들은 모두 정상적으로 동작합니다.

가변 인자 파라미터로 선언된 userId는 함수 내부에서 String 배열(String[])로 처리됩니다. 3번 라인의 파라미터를 이해하기 쉽게 ObjectiveC의 배열 형태로 표현해 보면 @[@“123”, @“456”, @“789”] 가 됩니다. 만약, userId가 Int 형이었다면 Int형 배열로 처리되고, Double 형이었다면 Double형 배열로 처리되는 것입니다.

가변 인자 파라미터를 함수 내부에서 처리하는 방식은 개선되었지만, C나 Objective-C에 있던 가변 인자 파라미터에 대한 제약들은 여전히 유효합니다. 모든 함수는 하나의 가변 인자 파라미터만을 가질 수 있고, 가변 인자 파라미터는 파라미터 목록의 마지막에 위치해야 합니다.

Swift의 파라미터는 기본적으로 let으로 선언한 상수입니다. Objective-C에서는 함수 내부에서 파라미터의 값을 변경할 수 있지만, Swift에서는 상수의 값을 변경할 수 없다는 컴파일 오류(Cannot assign to ‘let’ value)가 발생합니다. 만약 Objective-C와 같이 파라미터를 변수로 선언하고 싶다면 코드 4-7과 같이 해당 파라미터를 var로 선언해야 합니다. 이렇게 var로 선언된 파라미터를 변수 파라미터(Variable parameter)라고 합니다.

코드 4-5의 userId 파라미터는 함수 내부에서 값을 변경할 수 있습니다. 파라미터는 사용 범위는 함수 내부로 제한되므로 함수 내부의 변경사항은 함수 외부에 영향을 주지 않는다는 점에 유의해야 합니다.

앞에서 설명한 파라미터들은 기본적으로 사용 범위가 함수 내부로 제한되고, 값의 변경이 외부에 영향을 주지 않는 입력 파라미터입니다. 함수 내부의 변경사항을 함수 외부에도 적용하고자 한다면 입출력 파라미터(in-out parameter)를 사용해야 합니다. Swift의 입출력 파라미터는 C#의 ref 파라미터와 동일합니다.

입출력 파라미터를 선언할 때는 inout 키워드를 사용합니다. 코드 4-6에서 lhs와 rhs는 모두 입출력 파라미터로 선언 되었기 때문에 입력 파라미터와 달리 함수 내부에서 사용될 임시 변수가 생성되지 않습니다. 파라미터로 전달된 변수가 함수 내부에서도 동일하게 사용되기 때문에서 swap 함수를 실행하면 두 변수의 값이 교체됩니다. 입출력 파라미터의 경우 함수 내부에서 값을 조작할 수 있어야 하기 때문에 상수나 리터럴 값을 입출력 함수로 전달할 수는 없습니다. 그리고 기본값을 설정할 수 없다는 것도 기억해 두어야 합니다.

코드 4-6의 9번 라인과 같이 입출력 파라미터가 포함된 함수를 호출할 때, 입출력 파라미터 앞에 &를 붙여 함수 내부에서 변경될 수 있다는 것을 표시해 두어야 합니다. 그렇지 않은 경우에는 컴파일 오류가 발생합니다.

 

Function Type

Swift의 모든 함수는 파라미터와 리턴형으로 구성되는 특별한 함수 타입을 가집니다.

코드 5-1의 getAge 함수는 각각 하나의 문자열 파라미터와 Int형 리턴값을 가지고 있습니다. 이 함수의 함수 타입은 (String) -> Int가 됩니다. 파라미터와 리턴형이 없는 sayHello() 함수의 함수 타입은 () -> () 입니다. 실제로 sayHello() 함수는 “리턴값이 없다”는 의미의 Void를 리턴하며, Swift에서는 Void를 비어있는 튜플의 형태인 ()로 나타냅니다. 파라미터와 리턴형이 명시적으로 작성되어 있지는 않지만 두가지 모두 Void 형이기 때문에 함수 타입이 () -> () 와 같이 표현되는 것입니다.

함수 타입은 C의 함수 포인터, C#의 델리게이트와 유사한 역할을 한다고 볼 수 있습니다. 이를 통해 함수를 String이나 Int와 같은 다른 자료형처럼 함수의 파라미터로 전달하거나 함수 자체를 리턴하는 함수를 구현할 수도 있습니다.

코드 5-2에서는 getAge 함수 타입을 가지는 변수 f를 선언하고 getAge 함수를 할당하고 있습니다. 이 코드는 C에서 함수 포인터를 사용하는 것과 매우 유사합니다. f는 getAge를 가리키고 있는 함수 타입 변수이기 때문에 2번 라인과 같이 f라는 이름을 사용해서 getAge 함수를 호출할 수 있습니다.

코드 5-3은 getAge와 동일한 함수 타입을 첫번째 파라미터로 선언하고 있습니다. printResult 함수를 호출할 때 (String) -> Int와 일치하는 함수 타입을 파라미터로 전달할 수 있고, f 라는 하는 파라미터 이름을 통해 전달된 함수를 호출할 수 있습니다. 위에서 보는 것과 같이 String이나 Int를 파라미터로 전달하는 것과 큰 차이가 없습니다.

함수 타입을 리턴형으로 사용할 때는 함수 선언에 리턴 화살표(->)가 두개나 들어가서 조금은 혼동될 수도 있습니다. 하지만 첫번째 ->는 무조건 함수의 리턴형을 선언하는 화살표이고 그 뒤에 나오는 화살표는 함수 타입의 리턴형을 선언하는 화살표라고 기억해 두면 됩니다. -> 뒤에 String이나 Int가 오던 부분이 함수 타입 선언으로 바뀐 것이기 때문에 조금만 자세히 보면 크게 어렵지도 않습니다. 코드 5-4에서 chooseStepFunction 함수가 리턴한 함수 타입은 코드 5-2와 같이 함수 타입 변수나 상수에 할당한 다음 사용할 수 있습니다.

 

Nested Functions

앞에서 설명한 함수들은 모두 전역 공간에 선언된 전역함수 입니다. Swift에서는 함수 내부에 또 다른 함수를 선언할 수 있는 내포된 함수(Nested Function)을 제공합니다. 내포된 함수는 기본적으로 자신의 선언을 포함하고 있는 상위 함수의 범위 내에서만 사용될 수 있지만, 상위 함수에 의해 함수 타입으로 리턴되는 경우에는 리턴 받은 곳에서도 내포된 함수를 사용할 수 있습니다.

 

ps. Facebook 페이지에 가입해서 함께 정보를 공유해보아요 :) https://www.facebook.com/groups/254425194763076/

Filed under: Swift