[Swift 문법] - 함수, 옵셔널, 옵셔널 언래핑

sun02·2021년 8월 17일
0

100 days of Swift 

목록 보기
14/40

1. Functions

함수를 사용하면 특정 기능을 수행하는 재사용가능한 코드를 정의할 수 있다. 일반적으로 함수는 작동방식을 수정하기 위해 일부 값을 수정할 수 있지만 필수는 아니다.

간단한 함수로 시작해 보자:


func favoriteAlbum() {
    print(“My favorite is Fearless”)
}

만약 이 코드를 플레이그라운드에 작성한다면, 아무것도 출력되지 않을 것이다. 그리고 맞아, 그게 맞다. 아무것도 출력되지 않는 이유는 우리가 “My favorite is Fearless” 메세지를 favoriteAlbum()이라 불리는 함수 안에 작성하였기 때문이고 우리가 favoriteAlbum() 함수를 실행하라고 하기 전까진 저 코드는 호출되지 않을 것이다. 호출하기 위해선, 이 코드를 추가해라:

favoriteAlbum()

이것은 함수를 실행(또는 호출)하기 때문에 너는 이제 “My favorite is Fearless”가 출력되는 것을 볼 수 있을 것이다.

너도 보듯이, 너는 func를 작성한 뒤 함수 이름을 작성하고 열기 닫기 괄호, 그리고 열기 닫기 중괄호로, 그리고 코드블럭을 작성함으로써 함수를 정의한다. 그리고 그 함수를 이름과 열기 닫기 괄호를 함께 작성함으로써 호출한다.

당연히, 그것은 바보같은 예시이다 – 그 함수는 무슨 일이 있어도 같은 일을 하므로 존재하는 의미가 없다. 하지만 매번 다른 앨범을 출력하고 싶다면 어떻게 해야할까? 이 경우, 우리는 함수가 호출될 때 값을 받아들이고 그 값을 내부에서 사용하기를 원한다고 스위프트에게 말할 수 있다.

이제 실행해보자:

func favoriteAlbum(name: String) {
    print(“My favorite is \(name)”)
}

이것은 스위프트에게 우리가 “name”이라는 이름의 값(“파라미터”라고 불림)을 받아들이는 함수를 원하고 그것은 문자열이여야한다고 말한다. 그 후 문자열 보간을 사용하여 가장 좋아하는 앨범 이름을 출력 메세지에 바로 작성한다. 함수를 호출하기 위해, 너는 이것을 작성하면 된다:


favoriteAlbum(name: “Fearless”)

여전히 한 줄의 코드에 불과하다는 것을 고려하면 너는 요점이 뭔지 여전히 궁금할 것이다. 글쎄, 큰 앱에서는 20여개의 다른 위치에서 이 함수가 사용된다고 상상해봐라, 너의 수석 디자이너가 나타나 메세지를 “I love Fearless so much – it’s my favorite!”라고 바꾸라고 말한다면 네 코드의 모든 20여개의 인스턴스를 찾아서 변경할 것인가? 아마 아니다. 함수를 한 번 변경하면, 모든것이 업데이트된다.

함수는 네가 원하는 만큼 많은 파라미터를 받아들이도록 만들 수 있기 때문에 이름과 년도를 받아들이도록 만들어보자


func printAlbumRelease(name: String, year: Int) {
    print(“\(name) was released in \(year)”)
}

printAlbumRelease(name : “Fearless”, year: 2008)
printAlbumRelease(name : “Speak Now”, year: 2010)
printAlbumRelease(name : “Red”, year: 2012)

이 함수 파라미터 이름들은 중요하고 실제로 함수의 일부를 형성한다. 가끔 같은 이름을 가진 여러 함 수를 볼 수 있다, 예를 들어 Handle(), 그리나 다른 작업을 구별하기 위해 다른 매개변수 이름을 사용한다.

External and internal parameter names

가끔 너는 함수가 호출될 때 파라미터가 한 가지 방식으로 이름 짓고 함수 내에서 다른 방식으로 이름을 지정하려는 경우가 있다. 이것은 함수를 호출할 때는 거의 자연스러운 영어를 사용하지만 함수 내부에는 파라미터에 적절한 이름이 있다. 이 기술은 스위프트에서 매우 자주 사용되으로 지금 이해하는 것이 좋다.

이것을 보기 위해, 편지의 개수를 문자열로 출력하는 함수를 작성해보자. 이것은 문자열의 count 프로퍼티를 사용하면 가능하다

func countLettersInString(string: String) {
    print(“The string \(string) has \(string.count) letters.”)
}

저 함수를 우리는 다음과 같이 호출할 수 있다:

countLettersInString(string: “Hello”)

이것은 확실히 작동하지만, 조금 말이 많다. 게다가 그것은 당신이 소리내어 말하는 종류가 아니다: “ 문자열의 문자 갯수 문자열 안녕”

스위프트의 해결책은 호출할 때의 파라미터와 메서드 안의 파라미터를 각각 지정하는 것이다. 이것을 사용하기 위해, 파라미터 이름을 두 번 작성해라 – 하나는 외부용, 하나는 내부용

예를 들어, 우리는 호출될 때 마라피터 이름을 myString, 메서드 내부에서는 str 으로 이름 지을 수 있다.

func countLettersInString(myString str: String) {
    print(“The string \(str) has \(str.count) letters.”)
}

countLettersInString(myString: “Hello”)

너는 ‘_’를 외부 파라미터 이름으로 지정할 수 있다. 이것은 외부 파라미터 이름이 없다는 의미이다. 예를 들어,


func countLettersInString(_ str: String) {
    print(“The string \(str) has \(str.count) letters.”)
}

countLettersInString(“Hello”)

보이듯이, 이것은 코드가 영어문장처럼 읽어질 수 있게 한다. “cout Letters in String hello.”

_ 를 사용하는 것이 옳은 경우가 많이 있지만 스위프트 프로그래머들은 일반적으로 모든 파라미터에 이름 짓는 것을 더 선호한다. 그리고 생각해 봐라: 우리는 왜 함수에서 “String” 이라는 단어를 필요로하는가 – 문자를 세고자 하는 다른 것에는 또 무엇이 있는가?

따라서, 너는 주로 외부 파라미터의 이름으로 “in”, “for’, “with” 을 볼 것이다. 그리고 내부 파라미터 이름은 더 의미가 있을 것이다. 따라서, 이러한 함수를 작성하는 “Swifty”한 방법은 다음과 같다 :

func countLetters(in string: String) {
    print(“The string \(string) has \(string.count) letters.”)
}

이 함수를 “in” 파라미터로 호출할 수 있고 이는 함수 내애서 의미가 없다. 그러나, 함수 내애서 같은 파라미터가 “string”이라 불리고 이것이 더 유용하다. 따라서, 함수는 다음과 같이 호출된다.

countLetters(in: “Hello”)

이것이 진정 스위프티한 코드이다. : “count letters in hello” 자연스럽게 영어문장처럼 읽어지면서 코드는 간결하고 정확하다.

Return values

스위프트에서 함수는 파라미터 뒤에 -> 와 데이터타입을 작성하여 값을 반환할 수 있다. 한 번 이것을 하면, 스위프트는 네 함수가 무엇이든 간에 값을 반환하도록 보장한다. 따라서 이것은 네 코드가 하는 일에 대한 약속을 만드는 것이다.

예를 들어, 테일러 스위프트의 앨범 중 하나라면 참을 반환하고, 그렇지 않으면 거짓을 반환하는 함수를 작성해보자. 이것은 하나의 매개변수(확인해야하는 앨범의 이름)을 받아야하고 Boolean을 반환할 것이다. 여기 코드가 있다:

func albumIsTaylor(name: String) -> Bool {
    if name == “Taylor Swfit” { return true }
if name == “Fearless” { return true }
if name == “Speak Now” { return true }
if name == “Red” { return true }
if name == “1989” { return true }

    return flase
}

이번에 새로 배운 switch/case문을 사용해보고 싶다면, 이 함수가 그 기능이 잘 작동하는 곳이다.

이제 앨범이름을 전달하고 결과에 따라행동하도록 호출할 수 있다.


if albumIsTalyer(name: “Red”) {
    print(“That’s one of hers!”)
} else {
    print(“Who made that?!”)
}

if albumIsTaylor(name: “Blue”) {
    print(“That’s one of hers!”)
} else {
    print(“Who made that?!”)
}

2. Optionals

스위프트는 매우 안전한 언어이다. 코드가 네가 예상하지 못한 방식으로 실패하지 않도록 노력한다.

코드가 실패하는 가장 흔한 경우 중 하나는 사라지거나 없는 데이터를 사용하려 할 때이다. 예를 들어, 함수가 다음과 같다고 상상해보자

func getHaterStaus() -> String {
    return “Hate”
}

이 함수는 매개변수를 받지 않고 문자열“Hate”를 반환하다. 하지만 만약 오늘 날씨가 유독 좋아서 헤이터들이 헤이팅을 하고 싶지 않다면 – 어떻게 되나? 글쎄, 우리는 아무것도 반환하고 싶지 않을 것이다: 이 헤이터가 오늘은 어떠한 헤이팅도 하지 않는다.

문자열에 관해서 빈 문자열이 아무것도 전달하지 않는 좋은 방법이라고 생각할 수 있고 그것이 떄론 맞다. 하지만 숫자에 대해서는 어떤가 – 0 이 “빈숫자” 인가? 아니면 -1?

너만의 가상의 규칙을 만들려하기 전에, 스위프트는 해결책을 갖고 있다 : optionals. 옵셔널 값은 값을 가질 수도 있고 가지지 않을 수도 있다. 대부분은 사람들은 옵셔널을 이해하기 어려워하고 그것은 괜찮다. – 나는 이것을 여러가지 방법으로 설명할 것이고 그 중 하나라도 이해되길 바란다.

이제, 네가 “1에서 5 점 중 테일러 스위프트는 몇 점인가?”라고 물어야하는 설문을 해야한다고 상상해보자 – 누군가가 그녀에 대해 들어본 적이 없다고 하면 어쩔 것인가? 1은 그녀를 부당하게 비난하는 것이고 5는 테일러 스위프트를 모르는 사람을 칭찬하는 것이다. 해결책은 옵셔널이다 : “점수를 주고 싶지 않아요.”

우리가 -> String 을 사용할 때 이것은 “이것은 분명히 문자열을 반환한다”라는 의미인다. 이는 이 함수가 빈 값을 반환할 수 없고 네가 항상 문자열로 사용할 수 있는 값을 받는다는 것을 알기 때문에 안전하다고 불릴 수 있다. 만약 스위프트에게 이 함수가 값을 반환할 수도 있고 아닐 수도 있다고 말하고 싶다면, 우리는 대신 이것을 사용해야 한다.:

func getHaterStatuse() -> String? {
    return “Hate”
}

추가된 물음표: String? 에 주목해라. String? 은 “옵셔널 문자열”을 의미한다. 이제, 우리의 함수는 여전히 “Hate”를 반환하지만 함수를 더 수정해 보자 : 만약 날씨가 화창하다면, 헤이터들은 새로운 사람이 되어 헤이팅하는 삶을 포기하므로 우리는 빈 값을 반환한다. 스위프트에서 이 “빈 값”은 특별한 이름을 가진다 : nil

위 함수를 다음과 같이 바꾸자:


func getHaterStautus(weather: String) -> String {
    if weather == “sunny” {
        return nil
    } else {
        return “Hate”
    }
}

이 함수는 하나의 문자열 매개변수(weather)을 받고 하나의 문자열(hating status)를 반환하지만 그 반환값은 있을 수도 있고 없을 수도 있다 – nil. 이런 경우, 우리는 문자열을 얻거나 nil을 얻을 수도 있다고 말한다.

이제 중요한 사항에 대해 알아보자 : 스위프트는 너의 코드가 매우 안전하길 바라고 nil값을 사용하려는 것은 안좋은 생각이다. 너의 코드를 충돌시키거나 앱 로직이 엉망이 되거나 사용자 인터페이스에 잘못된 내용이 표시될 수 있다. 따라서 값을 옵셔녈으로 선언할 때, 스위프트는 네가 그 값을 안정하게 처리하도록 보장한다.

이것을 시도해 보자 : 네 플레이그라운드에 이 코드 라인을 추가하자:

var status: String
status = getHaterStatus(weather: “rainy”)

첫번째 라인은 문자열 변수를 생성하고 두 번째 줄은 getHaterStause()의 값을 변수에 할당한다 – 그리고 오늘의 날씨는 비가 오기때문에, 헤이터들은 헤이팅을 할 것이다.

이 코드는 실행되지 않는다. 왜냐하면 우리는 status를 값을 필요로하는 문자열타입이라 했지만 getHaterStatus()는 옵셔널 문자열을 반환하기 때문에 값을 제공하지 않을 수도 있기 때문이다. 즉, 우리는 status안에 문자열이 분명히 있다고 하였지만 getHaterStatus()는 아무것도 반환하지 않을 수도 있다는 것이다. 스위프트는 네가 이런 실수를 하도록 두지 않고 이것은 매우 유용하다. 이것은 일반적인 버그의 모든 클래스를 효과적으로 차단하기 때문이다.

문제를 해결하기 위해, 우리는 status 변수를 String? 으로 만들거나 타입 주석(annotation)을 제거하고 스위프트가 타입 추론을 사용하도록 할 수 있다. 첫번째 옵션은 다음과 같이 생겼다:


var status: String?
status = getHaterStatus(weather: “rainy”)

두 번째 옵션은 다음과 같다 :


var status = getHaterStatus(weather: “rainy”)

네가 무엇을 선택하든 그 값은 있을 수도 있고 없을 수도 있으며 기본적으로 스위프트는 네가 이 값을 위험하게 사용하도록 하지 않는다. 예를 들어, 다음과 같은 함수를 상상해 보자:

func takeHaterAction(status: String) {
    if status == “Hate” {
        print(“Hating”)
    }
}

이 함수는 문자열을 받아 그 내용에 따라 메시지를 출력한다. 이 함수는 String 값을 받고 String?값은 받지 않는다 – 여기서 옵셔널을 보낼 수 없고, 실제 문자열을 원하므로 status 변수를 사용하여 호출할 수 없다.

스위프트는 두 가지 해결책을 가진다. 두 가지 다 사용되지만 하나가 다른 하나보다 확실히 선호된다. 첫 번째 해결책은 optional unwrapping이라 불리고 이것은 특수한 구문을 사용하는 조건문 안에서 수행된다. 이것은 동시에 두 가지 작업을 수행한다 : 옵셔널이 값을 가지는지 확인하고, 만약 그렇다면 그것을 옵셔널이 아닌 유형으로 언래핑한 뒤 코드 블럭을 실행한다.

구문은 다음과 같이 생겼다:


If let unwrappedStatus = status {
    // unwrappedStatus 가 옵셔널이 아닌 값을 가지는 경우
} else {
    // else 블럭이 필요하다면, 여기 이렇게 사용해!
}

위 if let 조건문은 한 줄의 간결한 코드에서 확인과 언래핑을 하므로 매우 일반적이다. 이 방법을 사용하여 getHaterStatus()의 리턴 값을 안전하게 언래핑할 수 있고 우리가 takeHaterAction()을 유효하고 옵셔널이 아닌 문자열로 호출하도록 보장할 수 있다. 여기 완성된 코드 이다:


func getHaterStatus(weather: String) -> String? {
    if weather == “sunny” {
        return nil
    } else {
        return “Hate”
    }
}

func takeHaterAction(status: String) {
    if status == “Hate” {
        print(“Hating”)
    }
}

If let haterStatus = getHaterStatus(weather: “rainy”) {
    takeHaterAction(status: haterStatus)
}

이 개념을 이해했다면 “Force unwrapping optionals” 타이틀로 스킵하여 내려가도 된다. 만약 옵셔널에 대해 여전히 확실하지 않다면, 계속 읽어보자.

아직 여기 있다면 위의 설명이 말이 안되거나, 이해가 되었지만 설명이 필요할 수 있음을 의미한다. 옵셔널은 스위프트에서 광범위하게 사용되므로 너는 옵셔널을 이해할 필요가 있다. 나는 다른 방식으로 설명할 것이니 이것이 도움이 되길 바란다!

여기 새로운 함수가 있다:


func yearAlbumReleased(name: String) -> Int {
    if name == “Tayloer Swift” { return 2006 }
    if name == “Fearless” { return 2008 }
    if name == “Speak Now” { return 2010 }
    if name == “Read” { return 2012 }
    if name == “1989” { return 2014 }

    return 0
}

이것은 테일러 스위프트의 앨범 이름을 받고 그것이 발매된 년도를 반환한다. 하지만 만약 우리가 Taylor Swift와 HudsonMohawke를 헷갈려 “Lantern”이라는 이름으로 호출한다면 이것은 0을 반환할 것이다.

하지만 여기서 0 이 의미가 있나? 물론, 만약 앨범이 Caesar Augustus가 로마의 황제였던 AD 0년에 앨범이 발매되었다면 0이 의미가 있을 수 있지만 여기서는 혼란스럽다. – 사람들은 0이 “인식되지 않음”을 의미한다는 것을 미리 알아야한다.

더 좋은 아이디어는 함수를 재작성하여 정수를 반환하거나 nil을 반환하게 하는 것이다, 이것은 옵셔널 덕에 쉽다. 여기 새로운 합수가 있다 :


func yearAlbumReleased(name: String) -> Int? {
    if name == “Tayloer Swift” { return 2006 }
    if name == “Fearless” { return 2008 }
    if name == “Speak Now” { return 2010 }
    if name == “Read” { return 2012 }
    if name == “1989” { return 2014 }

    return nil
}

이제 이것은 nil을 반환하고, 우리는 그 값이 존재하는지 아닌지 확인하기 위해 if let을 사용하여 그 결과를 언래핑한다.

여기 이름 배열이 있다 :

var items = [“James”, “John”, “Sally”]

만약 우리가 해당 배열을 보고 특정 이름의 인덱스를 알려주는 함수를 작성하고 싶다면 다음과 같이 작성할 수 있다.

func position(of string: String, in array: [String]) -> Int {
    for i in 0..< array.count {
        if array[i] == string {
        return i
        }
    }
    return 0
}

이것은 배열 안의 모든 항목을 반복하여 일치하는 항목을 찾으면 위치를 반환하고 그렇지 않으면 0을 반환한다.

이제 이 네 줄의 코드를 실행해보자:

let jamesPosition = position(of: “James”, in: items)
let johnPosition = position(of: “John”, in: items)
let sallyPosition = position(of: “Sally”, in: items)
let bobPosition = position(of: “Bob”, in: items)

이것은 0, 1, 2, 0 을 출력할 것이다 – James는 존재하고 Bob는 존재하지 않는데도 둘의 위치는 같다. 이것은 내가 0을 “발견되지 않음”의 의미로 사용하였기 때문이다. 쉬운 수정은 -1을 발견되지 않음으로 만드는 것이지만 0이든 -1이든 특정 숫자가 “발견되지 않음”을 의미한다는 것을 기억해야하기 때문에 여전히 문제가 있다.

해결책은 옵셔널이다 : 일치하면 정수를 반환하고, 그렇지 않으면 nil을 반환해라. 사실, 이것은 내장된 “배열에서 찾기” 메서드가 사용하는 정확히 그 접근방식이다 : someArray.firstIndex(of: someValue)

이러한 “있을 수도 있고 없을 수도 있는” 값으로 작업할 때, 스위프트는 사용하기 전에 해당 값을 강제로 언래핑하여 값이 없을 수도 있음을 인정한다. 이것이 if let 구문이 하는 일이다 : 만약 그 옵셔널에 값이 있으면 언래핑하여 사용하고, 그렇지 않으면 전혀 사용하지 않는다. 스위프트가 허용하지 않기 때문에 너는 비어 있을 수도 있는 값을 실수로 사용할 수 없다.

Force unwrapping optionals

스위프트에서 느낌표 : ! 를 사용하여 안전성을 무시할 수 있다. 만약 그 옵셔널이 분명히 값을 가진다는 것을 안다면, 이 느낌표를 뒤에 붙여 강제 언래핑할 수 있다.

그러나 조심해라 : 만약 값을 가지지 않는 변수 뒤에 이것을 사용한다면, 코드는 충돌할 것이다.

여기 몇 가지 기본 코드이다 :


func yearAlbumReleased(name: String) -> Int? {
    if name == “Tayloer Swift” { return 2006 }
    if name == “Fearless” { return 2008 }
    if name == “Speak Now” { return 2010 }
    if name == “Read” { return 2012 }
    if name == “1989” { return 2014 }

    return nil
}

var year = yearAlbumReleased(name: “Red”)

if year == nil {
    print(“There was an error”)
} else { 
    print(“It was released in \(year)”)
}

이것은 앨범이 발매된 년도를 얻는다. 만약 앨범을 찾을 수 없으면 년도는 nil로 설정되고 오류 메시지가 출력된다. 그렇지 않으면, 년도가 출력된다.

또는 음, yearAlbumReleased()는 옵셔널 정수를 반환하고 이 코드는 if let을 사용하여 언래핑하지 않는다. 결과적으로 “It was release in Option(2012)” 를 출력한다 – 이것은 우리가 원했던 것이 아니다!

코드의 이 지점에서 우리는 유효한 값이 있는지 이미 확인했으므로 옵셔널을 안전하게 언래핑하기 위해 또 다른 if let을 사용하는 것은 약간 무의미하다. 따라서, 스위프트는 해결책을 제공한다 – 두 번째 print() 호출을 다음과 같이 변경한다.

print(“It was released in \(year!)”)

느낌표에 주목해라 : 이것은 “나는 이것이 값을 포함하고 있다고 확신하기 때문에 지금 강제 언래핑해라” 라는 의미이다.

Implicitly unwrapped optionals

이 느낌표를 사용하여 일부 사람들이 실제로 혼동하기 시작하는 암시적으로 래핑되지 않은 옵셔널을 만들 수 있다. 그러니, 주의 깊게 읽어보자!

  • 일반 변수는 반드시 값을 포함해야한다. 예를 들어: 문자열은 그 문자열이 비어있더라도 반드시 문자열을 포함해야한다, (예:” “) 이것은 nil일 수 없다.

  • 옵셔널 변수는 값을 포함할 수도 포함하지 않을 수도 있다. 이것은 사용되기 전에 반드시 언래핑 되어야한다. 예를 들어 : String?은 문자열을 포함하거나 nil을 포함할 것이다. 이것을 알아내는 유일한 방법은 언래핑하는 것이다.

  • 암시적으로 래핑되지 않은 옵셔널은 값을 포함하거나 포함하지 않을 수 있다. 그러나 사용되기 전까지 언래핑할 필요는 없다. 스위프트는 너를 위해 확인해 주지 않으니, 네가 각별히 주의해야한다. 예를 들어 : String! 은 문자열을 포함하거나 nil을 포함할 수 있다 – 이것을 적절히 사용하는 것은 너에게 달려 있다. 이것은 일반 옵셔널과 비슷하지만 스위프트는 네가 안전한 언래핑 없이 그 값에 직접 접근할 수 있게 한다. 그렇게 하려 한다면 네가 거기에 값이 있다는 것을 안다는 것을 의미한다 – 하지만 만약 네가 틀렸다면 너의 앱은 충돌한다

출처

0개의 댓글