[Swift] 함수

알파관·2022년 9월 26일
0
post-thumbnail

2022.09.24

클로저 공부를 하다가, 함수에서 배웠던 용어들과 개념들이 살짝 기억이 나지 않아 추가 공부가 필요할 것 같다는 생각이 들었다.

차근차근 복습을 하며 기억을 상기시켜보자,,


📌 함수란?

'특정 작업을 수행하는 코드 모음 형태'

Swift에서 함수는 함수 호출을 단순화 하기위해 기본값을 제공할 수 있으며 함수가 실행을 완료하면 전달된 변수를 수정하는 입출력 파라미터로 전달될 수 있다.

평소 메서드함수가 대체 무슨 차이인지에 대해 궁금했는데
그 둘을 다음과 같은 차이가 있다고 한다.

메서드

구조체/클래스 등 특정 타입에 연관되어 사용하는 함수를 말함

함수

모듈 전체에서 전역적으로 사용할 수 있는 함수를 말함


📌 함수의 정의와 호출

파이썬에서와 마찬가지로 Swift에서도 함수는 func 키워드를 사용해 정의하는데, 구문의 형식은 아래와 같다.

func 함수이름 (매개변수...) -> 반환 타입 {
	실행구문
    return 반환 값
}
func hello(name: String) -> String {
	return "Hello \(name)!"
}

let helloJenny: String = hello(name: "Jenny")
print(helloJenny)

func introduce(name: String) -> String {
	"제 이름은 " + name + "입니다"
}

let introduceJenny: String = introduce(name: "Jenny")
print(introduceJenny)

📌 함수의 매개변수와 반환값

매개변수와 반환값이 어떻게 되는가에 따라 함수는 다양한 종류로 나뉜다.

1. 매개변수가 없는 함수

2. 매개변수가 있는 함수

3. 반환값이 없는 함수

4. 반환값이 있는 함수

  • 매개변수가 없는 함수

함수에 매개변수 값이 없음, 하지만 함수는 어떠한 매개변수가 없더라도 함수의 이름 뒤에 소괄호() 가 필요하다.

함수는 입력 매개변수 정의를 요구하지 않음.

func sayHelloWorld() -> String {
    return "hello, world"
}
print(sayHelloWorld())
  • 매개변수가 있는 함수

함수는 함수의 소괄호 내에 콤마로 구분하여 여러개의 입력 매개변수를 가질 수 있다.

매개변수가 있는 함수의 경우에는, 소괄호 안에 적절한 매개변수 이름을 붙여주고 콜론(:)을 적은 후 전달인자를 보내준다.

이 때, 매개변수가 예시코드처럼 여러 개일 경우에는 쉼표(,) 로 구분해준다.

func greet(person: String, alreadyGreeted: Bool) -> String {
    if alreadyGreeted {
        return greetAgain(person: person)
    } else {
        return greet(person: person)
    }
}
print(greet(person: "Tim", alreadyGreeted: true))
  • 반환 값이 없는 함수

함수는 반환 타입 정의를 요구하지 않는다.

반환 값이 없는 함수의 경우엔 -> (반환 타입) 을 표기하지 않음

func greet(person: String) {
    print("Hello, \(person)!")
}
greet(person: "Dave")
// Prints "Hello, Dave!"

사실, 엄밀히 따지면 반환 값은 존재하긴 한다.

단지, 반환타입이 정의되지 않아 Void 타입이 반환되기 때문에 표기가 되지 않은 것 뿐이다.

  • 반환 값이 있는 함수

매개변수 선언부 옆에 -> (반환타입) 과 같은 구문을 작성하여 반환 값이 있음을 표시한다.

func minMax(array: [Int]) -> (min: Int, max: Int) {
    var currentMin = array[0]
    var currentMax = array[0]
    for value in array[1..<array.count] {
        if value < currentMin {
            currentMin = value
        } else if value > currentMax {
            currentMax = value
        }
    }
    return (currentMin, currentMax)
}

만일, 위의 예시코드처럼 함수가 여러 개의 반환값을 가진다면 그 함수는 최종적으로 튜플 을 반환할 것이다.

반환 값 속의 min, max는 함수의 반환 값 조회 시에 이름으로 접근할 수 있도록 라벨링 된 것이다.


📌 전달인자 레이블과 암시적 반환

- 전달인자 레이블

Swift에서는 함수의 매개변수 이름과 더불어 전달인자 레이블 을 지정해줄 수 있다.

전달인자 레이블은 함수 외부에서 매개변수의 역할을 좀 더 명확히 할 수 있게 해준다.

사용 시에는 매개변수 이름 앞에 한 칸을 띄운 후 전달인자 레이블을 지정해준다.

전달인자 레이블을 포함한 전체적인 함수 작성 구문을 정리해보면 아래와 같을 것이다.

func 함수 이름(전달인자 레이블 매개변수 타입, 전달인자 레이블 매개변수 이름:
	매개변수 타입...) -> 반환 타입 {
    실행 구문
    return 반환 값
}
func sayHello(from myName:String, to name: String) -> String {
	return "Hello \(name)! I'm \(myName)"
    }
    
    print(sayHello(from: "yagom", to: "Jenny"))

전달인자 레이블을 활용한 예시 코드를 보면 한가지 특징을 볼 수 있다.

바로, 함수 내부에서는 매개변수 이름이 사용가능하나, 전달인자 레이블은 사용하지 못한다는 점이다.
그리고 함수를 호출할 때는 매개변수 이름을 사용할 수 없지만, 전달인자 레이블은 사용이 가능하다.

만일 전달인자 레이블을 사용하고 싶지 않다면 와일드카드 식별자(_) 를 대신 사용하면 된다.

또한, 함수 이름은 같지만 전달인자 레이블을 다르게 하여 함수 오버로딩 기능을 사용할 수 있다는 특징도 있다.

- 암시적 반환을 가진 함수

함수의 전체 내용이 한줄로 표현되어있다면 함수는 맹목적으로 표현식을 반환한다. 즉, return 키워드가 없어도 반환이 가능하다는 것이다.

func greeting(for person: String) -> String {
    "Hello, " + person + "!"
}
print(greeting(for: "Dave"))
// Prints "Hello, Dave!"

func anotherGreeting(for person: String) -> String {
    return "Hello, " + person + "!"
}
print(anotherGreeting(for: "Dave"))
// Prints "Hello, Dave!"

📌 매개변수 기본값과 가변/입출력 매개변수

- 매개변수 기본값

Swift의 함수에서 매개변수는 기본값을 가질 수 있다.

이말인 즉슨, 함수 호출 시에 매개변수가 전달되지 않는다면 해당 매개변수의 기본 값이 자동으로 전달된다는 뜻이다.

func sayHello(_ name: String, times: Int = 3) -> String {
	var result: String = ""
    
    for _ in 0..<times {
    	result += "Hello \(name)!" + " "
    }
    return result
}

print(sayHello("Joe", times: 2)) // 기본 값이 변경되어 그 크기 만큼 반복문 실행
print(sayHello("Hana") // 기본값의 크기만큼 반복문 실행 

책에서는 가급적이면 기본값이 없는 매개변수를 기본값이 있는 매개변수 앞에 사용하라고 권장한다고 적혀있기도 했다 (참고!)

- 가변 매개변수

매개변수로 몇 개의 값이 들어올지 모를 때 활용하는 것이 바로 가변 매개변수 이다.

가변 매개변수는 0개 이상의 값을 받아올 수 있으며, 배열처럼 활용이 가능하다.

또한, 함수마다 가변 매개변수는 1개만 가질 수 있다.

func sayHelloToFriends(me: String, friends names: String...) -> String {
	var result: String = ""
    
    for friend in names {
    	result += "Hello \(friend)!" + " "
    }
    
    result += "I'm " + me + "!"
    return result
}

print(sayHelloToFriends(me: "yagom", friends: "Johansson", "Jay", "Wizplan")) //여러 개의 값을 가변 매개변수는 포함이 가능하다
print(sayHelloToFriends(me: "yagom")) //가변 매개변수 0개 값 포함시

- 입출력 매개변수 (inout 매개변수)

위의 예시코드처럼 함수의 전달인자로 값을 전달할 때는 보통 값을 복사해서 전달한다.

하지만, 만일 값이 아닌 참조를 전달하고 싶을 때는 어떻게 할까?

그런 경우에 사용하는 것이 바로 입출력 매개변수 이다.

입출력(inout) 매개변수를 이용하여 값 타입 데이터의 참조를 전달인자로 보내면 함수 내부에서 참조하여 원래 값을 변경하는 방식으로 동작이 이루어진다.

요약하면 아래와 같다.

1. 함수 호출 시, 전달인자의 값을 복사

2. 해당 전달인자의 값을 변경하면 1에서 복사한 것을 함수 내부에서 변경

3. 함수를 반환하는 시점에 2에서 변경된 값을 원래의 매개변수에 할당

물론, 이러면 함수 외부의 값에 어떤 영향을 줄지 모르기 때문에 함수형 프로그래밍 패러다임에서는 지양하는 패턴이다.

참조는 inout 매개변수로 전달될 변수 또는 상수 앞에 앰퍼샌드(&) 를 붙여서 표현한다.

var numbers: [Int] = [1, 2, 3]

func nonReferenceParameter(_ arr: [Int]) {
	var copiedArr: [Int] = arr
    copiedArr[1] = 1
}

func referenceParameter(_ arr : inout [Int]) {
	arr[1] = 1
}

nonReferenceParameter(numbers)
print(numbers[1]) //2 값이 안바뀜 참조가 없었기 때문

referenceParameter(&numbers)
print(numbers[1]) //1 값이 바뀜 참조가 있었기 때문(&)

입출력(inout) 매개변수는 기본값을 가질 수 없으며, 가변 매개변수로 사용될 수도 없다.

또한 상수는 변경될 수 없으므로 입출력 매개변수의 전달인자로 사용이 불가능하다.

입출력(inout) 매개변수는 잘 사용하면 문제가 없지만, 잘못 사용하면 메모리 안전을 위협한다.


📌 데이터 타입으로서의 함수

함수는 일급 객체이기에, 매개변수 타입과 반환 타입으로 구성된 하나의 타입으로 사용할 수 있다.

함수의 데이터 타입은 받게 될 매개변수의 데이터 타입과 반환될 데이터 타입을 조합하여 결정된다.

func sayHello(name: String, times: Int) -> String {
// ...
}

위의 코드를 예시로 들면, 함수의 타입은 (String, Int) -> String이라고 할 수 있을 것이다.

함수를 데이터 타입으로 사용할 수 있다는 것은 꽤나 의미가 큰 것이, 이러한 특성 때문에 함수를 전달인자로 받을 수도, 반환 값으로 돌려줄 수도 있다는 의미이다.

아래의 코드를 보면 더욱 이해가 쉬울 것이다.

func orderStarbucks() -> String {
  print("스타벅스 주문")
  return "카라멜마끼야또"
}

func orderMegaCoffee() -> String {
  print("메가커피 주문")
  return "꿀아메리카노"fu
}

func orderHollys() -> Int {
  print("할리스 주문")
  // return "바닐라딜라이트"
  return 100
}

var orderCafe: () -> String = orderStarbucks
print("손님 주문하신 \(orderCafe()) 나왔습니다")
orderCafe = orderMegaCoffee
print("손님 주문하신 \(orderCafe()) 나왔습니다")
let orderCafeNumber = orderHollys
print("\(orderCafeNumber()) 번째손님, 커피 나왔습니다")

함수는 데이터 타입으로 사용할 수 있다는 특징 때문에 변수, 상수에 함수를 할당이 가능해 다음과 같은 코드를 작성할 수 있었다.

func inchesToFeet(_ inches: Float) -> Float {
	return inches * 0.833333
 }
 
 func inchesToYards(_ inches: Float) -> Float {
 	return inches * 0.0277778
 }
 let toFeet = inchesToFeet
 let toYards = inchesToYards
 
 
 func outputConvertion(_ converterFunc: (Float)->Float, value: Float) {
 	let result = convertFunc(value)
    print("Result of convertion is \(result)")

func decideFunction(_ feet:Bool) -> (Float) -> Float {
	if feet {
    	return toFeet
    } else {
    	return toYards
    }

위의 예제코드는 함수가 매개변수로 함수를 받았을 경우와 함수가 함수를 반환값으로 가지는 경우를 모두 나타낸 코드이다.

outputConvertion 함수의 매개변수 convertFunc는 Float를 매개변수로 입력받아 Float 타입 값을 반환하는 함수를 값으로 입력받으므로, 선언부에 타입을 작성할 때 (Float) -> Float로 함수 매개변수의 타입을 작성한다.

decideFunction 함수는 Boolean 값을 매개변수로 받고
Float 값을 매개변수로 입력받아 Float 타입의 값을 반환하는 함수를 반환하므로, 선언부에 타입을 작성할 때 (Float) -> Float로 함수 반환 타입을 작성한다.


앞으로 함수에 관한 개념이 가물가물 할 때마다 본 포스팅을 다시 한번 읽어볼 것이며, 부족한 점이 또 생긴다면 포스팅을 업그레이드 시킬 예정이다😃

profile
iOS🍎

0개의 댓글