본문 바로가기
Programming/Swift

[iOS Swift5] 함수, 메서드 그리고 클로져 기본 개념

by 개발자 염상진 2024. 1. 8.

프로그래밍 영역에서 함수는 반복적인 작업을 대폭 줄여주는 장점이 있습니다. 애플의 스위프트 언어 또한 함수를 제공하고 있는데요, 이번 포스팅에서는 Swift의 함수에 대해 알아보도록 하겠습니다. 

 

 

 

 

Swift 함수와 메서드 기본

함수 Function은 특정 기능을 수행하기 위한 코드 블럭입니다. 특정 데이터를 매개변수로 받을 수도 있고, 함수의 결과물을 반환받을 수도 있습니다. 일반적으로 특정 기능을 수행하기 위한 코드 블럭을 함수라고 지칭합니다. 

 

함수와 유사하게 사용되는 개념이 메서드인데요, 메서드는 특정 클래스나 구조체 내에서 사용하는 함수를 통상 메서드라고 지칭합니다. 함수와 메서드가 작동하는 방식은 동일하지만 사용방법에서 조금 다르다는 차이점이 있습니다. 

Swift에서 함수를 사용하는 기본 방법은 다음과 같습니다.

func <함수이름>(매개변수 : 타입 어노테이션) -> 출력 타입 {
	// 실행 코드
}

 

이 함수를 실행하기 위해서는 다음과 같이 사용할 수 있습니다.

함수명(인자)

반면 메서드는 클래스나 구조체 내부에서 사용되는 함수, 코드 블럭을 의미합니다. 아래 예제에서는 displayInfo()가 메서드가 됩니다. 

class Car {
    var brand: String
    var model: String
    var year: Int
    
    init(brand: String, model: String, year: Int) {
        self.brand = brand
        self.model = model
        self.year = year
    }
    
    func displayInfo() {
        print("Car: \(brand) \(model), Year: \(year)")
    }
}

 

 

#1 매개변수 vs 인자

여기서 매개변수(parameter)인자(argument)가 헷갈릴 수 있는데요, 통상 함수를 정의할 때 어떤 타입의 데이터를 전달받겠다고 함수 선언식에 표시하는 변수를 매개변수(parameter)라고 지칭합니다. 

반면 선언된 함수를 사용하는 코드에서 특정 데이터를 전달하는 상황에서는 인자(argument)라는 개념을 사용합니다. 다음 예를 살펴보겠습니다.

greet이라는 함수는 name이라는 매개변수를 전달받고 있습니다. 실제 greet함수를 호출하는 시점에 name에 Alice 문자열을 인자로 전달해주고 있습니다. 

func greet(name: String) {
    print("Hello, \(name)! Welcome!")
}

// Calling the function with an argument
greet(name: "Alice") // Output: Hello, Alice! Welcome!

#2 반환값 처리

함수 결과값을 전달 받기 위해서는 할당 연산자(=)를 통해서 전달 받을 수 있습니다. 물론 함수 반환타입과 값을 전달받는 변수의 타입이 같아야 합니다.

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

// Calling the function and capturing the return value
let result = add(a: 5, b: 3)
print("The sum is: \(result)") // Output: The sum is: 8

위 예제에서 add() 함수의 반환타입이 Integer 타입이므로 result 상수의 타입 또한 Integer로 암묵적으로 추론되어 컴파일 되게 됩니다. 

또한 Swift에서 함수 내부 코드 블럭이 1줄인 경우 return 키워들 사용하지 않아도 됩니다. 아래 예제 또한 위의 코드와 동일한 결과를 얻을 수 있습니다. 

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

// Calling the function and capturing the return value
let result = add(a: 5, b: 3)
print("The sum is: \(result)") // Output: The sum is: 8

 

 

 

만약 결과값을 사용하지 않는 경우에는 반환값을 '_'에 할당해서 버릴 수도 있습니다. 이런 코드가 필요한 경우가 간혹 있습니다. 함수가 실행은 되어야 되는데 반환값이 필요없는 경우 이런 코드를 사용할 수 있습니다.

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

// Calling the function and ignoring the return value
_ = multiply(a: 4, b: 3)

 

#3 외부 매개변수 사용

함수를 선언할 때 특정 매개변수를 선언하고 그 값에 인자를 전달해서 함수를 실행했습니다. 하지만 여기서 추가적으로 외부 매개변수명을 별도로 지정할 수 있습니다. 함수의 매개변수를 좀 더 식별 가능하도록 하고 함수의 기능적인 측면을 살려서 사용하도록 합니다. 

func greet(personName name: String) {
    print("Hello, \(name)!")
}

// Calling the function using the external parameter name
greet(personName: "Alice") // Output: Hello, Alice!

위 예제를 보시면 매개변수는 name으로 지정되어 greet(name: "Alice")라고 함수를 호출해야 하는게 일반적입니다. 하지만 외부 매개변수(external parameter)을 지정해서 사용할 수도 있습니다. 만약 함수 호출 시점에 매개변수를 지정하지 않고 사용하기 위해서는 '_'를 사용할 수 있습니다.

func greet(_ name: String, _ age: Int) {
    print("Hello, \(name) you're \(age) years old!")
}

// Calling the function using the external parameter name
greet("Alice", 22) // Output: Hello, Alice you're 22 years old!!

다만 외부 매개변수를 _로 사용하는 경우에는 반드시 매개변수의 순서를 지켜서 호출해야 컴파일 에러가 나지 않습니다. 매개변수를 일일이 적어주는 것 보다는 인자만 전달해서 사용하는 편이 더 편리하지만 가독성 측면에서는 매개변수의 이름을 정확히 지정해서 사용하는 편이 좋습니다.

 

#4 함수의 매개변수는 기본적으로 상수다

함수 내에서 사용되는 매개변수는 기본적으로 상수입니다. Swift에서는 함수가 호출될 때 전달받은 인자의 값을 함수 내부에서 수정하는 걸 막고 있습니다. 

func changeNumber(number: Int) -> Int {
    number = 3
    return number
}

위의 예제처럼 함수 매개변수 값을 변경하려고 하면 let으로 선언된 변수의 값을 수정할 때와 동일한 컴파일 에러 메세지가 출력됩니다.

따라서 함수 내부에서 매개변수 값을 수정하기 위해서는 새로운 변수를 선언하고 값을 할당한 다음 사용해야 합니다. 매개변수의 이름과 새로운 변수의 이름(shadow parameter)이 겹쳐도 괜찮습니다. 매개변수의 값 자체에는 영향을 미치지 않기 때문입니다.  

func changeNumber(number: Int) -> Int {
    var number = number
    
    number = 3
    
    return number
}

let changedValueToThree = changeNumber(number: 10)

 

#5 함수 밖의 변수를 수정하는 방법 inout

위에서 매개변수는 기본적으로 상수라고 배웠습니다. 그럼 매개변수를 통해 들어오는 변수를 수정할 때마다 복사본을 만들어야 할까요? 그건 아닙니다. Swift 언어는 함수 밖에 존재하는 변수를 직접적으로 변경할 수 있는 기능이 있는데요 inout 키워드를 사용하면 됩니다. 

func swapValues(_ a: inout Int, _ b: inout Int) {
    let temp = a
    a = b
    b = temp
}

var num1 = 5
var num2 = 10

print("Before swapping: num1 = \(num1), num2 = \(num2)")

// Using '&' before the variables to pass them as inout parameters
swapValues(&num1, &num2)

print("After swapping: num1 = \(num1), num2 = \(num2)")

num1과 num2변수는 swapValue() 함수 밖에 존재하고 있습니다. 2개의 변수를 매개변수로 받아 함수 내부에서 값을 수정해서 반환하는 기능을 담고 있습니다. 하지만 swapValue 함수 내에서 복사본을 생성하는 작업없이 매개변수의 값을 바로 수정할 수 있습니다. 

매개변수 앞에 inout 키워드를 붙여주고, 함수 호출 시에도 & 기호를 붙여서 호출해주면 함수 밖의 변수 값을 수정할 수 있게 됩니다. 기본적으로 함수 내부에서 선언된 지역 변수는 전역 변수에 관여할 수 없지만 예외적인 케이스에서 inout 키워드를 사용할 수 있습니다. 다만 inout을 사용할 경우 예상치 못한 변수 값 수정으로 인해 런타임 에러가 날 수 있으니 신중하게 사용하셔야 합니다. 

 

 

 

 

#6 매개변수 디폴트 값 지정

만약 함수를 실행할 때 매개변수에 특정 값을 기본값으로 지정할 수 있습니다. 기본값이 설정된 매개변수는 별도의 값을 전달해주지 않아도 함수 내에서 그대로 사용할 수 있습니다. 

func greet(name: String = "Guest") {
    print("Hello, \(name)!")
}

// Calling the function without specifying the parameter
greet() // Output: Hello, Guest!

// Calling the function and providing a specific name
greet(name: "Alice") // Output: Hello, Alice!

위 예제에서는 name이라는 매개변수에 "Guest"라는 기본값을 설정하고 있습니다. 따라서 greet() 함수를 호출할 때 별도의 값을 지정하지 않으면 "Guest"가 출력됩니다. 이는 로그인을 하지 않았을 때 게스트 모드로 진입하는 경우 유용하게 사용할 수 있습니다. 

 

#7 가변 매개변수

함수 내에서 매개변수의 갯수가 지정이 되지 않는 경우가 있습니다. 이런 경우 지정되지 않은 임의 개수의 매개변수를 전달받아 함수 내에서 처리할 수 있는데요 이를 가변 매개변수(variadic parameter)라고 합니다. 자바스크립트에서도 동일한 문법이 있습니다. 가변 매개변수는 배열 타입으로 함수내에 전달되게 됩니다. 

가변 매개변수 사용은 (...)을 매개변수 타입 어노테이션 뒤에 붙여주시면 됩니다. 아래 예제에서 numbers라는 가변 매개변수를 Int 타입으로 선언했고, for-in 반복 연산자로 순회하면서 total 값을 출력해줍니다. 여기서 numbers 매개변수의 타입은 Array<Int> 형으로 함수 내부로 전달되게 됩니다. 

func sum(numbers: Int...) -> Int {
    var total = 0
    for number in numbers {
        total += number
    }
    return total
}

// Calling the function with different numbers of arguments
let result1 = sum(numbers: 1, 2, 3, 4, 5)
print("Sum 1: \(result1)") // Output: Sum 1: 15

let result2 = sum(numbers: 10, 20)
print("Sum 2: \(result2)") // Output: Sum 2: 30

let result3 = sum() // Calling with no arguments
print("Sum 3: \(result3)") // Output: Sum 3: 0

 

#8 함수를 매개변수로 받는 함수

말이 어렵게 느껴질 수 있지만 간단합니다. Swift 언어에서는 함수를 일반 데이터 타입 처럼 사용할 수가 있습니다. 마치 자바스크립트에서 고차함수를 사용하는 것 처럼 말이죠. 

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

let sum = calculateSum

print(sum(1,2)) // 3

위 코드에서 calculateSum() 함수를 선언하고 sum 상수에 할당할 수 있습니다. 이제 sum() 상수를 마치 함수처럼 호출해서 사용할 수 있게 됩니다. 이와 같이 함수를 변수처럼 사용할 수 있는 Swift 언어의 특징 덕분에 아래와 같은 코드가 가능해집니다.

func performOperation(_ operation: (Int, Int) -> Int, _ a: Int, _ b: Int) -> Int {
    return operation(a, b)
}

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

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

// Using performOperation to perform addition
let additionResult = performOperation(add, 5, 3)
print("Addition Result: \(additionResult)") // Output: Addition Result: 8

// Using performOperation to perform subtraction
let subtractionResult = performOperation(subtract, 10, 4)
print("Subtraction Result: \(subtractionResult)") // Output: Subtraction Result: 6

performOperation() 함수는 매개변수로 함수, 매개변수1, 매개변수2를 전달받습니다. 이제 전달받은 함수 매개변수를 실행한 값을 반환하게 됩니다. 실행해보면 함수를 매개변수로 전달받아서 정상적으로 출력이 되는 것을 확인할 수 있습니다. 

 

 

 

 

Swift 클로져

원래 컴퓨터 공학에서 클로저는 함수 혹은 클로져 표현식과 같은 독립적인 코드 블럭 주변에 하나 이상의 변수와 결합된 형태를 의미합니다. 가령 아래 예제처럼 함수를 매개변수를 받는 함수를 선언하고 실행하면 분명 함수 내부에 있는 변수임에도 불구하고 캐시 메모리 처럼 사용할 수 있는 함수 형태를 클로져라고 합니다.

func makeIncrementer(_ incrementAmount: Int) -> () -> Int {
    var total = 0
    
    let incrementer: () -> Int = {
        total += incrementAmount
        return total
    }
    
    return incrementer
}


let incrementByFive = makeIncrementer(5)

print(incrementByFive()) // Output: 5
print(incrementByFive()) // Output: 10
print(incrementByFive()) // Output: 15

여기서 incrementer 함수는 total이라는 변수를 capture 잡고 있기도 하고 closed over가두고 있다고도 할 수 있습니다. 또한 makeIncrementer() 함수는 8~10 라인의 클로져를 반환하고 있습니다. 

반면 클로져 표현식은 독립적인 코드 블록을 의미합니다. 

let numbers = [1, 2, 3, 4, 5]

// Using map with a closure expression to square each number
let squaredNumbers = numbers.map({ (number: Int) -> Int in
    return number * number
})

print(squaredNumbers) // Output: [1, 4, 9, 16, 25]

위 예제에서는 아래와 같은 클로져 표현식으로 작성된 클로져가 map 함수의 매개변수로 사용되었습니다. 클로져 표현식은 이름을 가지지 않고 in 키워드를 사용해서 선언문과 코드문을 구분하는 특징이 있습니다. 

{ (number: Int) -> Int in
    return number * number
}

또한 클로져는 약식 인수 이름 shorthand argument name을 지원합니다. 말로 하면 어려우니 바로 예제를 살펴보도록 하죠. 

let numbers = [1, 2, 3, 4, 5]

// Using map with a closure expression using shorthand argument names
let squaredNumbers = numbers.map({ $0 * $0 })

print(squaredNumbers) // Output: [1, 4, 9, 16, 25]

numbers Int 형 배열을 순회하는 map 함수가 있습니다. 이 때 인자가 전달되면서 특정 인덱스 값을 수정하고 반환하는데요, 조금 풀어서 클로져를 사용하면 상수에 클로저 표현식을 사용해서 함수를 정의하고 매개변수로 전달할 수 있습니다.

let instanceMap: (Int) -> Int = { $0 * $0 }
let squaredNumbers = numbers.map(instanceMap)

만약 약식 인수를 사용하지 않으면 다음과 같은 모양새가 됩니다. 코드는 조금 더 복잡해지지만 한눈에 볼 수 있어 가독성은 높습니다. 

let squaredNumbers = numbers.map({(number: Int) -> Int in
    return number * number
})

 

Swift 함수 클로져 정리

Swift의 함수 선언과 호출 방식은 Javascript와 매우 유사합니다. 타입 어노테이션이 붙어있는 모양새는 타입스크립트와도 유사하다고 할 수 있습니다. 자바스크립트의 고차함수를 생각하신다면 이해하시는데 큰 무리는 없습니다. 

매개변수의 전달과정과 반환값 그리고 함수의 코드블럭을 단순화한 클로져를 사용하신다면 코드를 반복적인 업무를 제거하고 한결 간단하고 가시성 높은 코드를 작성하실 수 있습니다.

 

 

🚀️ 도움이 되셨다면 구독좋아요 부탁드립니다 👍️

 

 

 

 

[iOS Swift5] if else, guard 제어 흐름 관리하기

Swift5 제어흐름 관리하기 안녕하세요 About Tech 입니다. 이번 포스팅에서는 Swfit 언어에서 제어흐름을 어떻게 관리할 수 있는지 알아보고자 합니다. 제어흐름은 조건식이라고도 불리는 if 문이나 gu

about-tech.tistory.com

 

 

[iOS Swift5] 연산자와 표현식

Swift 연산자와 표현식 변수에 값을 할당하는 방법은 기본적으로 표현식을 따릅니다. 피연산자와 할당자 그리고 연산자를 통해 값을 생성하고 변수에 데이터를 저장할 수 있습니다. var number = 1 +

about-tech.tistory.com

 

 

맥북 에어 코딩 괜찮을까?

개발자가 맥북을 선택하는 이유? 개발자를 희망한다면 노트북을 고를 때 1순위 제품이 바로 맥북입니다. 개발자가 맥북을 선탠하는 이유는 대표적으로 맥 OS라고 할 수 있습니다. 예를 들어 앱스

about-tech.tistory.com

 

댓글