From 4e71b2a8b861140397d8b1d5873cbc4e2c72282f Mon Sep 17 00:00:00 2001 From: shinmingyu Date: Tue, 27 May 2025 16:20:54 +0900 Subject: [PATCH 1/4] =?UTF-8?q?chore=20:=20Day=204=20=E2=80=93=20Type=20an?= =?UTF-8?q?notations=20and=20checkpoint=202.md=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0\223 Type annotations and checkpoint 2.md" | 18 ++++++++++++++++++ Docs/Echo.md | 3 --- 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 "Docs/Day 4 \342\200\223 Type annotations and checkpoint 2.md" delete mode 100644 Docs/Echo.md diff --git "a/Docs/Day 4 \342\200\223 Type annotations and checkpoint 2.md" "b/Docs/Day 4 \342\200\223 Type annotations and checkpoint 2.md" new file mode 100644 index 0000000..2319734 --- /dev/null +++ "b/Docs/Day 4 \342\200\223 Type annotations and checkpoint 2.md" @@ -0,0 +1,18 @@ +>[!question] +>GQ1. GQ를 쓰세요 +>GQ2. GQ를 쓰세요 + +## Description +- 개요와 설명을 작성 + +## 주요 기능 ++ 실제 활용을 작성 + +## 코드 예시 ++ 실제 코드 예시를 작성 + +## Keywords ++ 파생된 키워드들을 작성 + +## References +- 참고한 레퍼런스를 작성 (예 : Apple의 공식 문서) \ No newline at end of file diff --git a/Docs/Echo.md b/Docs/Echo.md deleted file mode 100644 index 0dcb1e1..0000000 --- a/Docs/Echo.md +++ /dev/null @@ -1,3 +0,0 @@ -안녕하세요 Echo입니다. - -오전 1팀 화이팅! \ No newline at end of file From 13bc796795dc2b477a34545e343422b086bba362 Mon Sep 17 00:00:00 2001 From: shinmingyu Date: Tue, 10 Jun 2025 14:00:35 +0900 Subject: [PATCH 2/4] =?UTF-8?q?#15=20=EA=B3=B5=EB=B6=80=ED=95=9C=EB=82=B4?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\223 Type annotations and checkpoint 2.md" | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git "a/Docs/Day 4 \342\200\223 Type annotations and checkpoint 2.md" "b/Docs/Day 4 \342\200\223 Type annotations and checkpoint 2.md" index 2319734..8904cc0 100644 --- "a/Docs/Day 4 \342\200\223 Type annotations and checkpoint 2.md" +++ "b/Docs/Day 4 \342\200\223 Type annotations and checkpoint 2.md" @@ -1,3 +1,85 @@ +# Swift 복합 데이터 유형 및 타입 어노테이션 + +이 문서는 Swift 학습 과정에서 복합 데이터 유형 (배열, 딕셔너리, 세트, 열거형)과 타입 어노테이션에 대해 배운 내용을 정리합니다. + +## 복합 데이터 유형 요약 + +Swift는 다양한 종류의 데이터를 다루기 위해 복합 데이터 유형을 제공합니다. 단순 데이터 유형을 넘어 여러 값을 함께 묶거나 사용자 정의 유형을 생성할 수 있습니다. + +- **배열 (Arrays)**: 여러 값을 한 곳에 저장하며, 정수 인덱스를 사용하여 값을 읽어옵니다. 배열은 반드시 특정 타입을 포함하도록 전문화(specialized)되어야 합니다 (예: `[String]` 문자열 배열). `count`, `append()`, `contains()`와 같은 유용한 기능이 있습니다. 배열은 딕셔너리, 세트에 비해 **가장 자주 사용되는** 유형입니다. +- **딕셔너리 (Dictionaries)**: 여러 값을 저장하지만, 사용자가 지정하는 키를 사용하여 값을 읽어옵니다. 키와 값 모두 특정 타입을 갖도록 전문화되어야 합니다 (예: `[String: Int]` 문자열 키와 정수 값의 딕셔너리). 배열과 유사하게 `contains()`, `count`와 같은 기능이 있습니다. 배열 다음으로 자주 사용됩니다. +- **세트 (Sets)**: 여러 값을 저장하는 **세 번째 방법**이며, 저장 순서를 사용자가 선택할 수 없습니다. 특정 항목을 포함하는지 확인하는 데 매우 효율적입니다. 세트도 특정 타입을 포함하도록 전문화되어야 합니다 (예: `Set` 문자열 세트). 세트는 배열, 딕셔너리에 비해 사용 빈도가 낮지만, 필요할 때 유용합니다. +- **열거형 (Enums)**: Swift에서 **사용자 정의 간단한 타입**을 생성할 수 있게 해줍니다. 이를 통해 허용 가능한 값의 범위를 지정할 수 있습니다 (예: 요일 목록, UI 테마 종류, 앱의 화면 종류 등). 열거형 값은 해당 열거형 자체와 동일한 타입을 갖습니다. + +## 타입 어노테이션 사용 방법 + +Swift는 변수나 상수에 할당하는 값을 기반으로 데이터 타입을 자동으로 파악합니다. 이를 **타입 추론 (Type Inference)**이라고 합니다. + +``` +let surname = "Lasso" // Swift infers String +var score = 0 // Swift infers Int +``` + +하지만 때로는 값을 즉시 할당하고 싶지 않거나, Swift의 타입 추론 결과를 변경하고 싶을 때 **타입 어노테이션 (Type Annotation)**을 사용합니다. + +타입 어노테이션은 변수/상수 이름 뒤에 콜론(:)을 붙이고 타입을 명시하는 방식입니다. + +``` +let surname: String = "Lasso" // Explicitly a String +var score: Int = 0 // Explicitly an Int +``` + +Swift가 추론하는 타입과 동일하게 명시하는 경우도 있지만, 다른 타입을 지정할 수도 있습니다. 예를 들어, 점수에 소수점이 필요하다면 `Int` 대신 `Double`로 명시할 수 있습니다. + +``` +var score: Double = 0 // Override inference to Double +``` + +타입 어노테이션이 필요한 주요 상황은 세 가지입니다: + +1. **Swift가 타입을 스스로 파악할 수 없는 경우**. 이는 주로 초기 값이 할당되지 않은 상수에 발생합니다. + +``` + let username: String // Must specify String as no initial value + // ... complex logic ... + username = "@twostraws" // Assign value later +``` + + (`username`은 한 번만 값을 할당받아야 상수로 유지됩니다). +2. **Swift의 기본 추론 타입을 변경하고 싶은 경우**. (예: `0`을 `Int` 대신 `Double`로 취급하고 싶을 때 `var score: Double = 0`). +3. **값을 나중에 할당하고 싶은 경우**. 변수나 상수가 존재할 것임을 미리 알리지만 값을 바로 설정하지 않을 때 사용합니다. + +``` +var name: String // Declare exists, assign later +``` + +**빈 컬렉션 (배열, 딕셔너리, 세트) 생성 시**: 초기 값이 없으므로 타입 어노테이션이 필요할 수 있습니다. 빈 컬렉션은 데이터를 미리 모두 알지 못하고 나중에 추가해야 할 때 유용합니다. + +``` +var teams: [String] = [String]() // Empty array of Strings (explicit type annotation) +var clues = [String]() // Empty array of Strings (type inference, but needs type definition on right side) +var cities: [String] = [] // Empty array of Strings (type annotation with empty literal) +``` + +타입 추론과 타입 어노테이션 중 무엇을 사용할지는 **개인의 스타일** 문제입니다. 소스 개발자는 코드를 더 짧고 읽기 쉽게 만들고, 초기 값 변경만으로 타입 변경이 가능하기 때문에 **타입 추론을 선호**한다고 합니다. 하지만 명시적인 타입 어노테이션을 항상 사용하는 것도 괜찮습니다. + +**골든 룰**: Swift는 항상 변수와 상수에 포함된 데이터의 타입을 알고 있어야 합니다. 이것이 Swift가 타입 세이프(Type-Safe) 언어인 핵심이며, `5 + true`와 같은 부적절한 연산을 방지합니다. 타입 어노테이션으로 타입을 지정해도, 실제로 해당 값을 그 타입으로 변환하는 것이 **가능해야** 합니다 (`let score: Int = "Zero"` 와 같은 코드는 허용되지 않습니다). + +## 체크포인트 2 + +이 체크포인트는 배열, 딕셔너리, 세트를 학습한 후 배운 내용을 되짚어보기 위한 작은 코딩 과제입니다. + +**과제**: 문자열 배열을 생성하고, 배열 내 **전체 항목 수**와 **고유 항목 수**를 출력하는 코드를 작성합니다. + +**힌트**: + +1. `let albums = ["Red", "Fearless"]`와 같이 문자열 배열을 생성합니다. +2. 배열의 전체 항목 수는 `albums.count`를 사용하여 얻을 수 있습니다. +3. `count`는 세트(sets)에도 존재합니다. +4. 배열로부터 `Set(someArray)`를 사용하여 세트를 만들 수 있습니다. +5. 세트는 중복 항목을 포함하지 않습니다. + +--- >[!question] >GQ1. GQ를 쓰세요 >GQ2. GQ를 쓰세요 From 6a6ca2d5ea0065505f6acdb2a28aa13c68246139 Mon Sep 17 00:00:00 2001 From: shinmingyu Date: Tue, 24 Jun 2025 14:10:54 +0900 Subject: [PATCH 3/4] =?UTF-8?q?#15=20=ED=95=A8=EC=88=98=20=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\353\260\230\355\231\230 \352\260\222).md" | 215 +++++++++ ...\223 Type annotations and checkpoint 2.md" | 408 +++++++++++++++++- 2 files changed, 601 insertions(+), 22 deletions(-) create mode 100644 "Day 7 \342\200\223 Functions, parameters, and return values(\355\225\250\354\210\230, \353\247\244\352\260\234\353\263\200\354\210\230 \353\260\217 \353\260\230\355\231\230 \352\260\222).md" diff --git "a/Day 7 \342\200\223 Functions, parameters, and return values(\355\225\250\354\210\230, \353\247\244\352\260\234\353\263\200\354\210\230 \353\260\217 \353\260\230\355\231\230 \352\260\222).md" "b/Day 7 \342\200\223 Functions, parameters, and return values(\355\225\250\354\210\230, \353\247\244\352\260\234\353\263\200\354\210\230 \353\260\217 \353\260\230\355\231\230 \352\260\222).md" new file mode 100644 index 0000000..b08ba1f --- /dev/null +++ "b/Day 7 \342\200\223 Functions, parameters, and return values(\355\225\250\354\210\230, \353\247\244\352\260\234\353\263\200\354\210\230 \353\260\217 \353\260\230\355\231\230 \352\260\222).md" @@ -0,0 +1,215 @@ +#### **1. 함수란 무엇이며 왜 사용해야 하는가?** + +- **정의:** 함수는 프로그램의 나머지 부분에서 분리하여 쉽게 참조할 수 있도록 이름을 붙인 코드 조각입니다. 즉, 여러 번 사용하고 싶은 코드 덩어리를 묶어 이름 지은 것입니다. +- **목적 및 장점:** + - **코드 재사용:** 동일한 기능을 여러 곳에서 구현해야 할 때 코드를 복사하여 붙여넣을 필요 없이 함수를 호출하여 재사용할 수 있습니다. + - **코드 중복 감소:** 코드 중복이 줄어들어 더 간결한 코드를 작성할 수 있습니다. + - **유지보수 용이성:** 함수를 변경하면 (예: 더 많은 작업을 추가) 해당 함수를 사용하는 모든 곳에 자동으로 새로운 동작이 적용되므로, 업데이트를 잊어버릴 위험이 없습니다. + - **가독성 및 조직화:** 긴 코드를 작고 관리하기 쉬운 함수로 나누면 전체 내용을 이해하기 더 쉬워집니다. + - **유연성:** 함수에 데이터를 전달하여 동작 방식을 사용자 정의할 수 있으며, 계산된 결과를 반환하여 알려줄 수 있습니다. + - **함수 합성 (Function Composition):** 기존 함수를 기반으로 새로운 함수를 만들 수 있습니다. 작은 함수들을 조합하여 더 큰 함수를 만들 수 있어, 코드를 레고 블록처럼 유연하게 구성할 수 있습니다. + +--- + +#### **2. 함수의 정의 및 호출** + +- **정의:** + - 함수는 `func` 키워드로 시작합니다. + - `func` 뒤에 함수 이름을 지정합니다 (예: `showWelcome`). 함수 이름은 기억하기 쉬운 것이 좋습니다 (예: `printInstructions`, `displayHelp`). + - 함수 이름 뒤에는 괄호 `()`가 붙습니다. 이 괄호는 함수에 `configuration options`(구성 옵션)을 추가하여 함수의 동작을 사용자 정의할 때 사용됩니다. + - 함수의 실제 코드는 중괄호 `{}` 안에 포함됩니다. 이를 함수의 **본문(body)**이라고 합니다. + - **예시:** + + ``` + func showWelcome() { + print("Welcome to my app!") + print("By default This prints out a conversion") + print("chart from centimeters to inches, but you") + print("can also set a custom range if you want.") + } + ``` + + 이 함수는 환영 메시지를 출력하는 네 개의 `print` 문으로 구성됩니다. +- **호출:** + - 함수를 실행하려면 함수 이름을 괄호 `()`와 함께 호출합니다 (예: `showWelcome()`). + - 함수가 호출되는 위치를 **호출 지점(call site)**이라고 합니다. + +--- + +#### **3. 함수 매개변수 (Parameters)** + +- **정의 및 목적:** 매개변수는 함수의 동작을 사용자 정의하기 위해 데이터를 함수에 전달하는 방법입니다. 매개변수는 함수에 유연성을 더합니다. +- **정의 방법:** + - 함수 정의 시 괄호 `()` 안에 `parameterName: Type` 형식으로 매개변수를 지정합니다 (예: `number: Int`). + - 매개변수는 함수 내부에서 상수로 사용될 수 있습니다. +- **호출 시 매개변수 사용:** + - 함수를 호출할 때 매개변수 이름을 명시적으로 작성해야 합니다 (예: `printTimesTables(number: 5)`). 이는 Swift에서 각 매개변수의 기능을 명확하게 보여주는 데 유용합니다. +- **여러 매개변수:** + - 여러 매개변수를 사용할 수 있으며, 이 경우 매개변수 명명이 더욱 중요해집니다. + - **예시:** `printTimesTables(number: Int, end: Int)`는 `number`와 `end`라는 두 개의 정수 매개변수를 받습니다. + - 함수 호출 시 매개변수 순서를 반드시 지켜야 합니다. +- **매개변수 vs. 인수 (Arguments):** + - **매개변수 (Parameters):** 함수 정의 시 사용되는 플레이스홀더 이름입니다 (예: `number`와 `end`는 `printTimesTables` 함수의 매개변수). 함수 내부에서 해당 값에 대한 이름을 갖게 됩니다. + - **인수 (Arguments):** 함수 호출 시 전달되는 실제 값입니다 (예: `printTimesTables(number: 5, end: 20)`에서 `5`와 `20`은 인수). + - 대부분의 개발자 (Paul Hudson 포함)는 이 구분을 무시하고 두 가지 모두에 '매개변수'라는 용어를 사용합니다. +- **매개변수 디버깅 (True/False 사례 참고):** + - `True` 사례는 올바른 매개변수 정의와 호출을 보여줍니다 (예: `count(to: Int)`, `format(number: Int)`). + - `False` 사례는 매개변수 타입 불일치, 호출 시 매개변수 이름 불일치, 또는 매개변수 개수 불일치 등의 오류를 보여줍니다 (예: `walkDogs(to: "the park")` -> `destination: String`인데 `to:`로 호출, `driveRace(laps: 100)` -> `laps: String`인데 `Int` 전달). + +**맞는 내용 (True) 예시:** + +``` +func format(number: Int) { + print("The number is \(number).") +} + +format(number: 32) +``` + +이 코드는 올바른 함수 호출과 매개변수 사용을 보여줍니다. format 함수는 Int 타입의 number라는 매개변수를 예상하며, 호출 시 format(number: 32)와 같이 매개변수 레이블과 함께 적절한 타입의 값(32)이 전달됩니다. Swift는 함수를 정의할 때 나열된 순서대로 값을 전달하도록 강제합니다. + +**틀린 내용 (False) 예시:** + +``` +func walkDogs(destination: String) { + print("Let's go for a walk to \(destination).") +} + +walkDogs(to: "the park") +``` + +이 코드는 함수 정의 시 매개변수 레이블이 destination으로 명시되었음에도 불구하고, 함수 호출 시 to라는 다른 레이블을 사용했기 때문에 False에 해당합니다. Swift에서는 매개변수 이름이 함수 호출 지점에서 매우 중요하며, 정의된 매개변수 이름을 명시적으로 작성해야 합니다. 때로는 매개변수에 외부 이름을 지정하지 않거나(언더스코어 _ 사용) 외부 이름과 내부 이름을 다르게 지정할 수 있지만..., 이 예시의 경우 정의된 외부 이름과 다른 이름으로 호출하여 오류를 발생시킵니다. + +--- + +#### **4. 매개변수 레이블 사용자 지정** + +Swift는 매개변수 레이블을 제어하는 두 가지 주요 방법을 제공하여 코드 가독성을 높입니다. + +- **1. 외부 매개변수 레이블 생략 (`_` 사용):** + - 때로는 매개변수 레이블이 불필요하거나 중복되어 보일 수 있습니다. + - 매개변수 이름 앞에 밑줄 (`_`)을 추가하면 외부 매개변수 레이블을 제거할 수 있습니다. + - **예시:** `hasPrefix("I see")` 함수는 `prefix`라는 매개변수를 받지만, 호출 시에는 `hasPrefix(prefix: "I see")` 대신 `hasPrefix("I see")`로 호출됩니다. 이는 `_`가 외부 이름으로 지정되었기 때문입니다. + - `func isUppercase(string: String)`를 `func isUppercase(_ string: String)`로 변경하면 호출 시 `isUppercase(string: string)` 대신 `isUppercase(string)`로 쓸 수 있습니다.(`string: string`에서 뒤에 있는 `string`은 이미 저장된 변수나 상수이다.) + - `append()`, `contains()`와 같은 Swift의 많은 내장 함수에서 이 방식이 사용됩니다. + - Objective-C와의 상호 운용성을 위해 첫 번째 매개변수 레이블에 밑줄이 있는 함수를 많이 볼 수 있습니다. +- **2. 외부 및 내부 매개변수 이름 분리:** + - 외부 매개변수 이름이 호출 지점에서 자연스럽게 읽히면서도, 함수 내부에서는 다른 이름으로 사용하고 싶을 때 사용합니다. + - `externalName internalName: Type` 형식으로 매개변수를 정의합니다. + - **예시:** `printTimesTables(number: Int)` 대신 `printTimesTables(for number: Int)`로 정의할 수 있습니다. + - 호출 시에는 `printTimesTables(for: 5)`처럼 외부 이름 `for`를 사용합니다. + - 함수 내부에서는 `for` 대신 `number`라는 내부 이름을 사용하여 코드를 작성합니다. + - `for`와 같은 키워드는 외부 이름으로는 허용되지만, 함수 내부에서는 키워드로 사용될 수 없기 때문에 이 방식이 유용합니다. +- **매개변수 레이블의 중요성:** + - 코드를 6개월 후에 다시 볼 때도 `rollDice(sides: 6, count: 4)`처럼 무엇을 하는지 명확하게 이해할 수 있도록 돕습니다. + - Swift에서는 동일한 함수 이름을 가진 여러 함수를 매개변수 이름으로 구분합니다 (함수 오버로드, 예: `hireEmployee(name: String)`, `hireEmployee(title: String)`, `hireEmployee(location: String)`). 다른 언어에서는 흔하지 않지만 Swift에서는 매우 일반적인 방식입니다. + +--- + +#### **5. 함수에서 값 반환하기** + +함수는 내부적으로 계산을 수행한 다음 그 결과를 호출 지점으로 다시 전달할 수 있습니다. + +- **단일 값 반환:** + - 함수 정의 시 반환할 데이터의 **타입**을 명시해야 합니다. 함수 이름 뒤에 `->`와 반환 타입을 작성합니다 (예: `func rollDice() -> Int`). + - 함수 본문 내에서 `return` 키워드를 사용하여 실제 데이터를 반환합니다 (예: `return Int.random(in: 1...6)`). + - Swift는 함수가 약속한 타입의 값을 항상 반환하도록 엄격하게 강제합니다. + - **예시:** `sqrt(169)`는 `169`의 제곱근인 `13.0`을 반환합니다. (sqrt는 기본 제공 함수)`areLettersIdentical(string1: String, string2: String) -> Bool` 함수는 두 문자열을 비교하여 `Bool` 값을 반환합니다. +- **`return` 키워드 생략 조건:** + - 함수 본문에 **단일 표현식(single expression)**만 포함된 경우 `return` 키워드를 생략할 수 있습니다. + - **표현식 (Expression):** `5 + 8`이나 `greet("Paul")`처럼 단일 값으로 변환되는 코드 줄. `print()` 호출처럼 값을 반환하지 않는 것은 표현식이 아닙니다. + - **명령문 (Statement):** 변수를 생성하거나, 루프를 시작하거나, 조건을 확인하는 등의 작업을 수행하는 것. `let name = "Otis"`는 명령문입니다. + - `if` 또는 `switch` 문도 각 분기가 단일 표현식인 경우 `return`을 생략할 수 있습니다. 그러나 새로운 상수를 생성한 후 반환하는 것은 허용되지 않습니다. + - **예시:** + + ``` + func doMoreMath() -> Int { + 5 + 5 // 'return' 키워드 생략 가능 + } + func areLettersIdentical(string1: String, string2: String) -> Bool { + string1.sorted() == string2.sorted() // 'return' 키워드 생략 가능 + } + func pythagoras(a: Double, b: Double) -> Double { + sqrt(a * a + b * b) // 'return' 키워드 생략 가능 + } + ``` + +- **함수 즉시 종료 (`return` 단독 사용):** + - 함수가 값을 반환하지 않더라도 `return` 키워드를 단독으로 사용하여 함수 실행을 즉시 중단할 수 있습니다. 특정 조건에서 함수를 더 이상 진행하지 않도록 할 때 유용합니다. +``` +func processUserData(username: String, age: Int) { + if username.isEmpty { + print("오류: 사용자 이름이 비어 있습니다. 처리를 중단합니다.") + return // 함수 즉시 종료 [1, 8] + } + + if age < 0 { + print("오류: 나이가 유효하지 않습니다. 처리를 중단합니다.") + return // 함수 즉시 종료 [1, 8] + } + + print("\(username)님, 환영합니다! 나이: \(age)") + // 추가적인 사용자 데이터 처리 로직... +} + +processUserData(username: "", age: 25) // 사용자 이름이 비어 있으므로 즉시 종료 +processUserData(username: "철수", age: -5) // 나이가 유효하지 않아 즉시 종료 +processUserData(username: "영희", age: 30) // 정상적으로 실행 +``` + +--- + +#### **6. 함수에서 여러 값 반환하기: 튜플 (Tuples) 활용** + +함수에서 두 개 이상의 값을 반환해야 할 때 튜플은 배열이나 딕셔너리보다 더 나은 해결책을 제공합니다. + +- **배열 (`Array`)의 한계:** + - `getUser()` 함수가 `["Taylor", "Swift"]`와 같은 배열을 반환할 때, `user`와 `user`가 각각 무엇을 의미하는지 기억하기 어렵습니다. + - 배열의 데이터가 조정되면 인덱스가 가리키는 값이 바뀌거나 아예 존재하지 않을 수도 있습니다. +- **딕셔너리 (`Dictionary`)의 한계:** + - `["firstName": "Taylor", "lastName": "Swift"]`와 같은 딕셔너리를 반환하면 값에 의미 있는 이름을 붙일 수 있습니다. + - 하지만 Swift는 딕셔너리의 특정 키가 존재하는지 컴파일 시점에 알 수 없으므로, 값을 읽을 때 기본값 (예: `default: "Anonymous"`)을 제공해야 하는 번거로움이 있습니다. + - 딕셔너리는 임의의 수많은 다른 값을 포함할 수 있어, 요청한 값이 반드시 존재한다고 보장할 수 없습니다. +- **튜플 (`Tuple`)의 장점:** + - 튜플은 여러 값을 하나의 변수에 담을 수 있습니다. + - 다른 데이터 구조와 달리 **크기가 고정되어 있고** (예: 항상 2개의 값) **다양한 데이터 타입을** (예: `String`과 `Int` 조합) 쉽게 가질 수 있습니다. + - **정의 및 사용:** + - 반환 타입으로 튜플을 정의할 때 각 값에 이름 (레이블)을 부여할 수 있습니다 (예: `(firstName: String, lastName: String)`). + - 튜플의 각 항목은 고유한 이름을 가지며, 이는 딕셔너리의 임의 키와 다릅니다. + - Swift는 튜플의 특정 값이 존재하고 어떤 타입인지 컴파일 시점에 보장합니다. 따라서 `user.firstName`처럼 점 구문으로 접근하며, 오타의 가능성이 없습니다. + - **예시:** + +``` +func getUser() -> (firstName: String, lastName: String) { + (firstName: "Taylor", lastName: "Swift") +} +let user = getUser() +print("Name: \(user.firstName) \(user.lastName)") // 'user.firstName'으로 접근 +``` + + - **반환 시 튜플 요소 이름 생략:** + - 함수에서 튜플을 반환할 때, 반환 타입에 이미 이름이 지정되어 있으므로 `return` 문에서 튜플 요소의 이름을 반복할 필요가 없습니다 (예: `("Taylor", "Swift")`만으로도 `firstName`과 `lastName`에 매칭됨). + - **이름 없는 튜플:** + - 튜플 요소에 이름이 없는 경우, `user.0`, `user.1`과 같이 0부터 시작하는 숫자 인덱스를 사용하여 접근할 수 있습니다.ㅗ + - 명명된 튜플에도 숫자 인덱스 접근이 가능하지만, 이름을 사용하는 것이 더 명확합니다. + - **튜플 디스트럭처링 (Destructuring):** + - 함수가 반환한 튜플을 개별 상수로 바로 분리하여 사용할 수 있습니다. + - **예시:** `let (firstName, lastName) = getUser()`는 `getUser()`가 반환한 튜플의 `firstName`과 `lastName`을 각각의 상수에 할당합니다. + - 필요 없는 튜플 요소는 밑줄 `_`를 사용하여 무시할 수 있습니다 (예: `let (firstName, _) = getUser()`). +- **튜플 사용 시기:** + - 정확히 두 개의 문자열, 또는 정확히 두 개의 문자열과 정수, 또는 정확히 세 개의 부울 값 등 고정된 개수와 유형의 값을 보관해야 할 때 사용합니다. + - 할 일 목록처럼 순서가 예측 가능할 때 효과적입니다. + +--- + +#### **7. 함수 디버깅 및 코드 품질** + +- **함수 작성, 매개변수, 반환 값 디버깅:** + - 제공된 `True/False` 예시들을 통해 함수 정의, 매개변수 전달, 반환 값 처리 시 발생할 수 있는 일반적인 오류 유형을 익힐 수 있습니다. `False` 예시들은 문법 오류, 타입 불일치, 매개변수 누락, 키워드 오타 등을 포함합니다. +- **함수 매개변수 개수 (코드 냄새 `Code Smell`):** + - 함수가 너무 많은 매개변수 (주관적이지만 6개 이상)를 받는 경우, 이는 해당 함수가 너무 많은 작업을 수행하고 있다는 "코드 냄새"일 수 있습니다. + - 이런 경우 함수를 더 작고 구체적인 함수로 분할하는 것을 고려해야 합니다. 이는 코드 구성 방식에 근본적인 문제가 있음을 암시할 수 있습니다. + +--- + +이 가이드를 통해 Swift 함수의 기본부터 고급 개념까지 체계적으로 학습하실 수 있습니다. 각 섹션의 예시와 설명은 실제 코딩에 적용하는 데 큰 도움이 될 것입니다. \ No newline at end of file diff --git "a/Docs/Day 4 \342\200\223 Type annotations and checkpoint 2.md" "b/Docs/Day 4 \342\200\223 Type annotations and checkpoint 2.md" index 8904cc0..11e7c65 100644 --- "a/Docs/Day 4 \342\200\223 Type annotations and checkpoint 2.md" +++ "b/Docs/Day 4 \342\200\223 Type annotations and checkpoint 2.md" @@ -64,37 +64,401 @@ var cities: [String] = [] // Empty array of Strings (type annotation with 타입 추론과 타입 어노테이션 중 무엇을 사용할지는 **개인의 스타일** 문제입니다. 소스 개발자는 코드를 더 짧고 읽기 쉽게 만들고, 초기 값 변경만으로 타입 변경이 가능하기 때문에 **타입 추론을 선호**한다고 합니다. 하지만 명시적인 타입 어노테이션을 항상 사용하는 것도 괜찮습니다. **골든 룰**: Swift는 항상 변수와 상수에 포함된 데이터의 타입을 알고 있어야 합니다. 이것이 Swift가 타입 세이프(Type-Safe) 언어인 핵심이며, `5 + true`와 같은 부적절한 연산을 방지합니다. 타입 어노테이션으로 타입을 지정해도, 실제로 해당 값을 그 타입으로 변환하는 것이 **가능해야** 합니다 (`let score: Int = "Zero"` 와 같은 코드는 허용되지 않습니다). + -## 체크포인트 2 +### **GQ** ++ 1. 왜 애플은 타입 추론을 권장할까?(자랑일까?) ++ 2. Swift에서 타입을 추론할 때 어떤 것을 상위로 사용을 할까? ++ 3. 옵셔널은 무엇인가? ++ 4. Swift에서 사용하는 코어 데이터와 스위프트 데이터에 대해서 알아보고, 여기서 옵셔널을 사용하는 이유는 무엇인가? -이 체크포인트는 배열, 딕셔너리, 세트를 학습한 후 배운 내용을 되짚어보기 위한 작은 코딩 과제입니다. +## 1️⃣ 왜 애플은 타입 추론을 권장할까? (자랑일까?) -**과제**: 문자열 배열을 생성하고, 배열 내 **전체 항목 수**와 **고유 항목 수**를 출력하는 코드를 작성합니다. +### ✅ 원인과 이유 -**힌트**: +- **가독성 향상** + Swift는 *간결하고 읽기 쉬운 코드*를 지향함. + → 타입을 명시하지 않고도 변수/상수 선언 가능 → 코드 간결 -1. `let albums = ["Red", "Fearless"]`와 같이 문자열 배열을 생성합니다. -2. 배열의 전체 항목 수는 `albums.count`를 사용하여 얻을 수 있습니다. -3. `count`는 세트(sets)에도 존재합니다. -4. 배열로부터 `Set(someArray)`를 사용하여 세트를 만들 수 있습니다. -5. 세트는 중복 항목을 포함하지 않습니다. +- **컴파일 타임 안전성** (컴파일 : 코드를 기계어로 바꾸는 작업) + 타입은 **컴파일 시점에 정확하게 추론**됨 → 런타임 타입 오류 방지 + → Swift는 "타입 안전성(type-safety)"을 매우 중요하게 생각함 + + +- **현대적 언어 철학** (가독성, 타입 안전성, 생산성, 유지보수성) + Swift는 *Modern programming language*로서 **타입 추론**을 핵심 기능으로 제공 + → WWDC에서는 Swift를 Fast(빠름), Modern(현대적임), Safe(안전함), Interactive(빠르게 실험 가능)라 말함. + +### ✅ Swift 코드 예시 + +```swift +let message = "Hello, Echo!" // String으로 추론 +let count = 42 // Int로 추론 +let ratio = 3.14 // Double로 추론 +```` + +### ✅ 출처 + +[Swift Language Guide - The Basics](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/#Type-Annotations) +_"Swift uses type inference to work out the appropriate type."_ + +--- + +## 2️⃣ Swift에서 타입을 추론할 때 어떤 것을 상위로 사용할까? + +### ✅ 원인과 이유 + +- **가장 구체적인 타입(specific type)** 으로 추론하려고 함. + +- 여러 타입의 상위 타입이 필요한 경우 → **공통 상위(super) 타입 또는 프로토콜 타입**으로 추론 + + +#### 우선순위 + +1️⃣ 리터럴 → 기본 타입 사용 (Int, Double, String 등) +2️⃣ 컬렉션 → 가장 구체적인 타입 사용 +3️⃣ 다형성 상황 → 상위 클래스 또는 프로토콜 타입 사용 + +### ✅ Swift 코드 예시 + +```swift +// 1️⃣ 리터럴 → 기본 타입 +let integer = 100 // Int +let pi = 3.1415 // Double + +// 2️⃣ 컬렉션 → 구체적 타입 +let fruits = ["Apple", "Banana", "Cherry"] // [String] +``` + +### ✅ 리터럴 종류 + +| 리터럴 종류 | 타입 후보들 | Swift 기본 추론 타입 | 왜 그렇게 되는가 | +| ----------------------------- | ----------------------------------- | --------------------------------------------------- | --------------------------------------- | +| 정수 리터럴 (e.g. `42`) | Int, UInt, Int8~Int64 | **Int** | 플랫폼 최적화 + 대부분 Int 사용 | +| 소수 리터럴 (e.g. `3.14`) | Double, Float, CGFloat | **Double** | 더 높은 정밀도, 안전성 우선 | +| 문자열 리터럴 (e.g. `"Echo"`) | String, NSString | **String** | Swift의 기본 문자열 타입 | +| 문자 리터럴 (e.g. `"E"`) | String, Character | **String** ← 문자열로 인식 (따옴표 사용 시) / `'E'` → Character | Swift에서 `" "`는 String, `' '`는 Character | +| Boolean 리터럴 (`true`, `false`) | Bool | **Bool** | 불리언은 Bool로 고정 | +| 배열 리터럴 (`[1, 2, 3]`) | [Int], [Double], [Any] 등 | **가장 구체적 타입 사용** | 배열 요소의 타입을 보고 추론 | +| 딕셔너리 리터럴 (`["a": 1, "b": 2]`) | [String: Int], [AnyHashable: Any] 등 | **가장 구체적 타입 사용** | 키, 값의 타입을 보고 추론 | + +### ✅ 출처 + +[Swift Language Guide - Type Inference](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/#Type-Inference) +_"Swift always chooses the smallest possible type that can hold the literal value."_ + +--- + +# Any vs AnyObject vs AnyHashable 차이 + +## 🏷️ Any + +### ✅ 의미 +- **모든 타입을 담을 수 있음** → Swift의 *가장 상위 타입* + +### ✅ 설명 +- Value type(값 타입) + Reference type(참조 타입) 전부 담을 수 있음 +- struct, class, enum, Int, String, Double, UIView 등 모두 가능 + +### ✅ 예시 + +```swift +let anyValue1: Any = 42 // Int +let anyValue2: Any = "Echo" // String +let anyValue3: Any = [1, 2, 3] // Array +```` + +### ✅ 특징 + +- **정말 모든 것**을 담을 수 있음 + +- 하지만 꺼낼 때는 **타입캐스팅 필요** (as?, as!) + +# Any에서 타입캐스팅 (as?, as!) 사용법 + +--- + +## 📌 기본 개념 + +- **Any는 모든 타입을 담을 수 있지만 → 꺼낼 때는 반드시 원래 타입으로 변환(캐스팅) 해야 사용 가능하다.** +- 그렇지 않으면 Swift는 타입이 뭔지 몰라서 연산도 못 하고 접근도 못 한다. + +## 타입캐스팅 방법 + +| 키워드 | 설명 | +|--------|------| +| `as?` | **Optional 타입으로 안전하게 캐스팅** → 실패 시 nil 반환 | +| `as!` | **강제 캐스팅** → 실패 시 런타임 에러 발생 | + +## 📌 예시 1️⃣ `as?` (안전한 캐스팅) + +```swift +let anyValue: Any = "Echo" + +// 안전한 캐스팅 → Optional(String) 반환 +if let stringValue = anyValue as? String { + print("String value:", stringValue) +} else { + print("Casting failed") +} +```` + +### 출력 결과 + +``` +String value: Echo +``` + +### 설명 + +- `as? String` → 성공하면 `stringValue`에 String이 들어감. + +- 실패하면 `nil` → else 블록 실행됨 → **안전하게 처리 가능**. + + +## 📌 예시 2️⃣ `as!` (강제 캐스팅) + +```swift +let anyValue: Any = "Echo" + +// 강제 캐스팅 → 실패 시 런타임 에러 발생 +let stringValue = anyValue as! String +print("String value:", stringValue) +``` + +### 출력 결과 + +``` +String value: Echo +``` + +### 만약 잘못된 캐스팅을 하면? + +```swift +let anyValue: Any = 42 +let stringValue = anyValue as! String // 런타임 크래시 발생 ❌ +``` + +### 에러 + +``` +Could not cast value of type 'Int' to 'String' +``` + +## 정리 + +|방법|사용 상황|안전성| +|---|---|---| +|`as?`|안전하게 시도 (옵셔널 반환)|✅ 안전| +|`as!`|타입이 확실할 때 강제로 캐스팅|⚠️ 위험 (잘못 쓰면 런타임 크래시 발생)| + +--- + +## 🏷️ AnyObject + +### ✅ 의미 + +- **모든 클래스 타입의 인스턴스**만 담을 수 있음 → Reference type 전용 + + +### ✅ 설명 + +- 클래스는 Reference type → 그 인스턴스만 담을 수 있음 + +- struct, enum 같은 Value type은 담을 수 없음 + + +### ✅ 예시 + +```swift +class MyClass {} +struct MyStruct {} + +let objectValue: AnyObject = MyClass() // 가능 +// let objectValue2: AnyObject = MyStruct() // 오류 발생! Value type은 안됨 +``` + +### ✅ 특징 + +- **클래스 타입 인스턴스만 허용** + +- 주로 Objective-C API와 연동할 때 많이 사용됨 (`NSObject` 기반 API들) + --- ->[!question] ->GQ1. GQ를 쓰세요 ->GQ2. GQ를 쓰세요 -## Description -- 개요와 설명을 작성 +## 🏷️ AnyHashable -## 주요 기능 -+ 실제 활용을 작성 +### ✅ 의미 + +- **Hashable 프로토콜을 따르는 값만 담을 수 있는 래퍼 타입** + + +### ✅ 설명 + +- Swift에서 Set의 요소, Dictionary의 Key는 반드시 Hashable 이어야 함 + +- AnyHashable은 서로 다른 타입의 Hashable 값을 하나로 담아서 처리할 때 사용 + + +### ✅ 예시 + +```swift +let hashableValue1: AnyHashable = 42 // Int는 Hashable +let hashableValue2: AnyHashable = "Echo" // String은 Hashable +let hashableValue3: AnyHashable = true // Bool도 Hashable + +let hashableSet: Set = [42, "Echo", true] +``` + +### ✅ 특징 + +- 오직 **Hashable 프로토콜을 따르는 값만 허용** + +- 내부적으로 HashValue 를 기반으로 구분 가능 + +- Dictionary의 Key에 사용 가능 + + +--- + +## 🎁 총정리 테이블 + +|타입|담을 수 있는 값|주요 용도|비고| +|---|---|---|---| +|**Any**|모든 타입 (Value + Reference)|타입이 다양한 값 다루기|가장 범용적| +|**AnyObject**|클래스 인스턴스만 (Reference type만)|Objective-C API 연동, 클래스 타입 전용|Value type 안 됨| +|**AnyHashable**|Hashable 프로토콜 채택 타입|Set 요소, Dictionary Key 등 Hash 기반 자료구조에 사용|Hashable 필수| + +--- + +## 💡 언제 쓰나? + +- **Any** → 정말 타입을 모를 때 (최후의 수단으로 사용 권장) + +- **AnyObject** → 클래스만 받을 때 (ex. UIKit API에서 delegate 배열 등에 사용) + +- **AnyHashable** → Dictionary Key에 여러 타입 섞어서 쓰고 싶을 때 + + +--- + +## 📚 공식 문서 출처 + +- [Swift Language Guide - Any, AnyObject](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/#Any-and-AnyObject) + +- [AnyHashable Documentation](https://developer.apple.com/documentation/swift/anyhashable) + + +> _"Any can represent an instance of any type at all, including function types and optional types."_ +> _"AnyObject can represent an instance of any class type."_ +> _"AnyHashable is a type-erased hashable value."_ + +--- -## 코드 예시 -+ 실제 코드 예시를 작성 +## ✨ 결론 + +- **Any** → 모든 것 (Value + Reference) + +- **AnyObject** → Reference type (class) 전용 + +- **AnyHashable** → Hashable만 가능 → Set, Dictionary Key 등에서 사용 + + +--- + +## 3️⃣ 옵셔널(Optional)은 무엇인가? + +### ✅ 원인과 이유 + +- **nil(없음)** 을 안전하게 표현하기 위한 타입 + +- Swift는 null pointer dereference 오류를 방지하기 위해 → 옵셔널이라는 타입 제공 + +- 옵셔널은 "값이 있을 수도 있고 없을 수도 있음"을 **명시적**으로 표현하도록 강제 + → Swift의 주요한 **안정성(safety)** 기능 중 하나 + + +### ✅ Swift 코드 예시 + +```swift +var name: String? = "Echo" // 옵셔널 String → 값이 있을 수도, 없을 수도 있음 + +// 옵셔널 바인딩 +if let unwrappedName = name { + print("Hello, \(unwrappedName)") +} else { + print("No name provided") +} + +// nil 할당 가능 +name = nil +``` + +### ✅ 출처 + +[Swift Language Guide - Optionals](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/#Optionals) +_"You use optionals in situations where a value may be absent."_ + +--- + +## 4️⃣ Core Data 와 SwiftData → 옵셔널 사용하는 이유는? + +### ✅ Core Data vs SwiftData + +|Core Data|SwiftData| +|---|---| +|오래된 ORM (Objective-C 기반 설계)|Swift 전용 최신 ORM (2023 WWDC에서 소개됨)| +|`NSManagedObject` 기반|`@Model` 속성 기반, 코드가 더 간결| +|코드가 복잡|코드가 깔끔하고 Swift 친화적| + +### ✅ 옵셔널 사용하는 이유 + +- **DB 설계에서 null(없음) 가능성 존재** + → Swift에서 이를 정확하게 표현하기 위해 옵셔널 사용 + +- **모든 값이 필수는 아님** + → 사용자 입력값, 서버 데이터 등에서 nil 가능성이 높음 + → 옵셔널로 안전하게 표현 → 앱 크래시 방지 + + +### ✅ SwiftData 코드 예시 + +```swift +import SwiftData + +@Model +class User { + var name: String + var email: String? // 옵셔널 사용 → 이메일 없는 사용자도 허용 + var age: Int + + init(name: String, email: String?, age: Int) { + self.name = name + self.email = email + self.age = age + } +} + +let user1 = User(name: "Echo", email: "echo@example.com", age: 30) +let user2 = User(name: "NoEmailUser", email: nil, age: 25) // email 옵셔널 활용 +``` + +### ✅ 출처 + +- [SwiftData Documentation](https://developer.apple.com/documentation/swiftdata/) + +- [Core Data Programming Guide - Optional Properties](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/Articles/cdDesignTechniques.html#//apple_ref/doc/uid/TP40004399-SW7) + _"Optional attributes map to optional properties in your NSManagedObject subclasses."_ + + +--- -## Keywords -+ 파생된 키워드들을 작성 +# 📚 총 정리 -## References -- 참고한 레퍼런스를 작성 (예 : Apple의 공식 문서) \ No newline at end of file +|항목|핵심 포인트|공식문서 출처| +|---|---|---| +|타입 추론 권장 이유|가독성 + 타입 안전성 유지|[Swift Language Guide - The Basics](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/)| +|타입 추론 시 상위 타입 사용|기본적으로 가장 구체적 타입 사용, 필요시 상위 타입 사용|[Swift Language Guide - Type Inference](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/#Type-Inference)| +|옵셔널이란?|nil 안정성 확보 (값이 있을 수도, 없을 수도 있음)|[Swift Language Guide - Optionals](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/#Optionals)| +|Core Data / SwiftData에서 옵셔널 사용하는 이유|DB에서 nullable 필드 표현|[SwiftData Docs](https://developer.apple.com/documentation/swiftdata/), [Core Data Programming Guide](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/Articles/cdDesignTechniques.html#//apple_ref/doc/uid/TP40004399-SW7)| From 208a29424b01b8f4a38cde7d42182a9d98f5af26 Mon Sep 17 00:00:00 2001 From: shinmingyu Date: Mon, 30 Jun 2025 16:47:35 +0900 Subject: [PATCH 4/4] =?UTF-8?q?#15=208=EC=9D=BC=EC=B0=A8-=ED=95=A8?= =?UTF-8?q?=EC=88=982=20=EB=82=B4=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\353\260\230\355\231\230 \352\260\222).md" | 6 +- .../Day 8 \342\200\223 Functions, part 2.md" | 243 ++++++++++++++++++ 2 files changed, 246 insertions(+), 3 deletions(-) rename "Day 7 \342\200\223 Functions, parameters, and return values(\355\225\250\354\210\230, \353\247\244\352\260\234\353\263\200\354\210\230 \353\260\217 \353\260\230\355\231\230 \352\260\222).md" => "Docs/Day 7 \342\200\223 Functions, parameters, and return values(\355\225\250\354\210\230, \353\247\244\352\260\234\353\263\200\354\210\230 \353\260\217 \353\260\230\355\231\230 \352\260\222).md" (98%) create mode 100644 "Docs/Day 8 \342\200\223 Functions, part 2.md" diff --git "a/Day 7 \342\200\223 Functions, parameters, and return values(\355\225\250\354\210\230, \353\247\244\352\260\234\353\263\200\354\210\230 \353\260\217 \353\260\230\355\231\230 \352\260\222).md" "b/Docs/Day 7 \342\200\223 Functions, parameters, and return values(\355\225\250\354\210\230, \353\247\244\352\260\234\353\263\200\354\210\230 \353\260\217 \353\260\230\355\231\230 \352\260\222).md" similarity index 98% rename from "Day 7 \342\200\223 Functions, parameters, and return values(\355\225\250\354\210\230, \353\247\244\352\260\234\353\263\200\354\210\230 \353\260\217 \353\260\230\355\231\230 \352\260\222).md" rename to "Docs/Day 7 \342\200\223 Functions, parameters, and return values(\355\225\250\354\210\230, \353\247\244\352\260\234\353\263\200\354\210\230 \353\260\217 \353\260\230\355\231\230 \352\260\222).md" index b08ba1f..ba58f74 100644 --- "a/Day 7 \342\200\223 Functions, parameters, and return values(\355\225\250\354\210\230, \353\247\244\352\260\234\353\263\200\354\210\230 \353\260\217 \353\260\230\355\231\230 \352\260\222).md" +++ "b/Docs/Day 7 \342\200\223 Functions, parameters, and return values(\355\225\250\354\210\230, \353\247\244\352\260\234\353\263\200\354\210\230 \353\260\217 \353\260\230\355\231\230 \352\260\222).md" @@ -71,7 +71,7 @@ format(number: 32) **틀린 내용 (False) 예시:** ``` -func walkDogs(destination: String) { +func walkDogs(to destination: String) { print("Let's go for a walk to \(destination).") } @@ -96,7 +96,7 @@ Swift는 매개변수 레이블을 제어하는 두 가지 주요 방법을 제 - **2. 외부 및 내부 매개변수 이름 분리:** - 외부 매개변수 이름이 호출 지점에서 자연스럽게 읽히면서도, 함수 내부에서는 다른 이름으로 사용하고 싶을 때 사용합니다. - `externalName internalName: Type` 형식으로 매개변수를 정의합니다. - - **예시:** `printTimesTables(number: Int)` 대신 `printTimesTables(for number: Int)`로 정의할 수 있습니다. + - **예시:** `printTimesTables(number: Int)` 대신 `func rintTimesTables(for number: Int)`로 정의할 수 있습니다. - 호출 시에는 `printTimesTables(for: 5)`처럼 외부 이름 `for`를 사용합니다. - 함수 내부에서는 `for` 대신 `number`라는 내부 이름을 사용하여 코드를 작성합니다. - `for`와 같은 키워드는 외부 이름으로는 허용되지만, 함수 내부에서는 키워드로 사용될 수 없기 때문에 이 방식이 유용합니다. @@ -140,7 +140,7 @@ Swift는 매개변수 레이블을 제어하는 두 가지 주요 방법을 제 func processUserData(username: String, age: Int) { if username.isEmpty { print("오류: 사용자 이름이 비어 있습니다. 처리를 중단합니다.") - return // 함수 즉시 종료 [1, 8] + return } if age < 0 { diff --git "a/Docs/Day 8 \342\200\223 Functions, part 2.md" "b/Docs/Day 8 \342\200\223 Functions, part 2.md" new file mode 100644 index 0000000..161a00f --- /dev/null +++ "b/Docs/Day 8 \342\200\223 Functions, part 2.md" @@ -0,0 +1,243 @@ + +# Swift 함수 학습 가이드 + +## 1. 함수 개요 (Functions Overview) + +- **함수의 정의 및 목적**: + - 함수는 코드를 **재사용**할 수 있도록 프로그램의 특정 코드 덩어리를 분리하고 이름을 부여하는 방식으로 사용됩니다. + - 이를 통해 동일한 코드를 계속해서 반복적으로 사용할 수 있게 됩니다. +- **함수 작성 기본 구조**: + - 모든 함수는 `func` **키워드**로 시작하며, 그 뒤에 함수의 이름이 옵니다. + - 함수의 본문은 **중괄호 `{}`** 안에 위치합니다. 이는 조건문(`if`)이나 반복문(`for`, `while`)과 유사한 구조입니다. +``` +// 아무 매개변수 없이 실행되며, 단순히 메시지를 출력하는 함수 +func sayHello() { + print("안녕하세요! 함수의 기본 구조입니다.") +} + +// 함수 호출 +sayHello() +// 출력: 안녕하세요! 함수의 기본 구조입니다. +``` +- **함수 매개변수 (Parameters)**: + - 함수는 동작을 제어하기 위해 **매개변수**를 받을 수 있습니다. + - 매개변수의 개수는 0개, 1개 또는 그 이상일 수 있습니다 (하지만 50개는 권장되지 않습니다). + - 매개변수는 **이름**, **콜론(:)**, 그리고 **타입** 순서로 하나씩 나열하며, 여러 개일 경우 **쉼표(,)**로 구분합니다. + - **커스텀 외부 매개변수 이름**을 사용하여 코드를 더 읽기 쉽게 만들거나, **밑줄(`_`)**을 사용하여 특정 매개변수의 외부 이름을 비활성화할 수도 있습니다. +``` +//예제1 : 매개변수 +// 이름을 받아서 인사하는 함수 +func greet(name: String) { + print("안녕하세요, \(name)님!") +} + +greet(name: "에코") +// 출력: 안녕하세요, 에코님! + + +//예제2 : 매개변수 여러 개(쉼표로 구분) +// 두 숫자를 받아서 덧셈 결과를 출력하는 함수 +func addNumbers(a: Int, b: Int) { + let result = a + b + print("결과: \(result)") +} + +addNumbers(a: 3, b: 5) +// 출력: 결과: 8 + + +//예제3 : 외부 매개변수 커스텀 + 외부 이름 생략 +// 외부 매개변수 이름 커스터마이징 +func sendMessage(to recipient: String, message: String) { + print("\(recipient)에게 보낸 메시지: \(message)") +} + +sendMessage(to: "에코", message: "안녕하세요~") +// 출력: 에코에게 보낸 메시지: 안녕하세요~ + +// 외부 매개변수 이름 생략하기 +func multiply(_ a: Int, _ b: Int) -> Int { + return a * b +} + +let result = multiply(4, 6) +print("곱셈 결과: \(result)") +// 출력: 곱셈 결과: 24 +``` +- **함수 반환 값 (Return Values)**: + - 함수는 필요에 따라 **값을 반환**할 수 있습니다. + - 함수에서 **여러 개의 데이터를 반환**해야 할 경우, **튜플(tuple)**을 사용하는 것이 가장 좋은 방법입니다. + - 튜플은 딕셔너리나 배열과 유사하지만, 여러 개의 **명명된 요소(named elements)**를 가지며, 그 **크기와 타입, 이름이 고정**되어 있다는 점에서 차이가 있습니다. +``` +//예제1 : 하나의 값만 반환하는 함수 +// 나이를 받아서 성인인지 판별하는 함수 +func isAdult(age: Int) -> Bool { + return age >= 20 +} + +// 함수 호출 +let result = isAdult(age: 22) +print("성인인가요? \(result ? "네" : "아니요")") +// 출력: 성인인가요? 네 + + +//예제2 : 여러 개의 값을 튜플로 반환하는 함수 +// 이름과 나이를 받아서 튜플 형태로 반환하는 함수 +func getUserInfo() -> (name: String, age: Int) { + let name = "에코" + let age = 30 + return (name, age) +} + +// 함수 호출 +let user = getUserInfo() +print("이름: \(user.name), 나이: \(user.age)") +// 출력: 이름: 에코, 나이: 30 + + +//예제3 : 튜플 반환 + 조건 분기 사용 +// 숫자를 나눌 때 0으로 나누는 경우를 처리하는 함수 +func divide(_ a: Int, by b: Int) -> (result: Int?, error: String?) { + if b == 0 { + return (nil, "0으로 나눌 수 없습니다") + } else { + return (a / b, nil) + } +} + +let outcome = divide(10, by: 0) +if let value = outcome.result { + print("나눈 결과: \(value)") +} else { + print("에러: \(outcome.error!)") +} +// 출력: 에러: 0으로 나눌 수 없습니다 +``` +## 2. 매개변수 기본값 설정 (Providing Default Values for Parameters) + +- **기본값 매개변수의 필요성**: + - 함수에 매개변수를 추가하면 **커스터마이징 지점**을 만들어 필요에 따라 다른 데이터를 전달할 수 있게 됩니다. + - 코드를 유연하게 유지하기 위해 이러한 커스터마이징 지점을 추가하는 것이 유용하지만, 때로는 대부분의 경우 **동일한 동작**을 원할 때가 많습니다. + - **기본 매개변수**를 사용하면 함수를 호출하기 쉽게 만들어서, **공통적인 기본값**을 제공할 수 있습니다. + +- **기본값 설정 방법**: + - Swift는 함수 매개변수 중 하나 또는 **모두**에 기본값을 제공할 수 있도록 지원합니다. + - 기본값을 설정하려면 매개변수 이름과 타입 정의 뒤에 **공백**을 두고 **등호(`=`)**와 **기본값**을 지정합니다. 예를 들어, `end: Int = 12` 와 같이 사용합니다. +- **실제 예시**: + - `printTimesTables(for number: Int, end: Int = 12)` 함수에서 `end` 매개변수에 기본값 `12`를 설정하면, `printTimesTables(for: 8)`과 같이 `end` 값을 **지정하지 않고 호출**할 경우 자동으로 `12`가 사용됩니다. 물론 `printTimesTables(for: 5, end: 20)`와 같이 `end` 값을 **명시적으로 지정**하여 호출할 수도 있습니다. + - 경로 찾기 함수 `findDirections(from: String, to: String, route: String = "fastest", avoidHighways: Bool = false)`는 `route`를 "fastest"로, `avoidHighways`를 `false`로 설정하는 **합리적인 기본값**을 가집니다. 이를 통해 `findDirections(from: "London", to: "Glasgow")`와 같이 더 짧은 코드로 호출할 수 있습니다. + - `removeAll()` 함수에도 `keepingCapacity: Bool` 매개변수에 `false`라는 기본값이 있어, 배열 항목을 제거할 때 **메모리 용량을 유지할지 여부**를 선택할 수 있는 유연성을 제공합니다. +- **기본값 매개변수의 이점**: + - 기본 매개변수를 사용하면 함수 호출 시 코드를 **더 짧고 간단하게** 만들면서도, 필요할 때 **유연성**을 유지할 수 있습니다. + - Swift 개발자들이 매우 흔하게 사용하는 기능이며, 정기적으로 변경되어야 하는 **중요한 부분에 집중**할 수 있도록 돕고, 복잡한 함수를 **단순화**하며 코드를 작성하기 쉽게 만듭니다. +``` +// 기본값을 여러 개 사용한 함수 +func findDirections(from: String, to: String, route: String = "fastest", avoidHighways: Bool = false) { + print("출발지: \(from), 도착지: \(to)") + print("경로: \(route), 고속도로 우회: \(avoidHighways ? "예" : "아니요")") +} + +// 기본값 사용 +findDirections(from: "서울", to: "부산") + +// 일부 기본값 덮어쓰기 +findDirections(from: "서울", to: "부산", route: "scenic") + +// 모든 매개변수 명시 +findDirections(from: "서울", to: "부산", route: "shortest", avoidHighways: true) +``` + +## 3. 함수에서 에러 처리 (Handling Errors in Functions) + +- **에러 처리의 중요성**: + - 파일이 없거나 네트워크가 끊기는 등 문제가 발생하는 것은 흔한 일입니다. + - 이러한 에러를 **우아하게 처리하지 않으면** 앱이 **충돌(crash)**할 수 있습니다. + - Swift는 개발자가 에러를 처리하거나 최소한 에러의 존재를 **인지하도록 강제**합니다. 에러 처리를 제대로 시도하지 않으면 코드가 **컴파일되지 않습니다**. +- **Swift 에러 처리의 세 단계**: Swift에서 에러를 처리하는 과정은 다음과 같은 **세 가지 핵심 단계**로 이루어집니다: + 1. **에러 정의**: + - 코드에서 발생할 수 있는 모든 에러를 **열거형(enum)**으로 정의합니다. 이 열거형은 Swift의 내장 **`Error` 타입**을 기반으로 합니다. + - 예를 들어, `enum PasswordError: Error { case short, obvious }`와 같이 비밀번호 관련 에러를 `short`와 `obvious`로 정의할 수 있습니다. 이들은 단지 가능한 에러의 존재를 의미합니다. + 2. **에러를 던지는 함수 작성**: + - 심각한 문제가 발견되면 하나 이상의 정의된 에러를 **던질(throw)** 수 있는 함수를 작성합니다. + - 에러가 "던져진다"는 것은 함수에서 **치명적인 문제**가 발생하여 정상적인 실행을 계속할 수 없고, 값을 반환하지 않고 **즉시 종료**되며, 에러가 다른 코드 블록에서 처리되도록 전달된다는 의미입니다. + - 함수가 에러를 던질 수 있다면, 함수의 **반환 타입 앞에 `throws` 키워드**를 명시해야 합니다. + - `throws` 키워드는 함수가 에러를 **던질 수 있음**을 의미하며, 반드시 에러를 던져야 한다는 의미는 아닙니다. + - 에러를 던질 때는 `throw` 키워드 뒤에 정의된 에러 중 하나를 사용합니다 (예: `throw PasswordError.short`). 에러가 던져지면 함수는 **즉시 종료**되고, 이후의 코드는 실행되지 않습니다. + 3. **에러 처리**: + - 함수를 실행하고 발생할 수 있는 모든 에러를 처리해야 합니다. 실제 Swift 프로젝트에서는 `do`, `try`, `catch` **세 가지 키워드**를 사용하여 에러를 처리합니다. +- **`do-try-catch` 블록**: + - **`do` 블록**: 에러를 던질 수 있는 코드를 포함하는 **코드 블록**을 시작할 때 사용합니다. + - **`try` 키워드**: `do` 블록 내에서 **던지는 함수**를 호출할 때 반드시 `try`를 붙여야 합니다. + - **`try` 키워드의 중요성**: `try`는 일반적인 코드 실행이 **중단될 수 있음**을 개발자 자신과 다른 개발자들에게 **시각적으로 알리는 신호**입니다. Swift가 모든 던지는 함수 앞에 `try`를 강제하는 주된 이유는, 코드의 어느 부분이 에러를 발생시킬 수 있는지 **명확히 인지**하도록 하기 위함입니다. 예를 들어, 하나의 `do` 블록 내에 여러 함수 호출이 있을 때, `try`가 붙은 함수만 에러를 던질 수 있음을 명확히 보여줍니다. + - **`try!` 사용 시 주의사항**: `try!`는 `do`나 `catch` 블록을 요구하지 않는 `try`의 다른 버전입니다. 하지만 `try!`를 사용한 함수에서 **에러가 던져지면 앱이 실제 충돌(fatal crash)**합니다. 따라서 에러가 발생하지 않을 것이라고 **절대적으로 확신할 때만** 드물게 사용해야 합니다. + - **`catch` 블록**: `do` 블록 내에서 발생한 **모든 에러를 처리**합니다. `try`를 사용할 때는 반드시 `do` 블록 안에 있어야 하며, `catch` 블록에서 에러를 처리해야 합니다. + - **특정 에러 캐치**: 모든 종류의 에러를 처리하는 **일반 `catch` 블록**은 항상 필요하지만, 원하는 경우 **특정 에러**를 명시하여 사용자 지정 메시지를 표시할 수 있습니다. 예를 들어, `catch PasswordError.short { ... }` 또는 `catch PasswordError.obvious { ... }`와 같이 특정 에러 `case`에 대한 처리를 분리할 수 있습니다. 마지막의 일반 `catch { ... }` 블록은 "포켓몬 캐치(Pokemon catch)"라고도 불리며, 모든 나머지 에러를 "잡아야" 하기 때문에 사용됩니다. + - **에러 메시지**: Apple의 자체 프레임워크에서 던져지는 대부분의 에러는 사용자에게 표시할 수 있는 **의미 있는 메시지**를 제공합니다. Swift는 `catch` 블록 내에서 **`error` 상수**를 자동으로 제공하며, `error.localizedDescription`을 사용하여 발생한 일에 대한 일반적인 언어 설명을 읽는 것이 일반적입니다. +- **던지는 함수 사용 시기**: + - 던지는 함수는 함수가 에러를 **처리할 수 없거나 처리할 의사가 없을 때** 사용됩니다. 이는 반드시 에러를 던진다는 의미가 아니라, **던질 가능성**이 있다는 의미입니다. + - 에러 처리에는 여러 유효한 해결책이 있습니다: 에러를 함수 내에서 직접 처리하거나, 호출한 곳으로 에러를 **전파(error propagation)**하거나, 일부는 함수 내에서 처리하고 나머지는 전파하는 방식 등이 있습니다. + - **초보자의 경우**, 처음에는 던지는 함수 사용을 **대부분 피하는 것**이 좋습니다. 왜냐하면 던지는 함수를 사용할 때마다 모든 에러를 처리해야 하므로 "전염성"이 강하게 느껴질 수 있고, 에러가 더 전파되면 "감염"이 확산되는 것처럼 느껴질 수 있기 때문입니다. + - 점차적으로 에러 관리에 익숙해지면서 던지는 함수 사용에 자신감을 얻게 될 것입니다. + +``` +import SwiftUI + +struct ContentView: View { +    @State private var username: String = "" +    @State private var password: String = "" +    @State private var loginMessage: String = "" + +    // 1. 에러 정의 +    enum LoginError: Error { +        case emptyUsername +        case shortPassword +        case wrongPassword +    } + +    // 2. 에러를 던지는 함수 +    func login(username: String, password: String) throws -> String { +        if username.isEmpty { +            throw LoginError.emptyUsername +        } +        if password.count < 6 { +            throw LoginError.shortPassword +        } +        if password != "123456" { +            throw LoginError.wrongPassword +        } +        return "로그인 성공!" +    } + +    var body: some View { +        VStack(spacing: 20) { +            TextField("사용자 이름", text: $username) +                .textFieldStyle(RoundedBorderTextFieldStyle()) +            SecureField("비밀번호", text: $password) +                .textFieldStyle(RoundedBorderTextFieldStyle()) + +            Button("로그인 시도") { +                do { +                    let result = try login(username: username, password: password) +                    loginMessage = result +                } catch LoginError.emptyUsername { +                    loginMessage = "⚠️ 사용자 이름을 입력해주세요." +                } catch LoginError.shortPassword { +                    loginMessage = "⚠️ 비밀번호가 너무 짧아요. 6자 이상 필요해요." +                } catch LoginError.wrongPassword { +                    loginMessage = "❌ 비밀번호가 틀렸어요." +                } catch { +                    loginMessage = "❗️예기치 못한 오류: \(error.localizedDescription)" +                } +            } +            .padding() +            .background(Color.blue) +            .foregroundColor(.white) +            .cornerRadius(8) + +            Text(loginMessage) +                .foregroundColor(.red) +        } +        .padding() +    } +} +``` \ No newline at end of file