[iOS] About Function (feat. method)

iOS

주로 Java를 사용하다가 iOS를 공부하는데 Objective-C를 공부하면서 함수라는 용어가 나와서 모호했던 개념을 다잡고자 정리합니다.
이번 포스트는 함수에 대해 알아보면서 메소드와이 차이도 간략하게 알아볼게요.

Function

함수와 메소드는 모두 코드 조각을 지칭하는 용어이지만 몇가지 차이점이 있습니다.
함수는 코드의 최상위 레벨에 선언되고, 메소드는 특정 형식 내부에 선언됩니다.
함수는 함수 이름만으로 호출 할 수 있지만, 메소드는 반드시 연관된 이름이나 인스턴스를 통해 호출해야 합니다.

Objective-C와 Swift는 함수와 메소드를 모두 구현할 수 있습니다.
C와 같이 함수만 구현할 수 있는 언어가 있고, Java와 같이 메소드만 구현할 수 있는 언어도 있습니다.
Objective-C의 경우 함수를 구현할 때 C 언어와 동일한 문법을 사용하지만,
메소드를 구현할 때는 Objective-C 자체 문법을 사용합니다.
또한 메소드를 구현할 수 있는 형식은 클래스로 제한됩니다.

Swift는 동일한 문법을 사용하여 함수와 메소드를 구현합니다.
Objective-C와 달리 클래스, 구조체, 열거형에 메소드를 구현할 수 있습니다.

함수의 구성 요소

함수는 일반적으로 다섯 가지 요소로 구성됩니다.

  1. 함수 이름
  2. 파라미터
  3. 리턴형
  4. 실행코드
  5. 프로토타입
Objective-C
1
2
3
NSInteger plusOne(NSInteger x) {
return x + 1;
}
Swift
1
2
3
func plusOne(x: Int) -> Int {
return x + 1
}

함수의 이름과 함수 호출

함수의 이름은 동사 + 명사로, 시작 동사는 소문자로 짓는 것이 관례입니다.
즉 PlusOne보다 plusOne으로 짓는 것이 좋습니다.
log, write, read와 같이 자주 사용하는 단어를 이름으로 사용할 경우 함수의 사용범위 내에 동일한 이름을 사용하는 함수가 존재할 가능성이 높습니다.
이 경우에는 좀 더 구체적인 이름을 사용하거나 접두어를 사용합니다.
Foundation 프레임워크에서 제공하는 함수들은 NS 접두어를 사용하고 있으며, NSLog가 그 예입니다.

파라미터

f(x) = x + 1에서 x를 파라미터라고 합니다.
파라미터는 함수를 선언할 때 ()사이에 ,로 구분하여 나열합니다.
함수가 받을 수 있는 파라미터 수에는 제한이 없지만 가독성을 고려해서 선언합니다.

Call by Value vs Call by Reference

파라미터에 인자를 전달하는 방식은 크게 두 가지로 분류할 수 있습니다.
첫 번째 방식은 인자의 값을 전달하는 Call by Value이고 두 번째 방식은 인자의 값이 저장된 메모리의 주소를 전달하는 Call by Reference입니다.

두 방식의 차이점으로 자주 예를 드는 swap함수를 만들어보겠습니다.
swap함수는 두개의 수를 받아서 해당 수를 바꾸는 역할을 수행할 함수입니다.

Objective-C
1
2
3
4
5
6
7
8
9
10
11
12
13
void swap(NSInteger lhs, NSInteger rhs) {
NSLog(@"before swap");
NSLog(@"lhs: %ld", lhs);
NSLog(@"rhs: %ld", rhs);

NSInteger tmp = lhs;
lhs = rhs;
rhs = tmp;

NSLog(@"after swap");
NSLog(@"lhs: %ld", lhs);
NSLog(@"rhs: %ld", rhs);
}

그리고 호출은 이렇게 합니다.

Objective-C
1
2
3
4
5
6
NSInteger a = 1;
NSINteger b = 2;
swap(a, b);

NSLog(@"a: %ld", a);
NSLog(@"b: %ld", b);

이 함수를 실행하면 함수 내부에서 파라미터의 값은 교체되지만, 함수 호출 후의 a와 b의 값은 교체되지 않은 것을 확인할 수 있습니다.
여기에서 인자를 전달한 방식은 Call by Value 입니다.

Call by Reference는 인자의 메모리 주소를 전달합니다.
이 방식으로 인자를 전달받는 파라미터의 자료형은 포인터로 선언되어야 합니다.
저는 이렇게 구현했습니다.

Objective-C
1
2
3
4
5
6
7
8
9
void swap(NSInteger* lhs, NSInteger* rhs) {
NSInteger tmp = *lhs;
*lhs = *rhs;
*rhs = tmp;

NSLog(@"after swap");
NSLog(@"lhs: %ld", *lhs);
NSLog(@"rhs: %ld", *rhs);
}

실행을 해보면 Call by Value 방식과 달리 함수를 호출한 후에 a, b값이 교체된 것을 확인할 수 있습니다.

Objective-C
1
2
3
4
5
6
NSInteger a = 1;
NSINteger b = 2;
swap(&a, &b);

NSLog(@"a: %ld", a);
NSLog(@"b: %ld", b);

이 코드는 이전 코드와 몇가지 차이점을 가지고 있습니다.
swap 함수의 두 파라미터는 주소를 파라미터로 받을 수 있도록 포인터로 선언되어 있습니다.
그리고 함수를 호출할때 변수의 주소를 넘겨주고, 함수 내에서는 변수의 주소로 접근해 값을 변경합니다.
따라서 이전 Call by Value와 다르게 swap함수가 끝나고 main에서 다시 접근했을때 값이 계속 변경되어있는 것입니다.

마치며

C 계열의 언어를 사용하는 프로그래머에게 포인터의 개념은 필수입니다.
특히나 이 포인터는 함수와 연관되어 많이 사용됩니다.
함수, 포인터는 항상 러닝커브가 급격하게 올라가는 시점이기 때문에 많은 분들이 어려워합니다.
이 포스트를 문법이 아닌 개념으로 접근했을때 이해가 되지 않는다면, 검색이나 책을 통해 포인터와 함수의 기본 개념에 대해서 숙지하는것을 권장합니다.