본문 바로가기
iOS프로그래밍 실무

[iOS프로그래밍 실무] 6주차

by heeaeeeee 2025. 4. 10.

옵셔널 체이닝

주로 자바스크립트와 같은 프로그래밍 언어에서 사용되는 문법으로, 객체의 속성에 접근할 때 해당 속성이 null 또는 undefined인 경우에 에러를 발생시키지 않고 안전하게 접근할 수 있도록 해줍니다. 옵셔널 체이닝을 사용하면 코드가 간결해지고, 중첩된 객체의 속성에 접근할 때 발생할 수 있는 오류를 줄일 수 있습니다.

const user = {
    profile: {
        name: '홍길동',
        age: 30
    }
};

// 옵셔널 체이닝을 사용하지 않은 경우
const userName = user && user.profile && user.profile.name; // '홍길동'

// 옵셔널 체이닝을 사용한 경우
const userName = user?.profile?.name; // '홍길동'

위의 코드에서 user?.profile?.name은 user나 profile이 null 또는 undefined일 경우 undefined를 반환하고, 그렇지 않으면 name의 값을 반환합니다. 이렇게 함으로써 코드의 가독성을 높이고, 예외 처리를 간편하게 할 수 있습니다.

 

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

var x: String? = "Hi" // 옵셔널 문자열 변수 x를 "Hi"로 초기화

print(x, x!) // x의 값과 x의 강제 언래핑 결과를 출력 (x가 nil이 아닐 경우에만 안전)

if let a = x { // x가 nil이 아닐 경우에만 a에 값을 할당
    print(a) // a의 값을 출력
}

let b = x!.count // x를 강제 언래핑하여 문자열의 길이를 b에 저장
print(type(of: b), b) // b의 타입과 값을 출력

let b1 = x?.count // x가 nil이 아닐 경우에만 count를 반환 (nil일 경우 nil)
print(type(of: b1), b1, b1!) // b1의 타입과 값을 출력, b1이 nil이 아닐 경우 강제 언래핑하여 출력

let c = x ?? "" // x가 nil일 경우 빈 문자열("")을 c에 저장
print(c) // c의 값을 출력

 

swift에서 물음표와 느낌표가 선언문과 실행문에서 쓰일 때 차이

기호  사용 용도  예시  코드 설명
? 옵셔널 타입 선언 var x: String? = "Hello" x는 String 타입이지만, nil이 될 수 있는 옵셔널 타입으로 선언됨
옵셔널 체이닝 let length = x?.count x가 nil이 아닐 경우에만 count를 반환하고, nil이면 결과도 nil
! 강제 언래핑 선언 let y = x! x의 값이 nil이 아닐 경우 그 값을 가져오지만, nil일 경우 런타임 오류가 발생함
강제 언래핑 실행 print(x!.count) x가 nil이 아닐 경우 count를 출력하지만, nil이면 프로그램이 중단됨
var x: String? = "Hello" // 물음표로 옵셔널 타입으로 선언

// 옵셔널 체이닝
if let length = x?.count { // x가 nil이 아닐 경우에만 length에 값 할당
    print("Length: \(length)")
} else {
    print("x is nil")
}

// 강제 언래핑
let y = x! // x의 값을 강제 언래핑 (nil일 경우 런타임 오류 발생)
print("Value: \(y)")

// 강제 언래핑 실행
print("Count: \(x!.count)") // x의 count를 출력 (nil일 경우 런타임 오류 발생)

 

int? / int! 차이점과 공통점

타입  설명  사용 예시
int? 옵셔널: nil을 가질 수 있는 정수형 변수. 안전하게 값을 다룰 수 있으며, 
값을 사용하기 전에 반드시 체크해야 합니다.
var x: Int? = nil <br> if let value = x { ... }
int! 강제 언래핑 옵셔널: 기본적으로 nil을 허용하지만, 
사용 시 강제로 값을 가져오는 타입. nil일 경우 런타임 오류가 발생합니다.
var x: Int! = 10 <br> let value = x 
(x가 nil일 경우 오류 발생)
var x: Int? = nil // x는 nil이 될 수 있는 옵셔널 정수
if let value = x { // 안전하게 값을 체크
    print("Value: \(value)")
} else {
    print("x is nil")
}

var y: Int! = 10 // y는 강제 언래핑 옵셔널
print("Value: \(y)") // y는 nil이 아닐 경우 정상 출력
// print(y!) // 강제 언래핑 사용 (nil일 경우 오류 발생)

 

swift에서 ?와 !의 차이

1. ? (옵셔널)

정의: 변수나 상수가 nil이 될 수 있음을 나타냅니다. 값이 존재하지 않을 수도 있음을 나타냅니다.

var optionalString: String? = "Hello" // 옵셔널 문자열
print(optionalString) // Optional("Hello")

if let unwrappedString = optionalString { // 옵셔널 바인딩
    print(unwrappedString) // "Hello" 출력
} else {
    print("optionalString is nil")
}

 

2. ! (강제 언래핑 옵셔널)

정의: 옵셔널 변수를 강제로 언래핑하여 값을 가져옵니다. 만약 값이 nil이라면 런타임 오류가 발생합니다.

var forcedString: String! = "World" // 강제 언래핑 옵셔널
print(forcedString) // "World" 출력

let unwrappedString = forcedString! // 강제 언래핑
print(unwrappedString) // "World" 출력

// 강제 언래핑 시도 (nil일 경우 오류 발생)
forcedString = nil
// print(forcedString!) // 이 줄은 런타임 오류를 발생시킵니다.

 

Optional Chaining 예

class Person { // Person이라는 클래스를 정의
    var name: String // 이름을 저장할 String 타입 변수
    var age: Int // 나이를 저장할 Int 타입 변수
    
    // 초기화 메서드
    init(name: String, age: Int) { // 클래스 초기화 시 이름과 나이를 매개변수로 받음
        self.name = name // 매개변수 name을 클래스 변수 name에 할당
        self.age = age // 매개변수 age를 클래스 변수 age에 할당
    }
}

let kim: Person = Person(name: "Kim", age: 24) // Person 클래스의 인스턴스를 생성하여 kim에 할당
print(kim.age) // kim의 나이를 출력 (24)

let han: Person? = Person(name: "Han", age: 20) // 옵셔널 타입으로 Person 인스턴스를 생성하여 han에 할당
print(han.age) // 오류 발생: han은 옵셔널이므로 직접 접근할 수 없음

// print(han!.age) // 강제 언래핑을 사용하여 han의 age를 출력 (han이 nil일 경우 오류 발생)

print(han?.age) // 옵셔널 체이닝을 사용하여 han의 age를 안전하게 출력 (han이 nil일 경우 nil 반환)

print((han?.age)!) // 강제 언래핑: han이 nil이 아닐 경우 age를 출력 (nil일 경우 오류 발생)

if let hanAge = han?.age { // 옵셔널 바인딩: han의 age가 nil이 아닐 경우 hanAge에 할당
    print(hanAge) // hanAge를 출력 (20)
} else {
    print("nil") // han이 nil이거나 age가 nil일 경우 "nil" 출력
}

 

throwing function

 

swift에서 throws 키워드가 있는 메소드 사용하는 방법

1. throws 메소드 정의하기

enum DivisionError: Error {
    case divideByZero // 0으로 나누는 경우
}

func divide(_ a: Int, _ b: Int) throws -> Int {
    if b == 0 {
        throw DivisionError.divideByZero // 오류 발생
    }
    return a / b // 정상적으로 나눈 결과 반환
}

2. try 키워드로 메소드 호출하기

do {
    let result = try divide(10, 2) // 정상 호출
    print("Result: \(result)") // "Result: 5" 출력
} catch {
    print("An error occurred: \(error)") // 오류가 발생할 경우 처리
}

3. 오류 처리하기

do {
    let result = try divide(10, 0) // 오류 발생
    print("Result: \(result)") // 이 줄은 실행되지 않음
} catch DivisionError.divideByZero {
    print("Error: Cannot divide by zero!") // 특정 오류 처리
} catch {
    print("An unexpected error occurred: \(error)") // 다른 오류 처리
}

 

swift에서 throwing 함수

1. 나누기 함수

enum DivisionError: Error {
    case divideByZero
}

func divide(_ a: Int, _ b: Int) throws -> Int {
    if b == 0 {
        throw DivisionError.divideByZero
    }
    return a / b
}

2. 문자열 정수 변환

func stringToInt(_ str: String) throws -> Int {
    guard let number = Int(str) else {
        throw NSError(domain: "InvalidInput", code: 1, userInfo: nil)
    }
    return number
}

3. 배열 인덱스 접근

func elementAtIndex<T>(_ array: [T], index: Int) throws -> T {
    guard index >= 0 && index < array.count else {
        throw NSError(domain: "IndexOutOfBounds", code: 2, userInfo: nil)
    }
    return array[index]
}

4. 파일 읽기

func readFile(atPath path: String) throws -> String {
    let content = try String(contentsOfFile: path)
    return content
}

5. JSON 파싱

func parseJSON(_ data: Data) throws -> [String: Any] {
    let json = try JSONSerialization.jsonObject(with: data, options: [])
    guard let dictionary = json as? [String: Any] else {
        throw NSError(domain: "InvalidJSON", code: 3, userInfo: nil)
    }
    return dictionary
}

6. 사용자 인증

enum AuthError: Error {
    case invalidCredentials
}

func authenticate(username: String, password: String) throws {
    guard username == "admin" && password == "password" else {
        throw AuthError.invalidCredentials
    }
}

7. 날짜 변환

func dateFromString(_ dateString: String) throws -> Date {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd"
    guard let date = formatter.date(from: dateString) else {
        throw NSError(domain: "InvalidDateFormat", code: 4, userInfo: nil)
    }
    return date
}

8. URL 검증

func validateURL(_ urlString: String) throws {
    guard let url = URL(string: urlString), UIApplication.shared.canOpenURL(url) else {
        throw NSError(domain: "InvalidURL", code: 5, userInfo: nil)
    }
}

9. 비밀번호 검증

func validatePassword(_ password: String) throws {
    guard password.count >= 8 else {
        throw NSError(domain: "WeakPassword", code: 6, userInfo: nil)
    }
}

10. 숫자 범위 체크

func checkRange(_ number: Int) throws {
    guard number >= 1 && number <= 100 else {
        throw NSError(domain: "OutOfRange", code: 7, userInfo: nil)
    }
}

 

프로그래밍 언어에서 (), [], {}, <> 용도

기호  용도  설명 예시
() 함수 호출 및 매개변수: 
함수의 매개변수를 전달하거나, 
수학적 표현에서 연산자의 우선 순위를 지정할 때 사용.
func add(a: Int, b: Int) -> Int { return a + b } 
<br> let sum = add(3, 5)
[] 배열 및 리스트: 
배열 또는 리스트와 같은 순차적 데이터 구조를 정의하고 접근할 때 사용.
let numbers = [1, 2, 3] <br> 
print(numbers[0])
{} 블록 및 객체 리터럴: 
코드 블록이나 객체, 클래스, 딕셔너리 등을 정의할 때 사용.
if condition { print("True") } <br> 
let dict = ["key": "value"]
<> 제네릭 및 타입 매개변수: 
제네릭 프로그래밍에서 타입 매개변수를 정의할 때 사용.
func printValue<T>(_ value: T) 
{ print(value) } <br> printValue("Hello")

 

일반 class vs. generic class

class Box<T> { // 제네릭 클래스를 정의, T는 타입 매개변수
    var item: T // 제네릭 타입 T를 사용하는 속성 item을 선언
    
    init(item: T) { // 초기화 메서드, 매개변수로 제네릭 타입 T를 받음
        self.item = item // 매개변수 item을 클래스 속성 item에 할당
    }
    
    func getItem() -> T { // 제네릭 타입 T를 반환하는 메서드 정의
        return item // item을 반환
    }
} // 일반 클래스 정의 종료

let intBox = Box<Int>(item: 12) // Box<Int> 타입의 인스턴스를 생성하고, item에 12 할당
// Box<Int>(item: 123)처럼 타입을 명시적으로 지정할 수 있지만,
print(intBox.getItem()) // intBox의 getItem() 메서드를 호출하여 12 출력

let stringBox = Box(item: "Hello") // Box<String> 타입의 인스턴스를 생성, 타입 추론으로 <String> 생략
print(stringBox.getItem()) // stringBox의 getItem() 메서드를 호출하여 "Hello" 출력

 

swift에서 generic 구조체

// 제네릭 구조체 정의
struct Pair<T, U> {
    var first: T // 첫 번째 요소, 타입 T
    var second: U // 두 번째 요소, 타입 U

    // 초기화 메서드
    init(first: T, second: U) {
        self.first = first // 첫 번째 요소 초기화
        self.second = second // 두 번째 요소 초기화
    }

    // 메서드: 두 요소를 튜플로 반환
    func getValues() -> (T, U) {
        return (first, second) // 두 요소를 튜플로 반환
    }
}

// 사용 예시

let intStringPair = Pair(first: 1, second: "One") // Int와 String 타입의 Pair 생성
print(intStringPair.getValues()) // (1, "One") 출력

let doubleBoolPair = Pair(first: 3.14, second: true) // Double과 Bool 타입의 Pair 생성
print(doubleBoolPair.getValues()) // (3.14, true) 출력

 

프로그래밍언어에서 collection Type 예를 들어 설명해줘

 


swift의 Array도 generic 구조체

 

빈 배열(empty array) 주의 사항

 

 

first와 last 프로퍼티

첫번째와 마지막 데이터 ? 값 가져오는 방법 > optional(1), optional(4) 출력

 

첨자(subscript)로 항목 접근

var num = [1, 2, 3, 4] // 배열 num을 정의
print(num[0], num[3]) // 1 4 출력
// 결과: 1과 4를 공백으로 구분하여 출력

print(num.first!) // 1 출력
// 결과: 배열의 첫 번째 요소인 1을 출력

for i in 0...num.count-1 { // 0부터 num.count-1까지 반복
    print(num[i]) // 각 요소를 출력
}
// 결과: 
// 1
// 2
// 3
// 4
// 각 요소가 개별적으로 출력됨

print(num[1...2]) // [2, 3] 출력
// 결과: 인덱스 1부터 2까지의 요소인 2와 3을 포함하는 배열을 출력

num[0...2] = [10, 20, 30] // 인덱스 0부터 2까지의 요소를 10, 20, 30으로 변경
print(num) // [10, 20, 30, 4] 출력
// 결과: 배열의 첫 세 요소가 10, 20, 30으로 변경되고, 마지막 요소는 그대로인 배열을 출력

 

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

var num = [1, 2, 3, 10, 20] // 배열 num을 정의
print(num) // [1, 2, 3, 10, 20] 출력
// 결과: 정의된 배열을 그대로 출력

print(num.min()) // Optional(1) 출력
// 결과: 배열의 최소값인 1을 Optional로 감싸서 출력

print(num.max()) // Optional(20) 출력
// 결과: 배열의 최대값인 20을 Optional로 감싸서 출력

print(num.min()!) // 1 출력
// 결과: Optional에서 값을 추출하여 1을 출력

print(num.max()!) // 20 출력
// 결과: Optional에서 값을 추출하여 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(num) //[1, 5, 4, 3, 2]
print(num.sorted(by:>)) //내림차순 정렬 결과를 리턴하고, 원본은 그대로
//[5, 4, 3, 2, 1]
print(num)//[1, 5, 4, 3, 2]

 

4주차

프로그래밍언어에서 access modifier

1. Public

public class PublicClass {
    public var property: Int = 0 // 외부에서 접근 가능
}

2. Internal

class InternalClass {
    var property: Int = 0 // 동일 모듈 내에서만 접근 가능
}

3. Fileprivate

class FilePrivateClass {
    fileprivate var property: Int = 0 // 같은 파일 내에서만 접근 가능
}

4. Private

class PrivateClass {
    private var property: Int = 0 // 같은 클래스 내에서만 접근 가능
    
    func accessProperty() -> Int {
        return property // 클래스 내부에서만 접근 가능
    }
}

 

swift 에서 access modifier

접근제어자 모듈 외부 접근 상속 및 재정의 가능 사용 범위
open 가능 가능 모듈 외부에서 접근 및 상속/재정의 가능
public 가능 불가능 모듈 외부에서 접근 가능
internal 불가능 불가능 같은 모듈 내에서만 접근 가능
fileprivate 불가능 불가능 같은 파일 내에서만 접근 가능
private 불가능 불가능 같은 타입 및 스코프 내에서만 접근 가능
package 제한적 제한적 같은 패키지 내에서만 접근 가능 (Swift 5.9 추가)

 

swift의 access control

public class MyClass{
// 모듈의 모든 소스 파일 내에서 접근+정의한 모듈을 가져오는 다른 모듈의 소스파일에서도 접근 가능
	fileprivate var name : String = "Kim"
	//현재 소스 파일 내에서만 사용 가능
	private func play() {}
	//현재 블럭 내에서만 사용 가능
	func display(){} //internal은 디폴트 속성으로 생략됨
	// internal 접근은 해당 모듈의 모든 소스 파일 내에서 사용
}

 

접근 제어

 

접근 제어 예제

// Public Class
public class Library {
    private var books: [Book] = [] // Private: 외부에서 접근 불가
    public var name: String // Public: 외부에서 접근 가능
    
    public init(name: String) {
        self.name = name
    }
    
    // Public 메서드: 책 추가
    public func addBook(_ book: Book) {
        books.append(book)
    }
    
    // Internal 메서드: 모든 책 목록 출력 (기본값)
    func listBooks() {
        for book in books {
            print(book.title)
        }
    }
}

// Public Class
public class Book {
    public var title: String // Public: 외부에서 접근 가능
    private var author: String // Private: 외부에서 접근 불가
    
    public init(title: String, author: String) {
        self.title = title
        self.author = author
    }
    
    // Public 메서드: 저자 이름 반환
    public func getAuthor() -> String {
        return author // 같은 클래스 내에서만 접근 가능
    }
}

// Internal Class
class Member {
    var name: String // Internal: 같은 모듈 내에서만 접근 가능
    private var borrowedBooks: [Book] = [] // Private: 외부에서 접근 불가
    
    init(name: String) {
        self.name = name
    }
    
    // Internal 메서드: 책 대출
    func borrowBook(_ book: Book, from library: Library) {
        library.addBook(book) // Library의 public 메서드 사용
        borrowedBooks.append(book)
    }
}

 

extension

 

 

iOS 강의 자료 참고했습니다