[Swift] 스위프트 기초 - 함수

koi·2022년 10월 2일
0
post-thumbnail

함수 대부분은 작업의 가장 작은 단위이자 하나의 작은 프로그램이기도 합니다.
swift에서 함수는 일급 객체 이기 때문에 하나의 값으로도 사용할 수 있습니다.

1️⃣ 일급객체란?

다음 조건을 모두 충족해야 일급 객체 가 될 수 있습니다.

  • 전달 인자로 전달할 수 있습니다.
  • 동적 프로퍼티 할당이 가능합니다.
  • 변수나 데이터 구조 안에 담을 수 있습니다.
  • 반환 값으로 사용할 수 있습니다.
  • 할당할 때 사용된 이름과 관계없이 고유한 객체로 구별할 수 있습니다.

스위프트의 함수는 이 조건을 모두 충족할 수 있기에 함수를 일급 객체 로 취급합니다.

함수가 일급 객체 가 된다는 의미는 다양한 종류의 함수를 호출하고, 전달하고, 반환하는 등의 동작만으로 프로그램을 구현할 수 있다는 뜻입니다.

함수와 메서드

함수와 메서드는 기본적으로 같습니다.
구조체, 클래스 열거형 등 특정 타입에 연관되어 사용하는 함수를 메서드, 모듈 전체에서 전역적으로 사용될 수 있는 함수를 그냥 함수라고 부릅니다.

함수의 정의와 호출

조건문이나 반복문 같은 스위프트의 다른 문법과 달리 함수에서는 소괄호 () 를 생략할 수 없습니다.

스위프트의 함수는 재정의(오버라이드)와 중복정의(오버로드)를 모두 지원합니다.

따라서 매개변수의 타입이 다르면 같은 이름의 함수를 여러개 만들 수 있고 매개 변수의 개수가 달라도 같은 이름의 함수를 만들 수 있습니다.

기본적인 함수 정의와 호출

스위프트의 함수는 자유도가 굉장히 높은 문법 중 하나 입니다.기본으로 함수의 이름과 매개 변수, 반환 타입 등을 사용하여 함수를 정의합니다.

  • 함수를 정의하는 키워드는 func입니다.
  • 함수 이름을 지정해준 후 매개변수는 소괄호 () 로 감싸줍니다.
  • 반환타입을 명시하기 전에는 -> 를 사용하여 어떤 타입이 반환될 것인지 명시해줍니다.
  • 반환을 위한 키워드는 다른 언어처럼 return 입니다.
func introduce(name: Stirng) -> String {
	// return "제 이름은" + name + "입니다"
    "제 이름은" + name + "입니다"
}

함수 내부의 코드가 단 한줄의 표현이고, 그 표현의 결괏값의 타입이 함수의 반환 타입과 일치한다면 return 키워드를 생략할 수 있습니다.


매개변수

매개변수가 없는 함수와 매개변수가 여러 개인 함수

매개변수가 없는 함수 정의와 사용

func helloWorld() -> String {
	return "Hello, World!"
}

print(helloWorld()) // Hello, World!

매개변수가 여러개인 함수의 정의와 사용

func sayHello(myName: String, yourName: String) -> String {
	return "Hello \(yourName)! I'm \(myName)"
}

print(sayHello(myName: "yagom", yourName: "toma"))
// Hello yagom! I'm toma

매개변수 이름과 전달인자 레이블

위에서 sayHello(myName:yourName:) 함수를 호출 할 때 myName과 yourName이라는 매개변수 이름을 사용했습니다.

매개변수 이름과 더불어 전달인자 레이블을 지정해줄 수 있습니다.

보통 함수를 정의할 때 매개변수를 정의하면 매개변수 이름과 전달인자 레이블을 같은 이름으로 사용할 수 있지만 레이블을 별도로 지정하면 함수 외부에서 매개변수의 역할을 좀 더 명확히 할 수 있습니다.

전달인자 레이블을 사용하려면 함수 정의에서 매개변수 이름 앞에 한칸을 띄운 후 전달인자 레이블을 지정합니다.

스위프트에서 기본적으로 사용하는 키워드 대부분은 매개변수 이름으로 사용할 수 없습니다. 하지만 이름을 지정해줄 때 백쿼트(`)로 이름을 감싸주면 대부분의 키워드를 이름으로 사용할 수 있습니다
ex. `var`


매개변수 이름과 전달인자 레이블을 가지는 함수 정의와 사용

func sayHello(from myName: String, to name: String) -> String {
	return "Hello \(name)! I'm \(myName)"
}

print(sayHello(from: "toma", to: "yagom"))
// Hello yagom! I'm toma
  • 함수 내부에서 전달인자 레이블을 사용할 수 없으며 함수를 호출할 때는 매개변수 이름을 사용할 수 없습니다.

  • 전달인자 레이블을 사용하고 싶지 않다면 와일드 카드 식별자 _ 를 사용하면 됩니다.

  • 전달인자 레이블을 변경하면 함수의 이름 자체가 변경됩니다.
    → 전달인자 레이블만 다르게 써주더라도 함수 중복 정의(오버로드)로 동작할 수 있습니다.

전달인자 레이블 변경을 통한 함수 중복 정의

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

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

print(sayHello(to: "Chope", 2))
print(sayHello(to: "Chope", repeatCount: 2))

매개변수 기본값

  • 스위프트 함수에서는 매개변수마다 기본값을 지정할 수 있습니다.

  • 매개변수가 전달되지 않으면 기본값을 사용합니다.

  • 매개변수 기본값이 있는 함수는 함수를 중복 정의한 것처럼 사용할 수 있습니다.

  • 기본값이 없는 매개변수는 중요한 값을 전달할 가능성이 높기 때문에
    기본값이 있는 매개변수 앞에 배치하는 것이 좋습니다.

가변 매개변수와 입출력 매개변수

  • 매개변수로 몇 개의 값이 들어올지 모를 때, 가변 매개변수를 사용할 수 있습니다.
  • 가변 매개변수는 0개 이상의 값을 받아올 수 있으며, 가변 매개변수로 들어온 인자 값은 배열처럼 사용할 수 있습니다.
  • 함수마다 가변 매개변수는 하나만 가질 수 있습니다.
    → swift 5.4 릴리즈 이후로 다중 가변 매개변수가 사용 가능해졌습니다.
func sayHelloToFriends(me: Stirng, friends names: String...) -> String {
	var result: String = ""
    
    for friend in names {
    	result += "Hello \(friend)! "
    }
    
    result += "I'm " + me + "!"
    return result
}

함수의 전달인자로 값을 전달할 때는 보통 값을 복사해서 전달합니다.
값이 아닌 참조를 전달하려면 입출력 매개변수를 사용합니다.
값 타입 데이터의 참조를 전달인자로 보내면 함수 내부에 참조하여 원래 값을 변경합니다.

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

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

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

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

nonReferenceParameter(numbers)
print(numbers[1]) // 2

referenceParameter(&numbers)
print(numbers[1]) // 1
  • 입출력 매개변수는 기본값을 가질 수 없음
  • 가변 매개변수로 사용될 수 없음
  • 상수는 변경될 수 없으므로 입출력 매개변수의 전달인자로 사용될 수 없음
  • 잘못 사용하면 메모리 안전을 위협하기도 함

반환이 없는 함수

값이 반환이 굳이 필요하지 않은 경우, 반환 값이 없는 함수를 만들 수 있습니다.
반환값이 없는 함수라면 반환 타입 "없음"을 의미하는 Void로 표기하거나 생략합니다.

반환값이 없는 함수의 정의와 사용

func sayHelloWorld() {
	print("Hello, World!")
}

데이터 타입으로서의 함수

스위프트의 함수는 일급 객체이므로 하나의 데이터 타입으로 사용할 수 있습니다. 즉 각 함수는 매개변수 타입과 반환타입으로 구성된 하나의 타입으로 사용할 수 있습니다.

(매개변수 타입의 나열) -> 반환 타입

함수를 하나의 데이터 타입으로 나타내는 방법입니다.

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

}

sayHello 함수의 타입은 (String, Int) -> String 입니다.


func sayHelloToFriends(me: String, names: String...) -> String { 

}

sayHelloToFriends 함수의 타입은 (String, String...) -> String 입니다.


func sayHello() { 

}

sayHello 함수의 타입은 아래와 같습니다. (다 같은 표현)

  • (Void) -> Void
  • () -> Void
  • () -> ()

함수 타입의 사용

typealias CalculateTowInts = (Int, Int) -> Int

func addTwoInts(_ a: Int, _ b: Int) -> Int {
 return a + b
}

func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
 return a * b
}

var mathFunction: CalculateTwoInts = addTwoInts
mathFunction(2, 5) // 7

mathFunction = multiplyTwoInts
mathFunction(2, 5) // 10
// 전달인자로 함수를 전달받는 함수
func printMathResult(_ mathFunction: CalculateTowInts, _ a: Int, _ b: Int) {
	print("Result: \(mathFunction(a, b))")
}
// 특정 조건에 따라 적절한 함수를 반환해주는 함수
func chooseMathFunction(_ toAdd: Bool) -> CalculateTwoInts {
	return toAdd ? addTwoInts : multiplyTwoInts
}

중첩 함수

스위프트는 데이터 타입의 중첩이 자유롭습니다. 예를 들어 열거형 안에 또 하나의 열거형, 클래스 안에 또다른 클래스가 들어올 수 있습니다.

함수의 중첩은 함수 안에 함수를 넣을 수 있다는 의미입니다.

  • 중첩 함수는 상위 함수의 몸통 블록 내부에서만 함수를 사용할 수 있다.
  • 중첩 함수를 담은 함수가 중첩 함수를 반환하면 밖에서도 사용할 수 있다.

-3-2-10123

원점이 0 이고 좌로는 음수, 우로는 양수로 이루어진 보드입니다.
특정 위치에서 원점으로 이동하는 함수를 만드려고 합니다.

typealias MoveFunc = (Int) -> Int

func goRight(_ currentPosition: Int) -> Int {
	return currentPosition + 1
}

func goLeft(_ currentPosition: Int) -> Int {
	return currentPosition - 1
}

func functionForMove(_ shouldGoLeft: Bool) -> MoveFunc {
 return shouldGoLeft ? goLeft : goRight
}

var postion: Int = 3

let moveToZero: MoveFunc = functionForMove(position > 0)

while position != 0 {
	position = moveToZero(position)
}

goLeft와 goRight가 사소한 기능의 차이일 뿐 원점을 찾아가는 목적은 같습니다. 따라서 굳이 모듈 전역에서 사용할 필요가 없습니다.

그래서 사용 범위를 함수를 하나의 함수 안쪽으로 배치하여 중첨 함수로 구현하고 필요할 때만 외부에서 사용할 수 있도록 구현해봅시다.

중첩 함수의 사용

typealias MoveFunc = (Int) -> Int 

func functionForMove(_ shouldGoLeft: Bool) -> MoveFunc {
	func goRight(_ currentPosition: Int) -> Int {
    	return currentPosition + 1
    }
	func goLeft(_ currentPosition: Int) -> Int {
    	return currentPosition - 1
    }
    
    return shouldLeft ? goLeft : goRight
}

var position: int = -4
let moveToZero: MoveFunc = functionForMove(position > 0)

while position != 0 {
	position = moveToZero(position)
}

전역함수가 많은 큰 프로젝트에서는 전역으로 사용이 불필요한 goRight(_:) 함수와 goLeft(:_) 함수의 사용 범위를 중첩함수를 통해 더 명확하고 깔끔하게 표현할 수 있습니다.


종료되지 않는 함수

스위프트에는 종료되지 않는 함수가 있습니다.

종료되지 않는다는 의미는 정상적으로 끝나지 않은 함수라는 뜻입니다.
비반환 함수(메서드) 라고 합니다.

  • 정상적으로 끝날 수 없는 함수입니다.
  • 비반환 함수 안에서는 오류를 던진다든가, 중대한 시스템 오류를 보고하는 등의 일을 하고 프로세스를 종료해버립니다.
  • 어디서든 호출이 가능하고 guard 구문의 else 블록에서도 호출할 수 있습니다.
    -재정의는 할 수 있지만 비반환 타입이라는 것을 변경할 수 없습니다.
  • 반환 타입을 Never라고 명시해주면 됩니다.
  • 스위프트 표준 라이브러리에서 사용되는 대표적인 예로는 fatalError 함수가 있습니다.

비반환 함수의 정의와 사용

func crashAndBurn() -> Never {
	fatalError("Something very, very bad happened")
}

crashAndBurn() // 프로세스 종료 후 오류 보고

func someFunction(isAllIsWell: Bool) {
	guard isAllIsWell else {
    	print("마을에 도둑이 들었습니다!")
		crashAndBurn()
	}
    print("All is well")
}

someFunction(isAllIsWell: true) // All is well
someFunction(isAllIsWell: false) // 마을에 도둑이 들었습니다!
// 프로세스 종료 후 오류 보고

반환 값을 무시할 수 있는 함수

프로그래머가 의도적으로 반환 값을 사용하지 않을 경우 컴파일러가 함수의 결과값을 사용하지 않았다는 경고를 보낼 때도 있습니다.

이런 경우 함수의 반환 값을 무시해도 된다는 @discardableResult 선언 속성을 사용하면 됩니다.

func say(_ something: String) -> String {
    return something
}

@discardableResult
func discarableResultSay(_ something: String) -> String {
    return something
}

say("Hello") // 반환 값을 사용하지 않아 컴파일러가 경고를 표시할 수 있음
discarableResultSay("Hello") // 반환 값을 사용하지 않아도 컴파일러가 경고하지 않음
profile
Don't think, just do 🎸

0개의 댓글