iOS프로그래밍 실무

6주차 - iOS앱

리버윤 2025. 4. 9. 14:03
728x90

 - 옵셔널을 언래핑하는 여러가지 방법

var x: String? = "Hi" // x는 옵셔널 타입(String?)이며 초기값은 "Hi"입니다.

 

- `x`는 옵셔널 타입으로 선언되었으며, 초기값은 `"Hi"`입니다. 옵셔널 타입은 값이 있을 수도 있고 없을 수도 있는 상태를 나타냅니다.

print(x, x!) // x! 강제 언래핑

- `x`는 옵셔널 타입이므로 출력 시 `Optional("Hi")` 형태로 나타납니다.
- `x!`는 강제 언래핑을 통해 옵셔널 값을 꺼내 `"Hi"`를 출력합니다.
  - **주의**: `x`가 `nil`일 경우 강제 언래핑(`x!`)은 런타임 에러를 발생시킵니다.

if let a = x {
    print(a)
}

- 옵셔널 바인딩(`if let`)을 사용하여 `x`의 값을 안전하게 추출합니다.
- `x`가 `nil`이 아니라면, 값이 `a`에 바인딩되고 `"Hi"`가 출력됩니다.
- 만약 `x`가 `nil`이라면 이 블록은 실행되지 않습니다.

let b = x!.count // nil값일 수도 있기 때문에 크래시 위험이 있음
print(type(of: b), b)

- 강제 언래핑(`x!`)을 통해 옵셔널 값을 꺼내고, 문자열의 길이를 계산합니다.
  - `"Hi"`의 길이는 2이므로 `b = 2`입니다.
- `type(of: b)`는 `Int`로 출력됩니다.
- **주의**: 만약 `x`가 `nil`이라면 강제 언래핑 시 런타임 에러가 발생합니다.

 

let b1 = x?.count // 크래시 위험때문에 ? 사용함
print(type(of: b1), b1, b1!)

- **옵셔널 체이닝**을 사용하여 안전하게 문자열 길이에 접근합니다.
  - `x?.count`는 `x`가 nil일 경우 `nil`, 그렇지 않으면 문자열 길이를 반환합니다.
  - 결과적으로, `b1 = Optional(2)`입니다.
- 출력:
  - `type(of: b1)`는 옵셔널 타입(`Optional`)입니다.
  - `b1!`는 강제 언래핑하여 값을 꺼내므로 2를 출력합니다.

let c = x ?? ""
print(c)

- **Nil 병합 연산자 (`??`)**를 사용하여 기본값을 설정합니다.
  - 만약 `x`가 nil이라면 빈 문자열(`""`)을 반환합니다.
  - 현재 `x = "Hi"`이므로, 결과적으로 `c = "Hi"`입니다.

---
1. **강제 언래핑 (`!`)**: 위험하지만 직접 값을 꺼낼 수 있음.
2. **옵셔널 바인딩 (`if let`)**: 안전하게 값을 추출하는 방법.
3. **옵셔널 체이닝 (`?.`)**: 중첩된 값에 안전하게 접근 가능.
4. **Nil 병합 연산자 (`??`)**: 기본값 설정으로 nil 처리.

 

 

- 두 문장의 공통점과 차이점

let b = x!.count
print(type(of:b),b)

let b1 = x?.count
print(type(of:b1),b1, b1!)


 **공통점**
1. 옵셔널 값 처리
   - 두 문장 모두 `x`라는 옵셔널 값을 기반으로 작업합니다.
   - `x`가 `nil`일 가능성을 고려하여 값을 접근하거나 처리합니다.
2. 값의 길이 확인
   - 두 문장 모두 `x`의 문자열 길이를 확인하려는 목적으로 작성되었습니다.
3. 언래핑 방식 사용
   - 두 문장은 각각 다른 방식으로 언래핑을 시도하지만, 최종적으로 `count` 값을 추출하려고 합니다.

---

1. **강제 언래핑 ('let b = x!.count')

var x: String? = nil
let b = x!.count // 런타임 에러 발생 (Fatal Error: Unexpectedly found nil while unwrapping an Optional value)


2. **옵셔널 체이닝 ('let b1 = x?.count')

var x: String? = nil
let b1 = x?.count // b1은 Optional(nil)로 설정됨, 에러 없음
print(b1) // 출력: nil

 

---
### 결론
- 강제 언래핑은 값이 반드시 존재한다고 확신할 때 사용하는 반면, 옵셔널 체이닝은 값이 없을 가능성을 고려하여 안전하게 접근하는 방법입니다

 

▷ Optional Chaining

// Person 클래스 정의
class Person {
  // 이름 속성 (String 타입)
  var name: String
  // 나이 속성 (Int 타입)
  var age: Int

  // 초기화 메서드 (initializer)
  init(name: String, age: Int) {
      // 전달받은 이름으로 Person 객체의 name 속성 초기화
      self.name = name
      // 전달받은 나이로 Person 객체의 age 속성 초기화
      self.age = age
  }
}

// Person 클래스의 인스턴스 생성 (Kim)
let kim: Person = Person(name: "Kim", age: 20)
// Kim 객체의 age 속성 출력 (옵셔널이 아님)
print(kim.age) // 출력: 20

// Person 클래스의 옵셔널 인스턴스 생성 (Han)
// Han은 nil일 수도 있는 Person 객체를 가리킬 수 있음
let han: Person? = Person(name: "Han", age: 25)

// 옵셔널 체이닝을 사용하여 han 객체의 age 속성에 접근
// han이 nil이 아니면 age 속성 값을 가져오고, nil이면 nil을 반환
//print(han?.age) //에러 발생: han이 nil일 수도 있으므로, age 속성도 옵셔널(Int?) 타입이 됨.
//print(han!.age) // Han이 nil일 경우 런타임 에러 발생 (강제 언래핑)
 //print(han?.age) //Optional(25), 옵셔널 체이닝

// 옵셔널 체이닝과 강제 언래핑을 함께 사용 (위험)
// han이 nil이 아니면 age 속성 값을 가져와 강제로 언래핑
// han이 nil이면 런타임 에러 발생
 print((han?.age)!) // 출력: 25

// 옵셔널 바인딩을 사용하여 안전하게 옵셔널 값 처리
// han?.age가 nil이 아니면 hanAge에 값을 할당하고 if 블록 실행
if let hanAge = han?.age {
  // hanAge는 옵셔널이 아닌 Int 타입
  print(hanAge) // 출력: 25
} else {
  // han이 nil이거나 age 속성이 없는 경우
  print("nil") // 출력되지 않음 (han

 

- 옵셔널 체이닝 : 점 앞에 ? 사용

class Company {
    var ceo: Person?
}
class Person {
    var name: String
    init(name: String) {
        self.name = name
    }
}
let apple = Company()
apple.ceo = Person(name: "Kim")
//print(apple.ceo.name) //오류

// 옵셔널 체이닝 없이면:
if let ceo = apple.ceo {
    print(ceo.name) //kim
}
print(apple.ceo!.name) //kim
print(apple.ceo?.name) //Optional("Kim")
// 옵셔널 체이닝
if let name = apple.ceo?.name {
    print(name) //kim
}
print(apple.ceo?.name ?? "CEO가 없습니다") // kim

 

- 오류 제어

 

▷ try?

: 에러를 간단히 무시하거나 실패 여부만 확인하고 싶을 때 사용한다.

let result = try? throwingFunction()
if let value = result {
	print("성공: \(value)") 
} else {
	print("실패")
}

- try!

let result = try! throwingFunction()
print("결과: \(result)")

- 에러가 절대 발생하지 않은 것이라고 가정할 때 사용한다.

- 만약 에러가 발생한다면 프로그램이 크래시

 

- throwing function

iOS 개발에서 자주 사용되는 throwing function (오류를 던질 수 있는 함수) 10가지:
1.  **`Data(contentsOf: URL)`:** URL에서 데이터를 읽을 때, URL 문제나 네트워크 오류 시 오류 발생.
2.  **`JSONSerialization.jsonObject(with: Data, options: ...)`:** JSON 데이터를 Swift 객체로 변환할 때, JSON 형식이 잘못되면 오류 발생.
3.  **`FileManager.createDirectory(at: URL, ...)`:** 파일 시스템에 디렉토리를 생성할 때, 권한 문제나 공간 부족 시 오류 발생.
4.  **`FileManager.copyItem(at: URL, to: URL)`:** 파일을 복사할 때, 권한 문제나 파일 없음 등의 이유로 오류 발생.
5.  **`FileManager.removeItem(at: URL)`:** 파일 또는 디렉토리를 삭제할 때, 권한 문제나 파일 없음 등의 이유로 오류 발생.
6.  **`String(contentsOf: URL, encoding: ...)`:** URL에서 문자열을 읽을 때, URL 문제나 인코딩 오류 시 오류 발생.
7.  **`PropertyListSerialization.propertyList(from: Data, ...)`:** Property List 데이터를 Swift 객체로 변환할 때, 형식 오류 시 오류 발생.
8.  **Core Data `managedObjectContext.save()`:** Core Data 변경 사항을 저장할 때, 유효성 검사 실패나 저장소 문제 시 오류 발생.
9.  **`NSRegularExpression(pattern: ..., options: ...)`:** 정규 표현식을 생성할 때, 패턴이 유효하지 않으면 오류 발생.
10. **UserDefault에 저장하는 방식:** 저장하려는 데이터가 Property list가 아닐경우 오류가 발생 

func can() throws

 

매개변수 괄호 다음에 throws라는 키워드가 있는 함수는 그냥 사용할 수 없고 error handing을 해야한다.

 

그냥 AVAudioPlayer(contentsOf:audioFile) 이렇게는 호출 불가

do try catch로 error handing해야 함.

( 하지 않으면 Call can throw, but it is not marked with 'try' and the error is not handled" 오류가 발생 )

 

▷ Generic <>

func myPrint <T> (a: T, b: T) {
    print(b,a)
}
//func myPrint(a: Double, b: Double) { //함수중첩(오버로딩)
//    print(b,a)
//}
myPrint(a:1,b:2)
myPrint(a:2.5,b:3.5)
myPrint(a: "Hi", b: "Hello")

 

- Generic class

class Box {
    var item: Int
    
    init(item: Int) {
        self.item = item
    }
    func getItem() -> Int {
        return item }
} //일반 클래스

let intBox = Box(item: 12)
// Box<Int>(item: 123), generic class는 이렇게 쓰지만 타입 추론으로 <Int> 생략 가능
//print(intBox.getItem()) // 12

let stringBox = Box(item: "Hello") // Box<String>(item: "Hello")
print(stringBox.getItem()) // Hello

class Box <T> {
    var item: T
    
    init(item: T) {
        self.item = item
    }
    func getItem() -> T {
        return item }
} //일반 클래스

let intBox = Box(item: 12)
// Box<Int>(item: 123), generic class는 이렇게 쓰지만 타입 추론으로 <Int> 생략 가능
//print(intBox.getItem()) // 12

let stringBox = Box(item: "Hello") // Box<String>(item: "Hello")
print(stringBox.getItem()) // Hello

 

- 빈 배열 (swiftArraygeneric 구조체)

var x : [Int]=[] //빈 배열, empty array
var y = [Int]()
var z : Array<Int> = []

 

▷ Collection Type

 프로그래밍 언어에서 컬렉션 타입(Collection Type)은 여러 개의 데이터 요소(element)를 하나의 단위로 묶어서 관리하고 조작할 수 있도록 제공하는 자료구조입니다. 컬렉션 타입은 데이터를 효율적으로 구성하고, 저장하고, 접근하고, 수정하고, 검색하는 데 사용됩니다.

- 컬렉션 타입의 주요 특징
*   **데이터 저장 :  여러 개의 데이터를 저장할 수 있습니다.
*   **데이터 관리 :  데이터를 추가, 삭제, 수정, 검색하는 기능을 제공합니다.
*   **데이터 접근 :  특정 위치의 데이터에 접근할 수 있는 방법을 제공합니다.
*   **다양한 종류 :  프로그래밍 언어는 다양한 컬렉션 타입을 제공하며, 각각 다른 특징과 성능을 가집니다.

**일반적인 컬렉션 타입의 종류:**
1.  **배열 (Array/List):**
    *   순서가 있는 데이터의 집합입니다.
    *   각 요소는 인덱스(index)를 통해 접근할 수 있습니다.
    *   대부분의 언어에서 배열의 크기는 고정되거나 동적으로 조절될 수 있습니다.
    *   예: `[1, 2, 3, 4, 5]`, `["apple", "banana", "cherry"]`
2.  **연결 리스트 (Linked List):**
    *   각 요소가 다음 요소의 주소를 가지고 있는 데이터의 집합입니다.
    *   배열과 달리 메모리에 연속적으로 저장되지 않습니다.
    *   데이터의 삽입/삭제가 빈번하게 일어나는 경우 효율적입니다.
3.  **집합 (Set):**
    *   중복된 요소를 허용하지 않는 데이터의 집합입니다.
    *   요소의 순서는 보장되지 않습니다.
    *   특정 요소가 집합에 존재하는지 빠르게 확인할 수 있습니다.
    *   예: `{1, 2, 3, 4, 5}`, `{"apple", "banana", "cherry"}`
4.  **딕셔너리 (Dictionary/Map/Associative Array):**
    *   키(key)와 값(value)의 쌍으로 이루어진 데이터의 집합입니다.
    *   키를 사용하여 값에 접근할 수 있습니다.
    *   키는 중복될 수 없습니다.
    *   예: `{"name": "John", "age": 30, "city": "New York"}`
5.  **큐 (Queue):**
    *   FIFO (First-In, First-Out) 원칙에 따라 데이터를 관리하는 자료구조입니다.
    *   데이터는 큐의 뒤쪽(rear)에 추가되고, 앞쪽(front)에서 제거됩니다.
    *   예: 프린터 큐, 메시지 큐
6.  **스택 (Stack):**
    *   LIFO (Last-In, First-Out) 원칙에 따라 데이터를 관리하는 자료구조입니다.
    *   데이터는 스택의 맨 위(top)에 추가되고, 맨 위에서 제거됩니다.
    *   예: 함수 호출 스택, 되돌리기 기능
7.  **트리 (Tree):**
    *   계층적인 데이터 관계를 표현하는 자료구조입니다.
    *   노드(node)와 간선(edge)으로 구성됩니다.
    *   예: 파일 시스템, 조직도
8.  **그래프 (Graph):**
    *   노드(node)와 간선(edge)으로 이루어진 자료구조입니다.
    *   노드 간의 관계를 표현하는 데 사용됩니다.
    *   예: 소셜 네트워크, 도로망
다. 컬렉션 타입을 적절하게 선택하고 사용하는 것은 효율적인 프로그래밍에 매우 중요합니다.
 

▷ Array

- 프로퍼티

*   **`count`**: 배열 요소 개수 확인 (배열 크기)
*   **`isEmpty`**: 배열이 비어있는지 확인

- 메소드

*   **`append(_:)`**: 배열 끝에 요소 추가
*   **`remove(at:)`**: 특정 인덱스의 요소 삭제
*   **`forEach(_:)`**: 배열의 각 요소에 대해 반복 작업 수행
*   **`map(_:)`**: 배열의 각 요소를 변환하여 새로운 배열 생성
*   **`filter(_:)`**: 특정 조건을 만족하는 요소만 골라 새로운 배열 생성

 

-  빈 배열(empty array) 주의 사항

let으로 선언된 변수는 불변(immutable)하기 때문이다. 즉, 한 번 값이 할당되면 변경할 수 없습니다.

//let number : [Int] = []
var number : [Int] = []

var odd = [Int] ()
var even : Array<Int> = Array()
print(number)
//print(number[0]) //오류, 빈 배열은 값을 넣은 다음에 접근해야 함.
number.append(100) //let으로 선언했다면 불변형 배열이라 추가 불가능
print(number[0])
number.append(200)
print(number[0], number[1], number)

let으로 선언한다면 불변형 배열이라 추가가 불가능하기 때문에 오류가 발생함.

 

- 초기값에서 변경되지 않는 애들은 굳이 var로 만들지 않고 let으로 만들어도 됨.

- 가변형(mutable)
   var animal = ["dog", "cat","cow"]

- 불변형 (immutable)

    초기화 후 변경 불가

    let animal1 = ["dog", "cat","cow"]

var number : [Int] = []

//number[0]=1 //crash, 방을 만든 후 사용하라!, 첫번째 방을 만들고 1을 넣어라
number.append(1)
print(number)
number[0]=10
print(number)

꼭 append로 방 만들어서 사용해야 함.

- Array(repeating:count:)

var x = [0,0,0,0,0]
print(x) //[0, 0, 0, 0, 0]

var x1 = Array(repeating: 0, count: 5)
print(x1) //[0, 0, 0, 0, 0]

var x2 = [Int](repeating: 1, count: 3)
print(x2) //[1, 1, 1]

var x3 = [String](repeating: "A", count: 4)
print(x3) //["A", "A", "A", "A"]

 

- for~in으로 배열 항목 접근

let colors = ["red", "green", "blue"]
print(colors) //["red", "green", "blue"]

for x in colors {
    print(x)
}
////red
//green
//blue

 

- 항목이 몇 개인지(count), 비어있는지(isEmpty) 알아내기

let num = [1, 2, 3, 4]
var x = [Int]()
print(num.isEmpty) //배열이 비어있나?, false
if num.isEmpty {
    print("비어 있습니다")
}
else {
    print(num.count) //배열 항목의 개수, 4
}

 

- firstlast 프로퍼티

let num = [1, 2, 3, 4]
let num1 = [Int]()

print(num.first, num.last) //Optional(1) Optional(4)
print(num1.first, num1.last) //nil nil
if let f = num.first, let l = num.last {
    print(f,l) //1 4
}

 

첨자(subscript)로 항목 접근

var num = [1, 2, 3, 4]
print(num[0], num[3])  //1 4
print(num.first!)  //1

for i in 0...num.count-1{
    print(num[i]) //1n\ 2\n 3\n 4\n
}
print(num[1...2]) //[2, 3]

num[0...2] = [10,20,30] //0~2번째 것들이 이걸로 바뀜
print(num) //[10, 20, 30, 4]

 

- Array : 추가/제거

//let x = [1, 2, 3, 4] //불변 배열은 초깃값에서 변경 불가
//x.append(5)
var num = [1,2,3]
print(num)

num.append(4)
print(num)

num.append(contentsOf: [6, 7, 8])
print(num) //[1, 2, 3, 4, 6, 7, 8]

num.insert(5, at:4)
print(num) //[1, 2, 3, 4, 5, 6, 7, 8]

num.remove(at:3)
print(num) //[1, 2, 3, 5, 6, 7, 8]

num.removeLast()
print(num) //[1, 2, 3, 5, 6, 7]
print(num.firstIndex(of:2)) //Optional(1), 2가 처음으로 나오는 첨자

if let i = num.firstIndex(of:2) { //2가 처음으로 저장된 방의 값을 20으로 바꿈
    num[i] = 20 //num[1] = 20
}
print(num) //[1, 20, 3, 5, 6, 7]

num=num+num
print(num) //[1, 20, 3, 5, 6, 7, 1, 20, 3, 5, 6, 7]

num+=[8,9]
print(num) //[1, 20, 3, 5, 6, 7, 1, 20, 3, 5, 6, 7, 8, 9]

num.removeAll()
print(num) //[]

 

 

* Array는 구조체이므로 값 타입

var num = [1,2,3]
var x = num //x는 num의 복사본, 별개의 배열
num[0]=100
print(num)
print(x)

 

 

- 배열(Array) 요소의 최댓값 최솟값 : max(), min()

var num = [1,2,3,10,20]
print(num) //[1, 2, 3, 10, 20]
print(num.min()) //Optional(1)
print(num.max()) //Optional(20)
print(num.min()!) //1
print(num.max()!) //20

 

 

- Array 요소의 정렬 (시험에 잘 나옴!!)

var num = [1,5,3,2,4]
num.sort() //오름차순 정렬하여 !원본 변경!
print(num) //[1, 2, 3, 4, 5]

num[0...4] = [2,3,4,5,1]
num.sort(by:>) //내림차순 정렬하여 !원본 변경!
print(num) //[5, 4, 3, 2, 1]

num[0...4] = [2,3,4,5,1]
num.reverse() //반대로 정렬하여 !원본 변경!, 배열의 앞 뒤를 완전히 바꾸는 것.
print(num) //[1, 5, 4, 3, 2]

print(num.sorted()) //오름차순 정렬 결과를 리턴하고, !원본은 그대로!
//var x = num.sorted() //[1, 2, 3, 4, 5]
//print(x)

print(num) //[1, 5, 4, 3, 2]

print(num.sorted(by:>)) //내림차순 정렬 결과를 리턴하고, 원본은 그대로
//[5, 4, 3, 2, 1]
print(num)//[1, 5, 4, 3, 2]

 

▷ swift 접근제어

: 접근 속성(접근 수정자, 액세스 수정자, 액세스 지정자 )는 클래스, 메서드, 멤버의 접근 가능성을 설정하는 객체 지향 언어의 키워드

 

  • internal 접근
    • 해당 모듈의 모든 소스 파일 내에서 사용되지만, 해당 모듈 외부의 소스파일에서는 사용되지 않도록 함
  • package 접근
    • 패키지의 모든 소스 파일 내에서 사용할 수 있지만 패키지 외부의 모든 소스 파일에서는 사용할 수 없음
  • fileprivate 접근
    • 해당 소스 파일 내에서만 사용 가능
  • private 접근
    • 블록{}과 동일한 파일에 있는 해당 선언의 extension에서만 접근 가능

 

 

 

뤼튼 사용하러 가기 > https://agent.wrtn.ai/5xb91l

728x90