swift

swift - 기본문법

호리둥절 2023. 4. 17. 17:51

상수와 변수

[ 상수 ]

상수(constant)는 값을 한 번 할당한 후 변경할 수 없습니다. let 키워드를 사용하여 선언합니다

let name = "John"

[ 변수 ]

변수(variable)는 값을 할당한 후 변경할 수 있습니다. var 키워드를 사용하여 선언합니다.

var age = 30
age = 31 // 값을 변경할 수 있습니다.

기본데이터 타입

  • Int: 정수 값을 나타냅니다. Int8, Int16, Int32, Int64 등의 다양한 크기의 정수 타입도 있습니다.
  • UInt: 부호 없는 정수 값을 나타냅니다. UInt8, UInt16, UInt32, UInt64 등의 다양한 크기의 부호 없는 정수 타입도 있습니다.
  • Float: 단정밀도 부동 소수점 값을 나타냅니다.
  • Double: 배정밀도 부동 소수점 값을 나타냅니다.
  • Bool: true 또는 false 값을 나타냅니다.
  • String: 문자열 값을 나타냅니다.
  • Character: 단일 문자 값을 나타냅니다.
  • Optional: nil 또는 값을 가질 수 있는 타입을 나타냅니다. ex) Int?, String?  

* Optional 옵셔널 변수에 접근할 때에는 옵셔널 연산자 ! 또는 ?를 사용합니다. ! 연산자는 옵셔널 변수의 값이 nil이 아님을 확신할 때 사용합니다. 이때, 값이 nil인 상태에서 ! 연산자를 사용하면 런타임 오류가 발생합니다. 반면, ? 연산자는 옵셔널 변수의 값이 nil일 수도 있을 때 사용하며, 이를 안전하게 처리할 수 있도록 돕습니다.

Any, AnyObject, nil 타입

[ Any ]

Swift에서 모든 타입을 나타내는 최상위 타입입니다. Any 타입은 어떤 타입의 값도 담을 수 있습니다. Any 타입을 사용하면 모든 값을 하나의 변수에 담을 수 있습니다.

var anyValue: Any = 10
anyValue = "Hello, world!"

[ AnyObject ]

AnyObject는 클래스 타입을 나타내는 프로토콜입니다. 즉, AnyObject는 클래스 인스턴스만을 나타낼 수 있습니다. AnyObject는 Objective-C와의 호환성을 위해 사용됩니다.

class MyClass {}
var object: AnyObject = MyClass()

[ nil ]

nil은 Swift에서 "값이 없음"을 나타내는 특별한 상태입니다. nil은 모든 타입의 값이 될 수 있습니다. Optional 타입에서는 값이 있을 수도 있고 없을 수도 있는 경우에 nil을 사용합니다.

var optionalValue: Int? = nil

Array, Dictionary, Set 컬렉션 타입

[ Array ]

순서가 있는 동일한 타입의 값들의 컬렉션입니다. 배열은 대괄호([])를 사용하여 생성하며, 배열의 요소는 인덱스를 사용하여 접근할 수 있습니다.

var numbers = [1, 2, 3, 4, 5]
numbers.append(6)

[ Dictionary ]

키-값 쌍의 컬렉션입니다. 딕셔너리는 대괄호([])를 사용하여 생성하며, 각 요소는 키와 값의 쌍으로 이루어집니다. 딕셔너리의 요소는 키를 사용하여 접근할 수 있습니다.

var scores = ["Alice": 80, "Bob": 90, "Charlie": 85]
scores["Dave"] = 70
print(scores["Bob"]) // Optional(90)

[ Set ]

중복되지 않는 동일한 타입의 값들의 컬렉션입니다. 세트는 중괄호({})를 사용하여 생성하며, 요소는 순서 없이 저장됩니다.

var letters: Set<Character> = ["a", "b", "c", "d", "e"]
letters.insert("f")

열거형

열거형(Enum)은 다양한 값 중 하나를 가지도록 정의하는 유용한 데이터 타입입니다. 열거형을 사용하면 특정 값을 제한하거나 구분할 수 있습니다.

열거형을 정의할 때, 각 케이스는 고유한 이름을 가집니다. 이러한 이름은 일반적으로 대문자로 작성됩니다. 또한 열거형의 각 케이스는 해당 값을 지정할 수 있습니다.

// 간단한 열거형 예시
enum CompassDirection {
    case north
    case south
    case east
    case west
}

// 열거형을 이용한 변수 선언 및 사용
var direction: CompassDirection = .north
direction = .east

// 연관 값을 갖는 열거형 예시
enum HTTPStatus {
    case ok
    case badRequest(String)
    case unauthorized
    case notFound
}

// 연관 값을 사용하는 열거형 변수 선언 및 사용
let status = HTTPStatus.badRequest("Invalid Request")
switch status {
case .ok:
    print("Success")
case .badRequest(let message):
    print("Error: \(message)")
case .unauthorized:
    print("Unauthorized")
case .notFound:
    print("Not Found")
}

구조체

구조체는 데이터 모델링과 관련된 값(Value) 타입을 정의하는데 사용됩니다. 구조체는 클래스와 비슷한 기능을 가지지만, 구조체는 값(Value) 타입이고 클래스는 참조(Reference) 타입입니다. 이러한 차이점 때문에 구조체는 상속이 불가능하며, 인스턴스가 생성될 때 복사(copy)가 이루어집니다.

[ 정의 ]

구조체를 정의할 때는 struct 키워드를 사용합니다. 구조체 내부에는 변수, 상수, 메소드 등이 포함될 수 있습니다.

struct Person {
    var name: String
    var age: Int
    
    func sayHello() {
        print("Hello, my name is \(name) and I'm \(age) years old.")
    }
}

[ 사용 ]

구조체를 사용할 때는 var 또는 let 키워드를 사용하여 인스턴스를 생성하고 값을 할당할 수 있습니다. let은 불변프로퍼티 이므로 인스턴스 생성후 수정이 불가합니다

var person1 = Person(name: "John", age: 30)
person1.sayHello()

let person2 = Person(name: "Jane", age: 25)
person2.sayHello()

※ 구조체는 값(Value) 타입이기 때문에 함수의 매개변수로 전달될 때, 함수 내부에서 값을 변경해도 호출자의 원본 인스턴스에는 영향을 주지 않습니다.

func increaseAge(person: Person) {
    var newPerson = person
    newPerson.age += 1
    print("\(person.name)'s age increased to \(newPerson.age)")
}

let person3 = Person(name: "Mike", age: 40)
increaseAge(person: person3)
person3.sayHello() // "Hello, my name is Mike and I'm 40 years old."

class

Class는 객체지향 프로그래밍(OOP)을 구현하는 데 사용되는 데이터 유형입니다. Class는 구조체(Struct)와 매우 유사하지만 몇 가지 중요한 차이점이 있습니다.

가장 큰 차이점은 클래스는 참조 유형(reference type)이라는 것입니다. 즉, 클래스 인스턴스는 복사가 아니라 참조되며, 한 번 인스턴스를 생성하면 해당 인스턴스에 대한 포인터를 전달하여 참조로 전달됩니다. 구조체는 값 유형(value type)이므로 값 복사가 발생합니다.

다른 차이점으로는 클래스는 상속(inheritance)과 타입 캐스팅(type casting)이 가능하다는 점입니다. 이러한 기능은 구조체에서는 사용할 수 없습니다.

[ 정의 ]

class Person {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    
    func sayHello() {
        print("Hello, my name is \(name) and I'm \(age) years old.")
    }
}

[ 사용 ]

let person = Person(name: "John", age: 30)
person.sayHello() // Hello, my name is John and I'm 30 years old.

조건문

[ if-else문 ]

if condition1 {
    // condition1이 참인 경우 실행할 코드
} else if condition2 {
    // condition2가 참이고, condition1이 거짓인 경우 실행할 코드
} else {
    // condition1과 condition2가 모두 거짓인 경우 실행할 코드
}

//예시
let num = 7

if num > 10 {
    print("num is greater than 10")
} else if num > 5 {
    print("num is greater than 5")
} else {
    print("num is less than or equal to 5")
}

[ switch문 ]

switch value {
case pattern1:
    // do something
case pattern2, pattern3:
    // do something else
default:
    // do something if none of the above cases are true
}
//예시
let color = "green"

switch color {
case "red":
    print("The color is red.")
case "blue":
    print("The color is blue.")
default:
    print("The color is neither red nor blue.")
}

반복문

[ for-in ]

for element in sequence {
    // do something with each element
}

//예시
let fruits = ["apple", "banana", "orange"]

for fruit in fruits {
    print("I like to eat \(fruit)s.")
}

for-in key value형태

let dict = ["a": 1, "b": 2, "c": 3]

for (key, value) in dict {
    print("\(key): \(value)")
}

[ while ]

while condition {
    // do something while condition is true
}

//예시
var count = 0

while count < 10 {
    print("The count is \(count).")
    count += 1
}

함수

[ 구현 ]

func functionName(parameter1: ParameterType1, parameter2: ParameterType2, ...) -> ReturnType {
    // 함수 구현
    return returnValue
}

위 구조에서, functionName은 함수의 이름이며, parameter1, parameter2 등은 함수에 전달되는 매개변수의 이름과 타입을 나타냅니다. ReturnType은 함수가 반환하는 값의 타입을 나타내며, returnValue은 함수가 반환하는 값입니다.

[ 호출 ]

functionName(argument1: value1, argument2: value2)

[ 레이블 생략 ]

매개변수 레이블을 생략하면, 함수 호출 시에는 해당 매개변수에 대한 레이블을 사용하지 않아도 됩니다.

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

let result = sum(10, 20)
print(result) // 30

[ 매개변수 기본값 ]

매개변수에 기본값을 설정하면, 함수 호출 시 해당 매개변수에 값을 전달하지 않으면 기본값이 사용됩니다.

func sayHello(to name: String = "World") {
    print("Hello, \(name)!")
}

// "Hello, World!" 출력
sayHello()

// "Hello, Alice!" 출력
sayHello(to: "Alice")

[ 전달인자 레이블 ]

전달인자 레이블은 함수 호출 시, 매개변수 이름 앞에 사용되는 레이블입니다. 전달인자 레이블을 사용하면, 함수를 호출하는 코드의 가독성이 좋아지고, 함수의 매개변수 역할을 명확하게 알 수 있습니다.

func sendMessage(to recipient: String, message: String) {
    print("To: \(recipient), Message: \(message)")
}

// "To: Alice, Message: Hello!" 출력
sendMessage(to: "Alice", message: "Hello!")

[ 가변매개변수 ]

가변 매개변수(variable number of arguments)를 사용하면, 함수를 호출할 때 매개변수의 개수를 동적으로 지정할 수 있습니다.

가변 매개변수는 매개변수 이름 앞에 ...을 붙여서 정의합니다. 함수 내에서 가변 매개변수는 배열(Array)으로 사용됩니다.

func printNumbers(_ numbers: Int...) {
    for number in numbers {
        print(number)
    }
}

printNumbers(1, 2, 3)  // 1, 2, 3 출력
printNumbers(4, 5)     // 4, 5 출력
printNumbers()

※ 주의할 점은, 함수 내에서 가변 매개변수를 사용하는 경우, 가변 매개변수는 반드시 매개변수 목록의 마지막에 위치해야 합니다.

[ 함수를 데이터 타입으로 사용 ]

예를 들어, 다음과 같이 Int 타입 매개변수를 받고 String 타입을 반환하는 함수의 타입을 정의할 수 있습니다.

func intToString(_ number: Int) -> String {
    return String(number)
}

var functionType: (Int) -> String = intToString

코드에서 functionType 변수는 (Int) -> String 타입을 가지는 변수입니다. 이는 Int 타입 매개변수를 받고 String 타입을 반환하는 함수를 참조할 수 있다는 의미입니다. 따라서 functionType 변수에는 intToString 함수를 할당할 수 있습니다.

클로저

클로저(Closure)는 일종의 함수이며, 이름 없는 함수를 의미합니다. 함수와 마찬가지로, 입력 매개변수와 반환 값이 있습니다.

Swift에서는 클로저를 { }(중괄호)로 표현합니다. 함수와 마찬가지로 클로저도 코드의 재사용성과 유지보수성을 높이기 위해 사용됩니다. 주로 비동기 처리나 함수형 프로그래밍 패턴에서 사용됩니다.

[ 사용 ]

//기본적인 클로저

let add = { (a: Int, b: Int) -> Int in
    return a + b
}

let result = add(2, 3) // result는 5가 됩니다.

//클로저를 이용한 정렬
let names = ["Apple", "Orange", "Banana"]
let sortedNames = names.sorted { (name1: String, name2: String) -> Bool in
    return name1 < name2
}
print(sortedNames) // 출력: ["Apple", "Banana", "Orange"]

//클로저를 이용한 맵핑
let numbers = [1, 2, 3, 4, 5]
let mappedNumbers = numbers.map { (number: Int) -> Int in
    return number * 2
}
print(mappedNumbers) // 출력: [2, 4, 6, 8, 10]

//클로저를 이용한 필터링
let numbers = [1, 2, 3, 4, 5]
let filteredNumbers = numbers.filter { (number: Int) -> Bool in
    return number % 2 == 0
}
print(filteredNumbers) // 출력: [2, 4]

후행클로저

 

프로퍼티

프로퍼티(Property)란 클래스, 구조체, 열거형 등의 데이터 타입 내부에 선언되어, 해당 데이터 타입이 가지는 속성을 나타내는 변수나 상수를 말합니다. 프로퍼티를 사용하면, 해당 데이터 타입이 가진 정보를 저장하고, 접근하고, 수정할 수 있습니다.

class Person {
    // 인스턴스 저장 프로퍼티
    var name: String
    var age: Int
    
    // 지연 저장 프로퍼티
    lazy var greeting: String = {
        return "Hello, \(name)"
    }()
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    
    // 인스턴스 연산 프로퍼티
    var isAdult: Bool {
        get {
            return age >= 19
        }
        set(newAdult) {
            age = newAdult ? 19 : 18
        }
    }
    
    // 타입 저장 프로퍼티
    static let species = "Homo Sapiens"
    
    // 타입 연산 프로퍼티
    static var lifeExpectancy: Int {
        return 80
    }
}

var person1 = Person(name: "John", age: 30)
print(person1.name) // "John"
print(person1.age) // 30
print(person1.greeting) // "Hello, John"
print(person1.isAdult) // true

person1.isAdult = false
print(person1.age) // 18

print(Person.species) // "Homo Sapiens"
print(Person.lifeExpectancy) // 80

프로퍼티 감시자

프로퍼티 감시자(Property Observers)는 프로퍼티 값의 변화를 감시하고, 값이 변경되면 적절한 액션을 취할 수 있도록 하는 기능입니다.

프로퍼티 감시자는 크게 두 가지 종류가 있습니다.

  • willSet: 프로퍼티 값이 변경되기 직전에 호출됩니다. 이 때 바뀌기 전 값이 파라미터로 전달됩니다.
  • didSet: 프로퍼티 값이 변경된 직후에 호출됩니다. 이 때 바뀐 후의 값이 파라미터로 전달됩니다.
class Person {
    var name: String {
        willSet {
            print("name will change from \(name) to \(newValue)")
        }
        didSet {
            print("name did change from \(oldValue) to \(name)")
        }
    }
    
    init(name: String) {
        self.name = name
    }
}

var person = Person(name: "John")
person.name = "Mike"

// 출력 결과:
// name will change from John to Mike

상속

상속을 하게 되면 부모 클래스에서 정의한 프로퍼티, 메서드, 이니셜라이저 등을 자식 클래스에서도 사용할 수 있습니다.

상속을 받기 위해서는 자식 클래스의 클래스 선언부에 부모 클래스 이름을 써주면 됩니다.

class Parent {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func sayHello() {
        print("Hello, I'm \(name)")
    }
    
    //재정의 불가 타입 메서드
      final func move() {
        print("Move!")
    }
    
}

class Child: Parent {
    var age: Int
    
    init(name: String, age: Int) {
        self.age = age
        super.init(name: name) // 부모 클래스의 이니셜라이저 호출
    }
    
    override func sayHello() {
        print("Hi, I'm \(name) and I'm \(age) years old")
    }
}

let parent = Parent(name: "John")
let child = Child(name: "Mike", age: 10)

parent.sayHello() // "Hello, I'm John"
child.sayHello()  // "Hi, I'm Mike and I'm 10 years old"

인스턴스의 생성과 소멸

인스턴스 초기화는 클래스, 구조체, 열거형에서 가능합니다. 초기화는 새로운 인스턴스가 메모리에 할당되고, 해당 인스턴스의 프로퍼티에 초기값을 설정하는 작업입니다.

클래스와 구조체는 생성자(constructor)를 사용하여 인스턴스 초기화를 수행합니다. 생성자는 인스턴스 생성과 동시에 자동으로 호출되며, 인스턴스가 필요한 초기값을 설정하는 역할을 합니다.

class Person {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

let person = Person(name: "John", age: 20)

 

인스턴스의 메모리는 해당 인스턴스가 더 이상 필요하지 않을 때 해제됩니다. 클래스 인스턴스의 경우, 인스턴스가 소멸되기 전에 할당된 자원을 정리하고 메모리에서 해제되는 과정을 수행할 수 있습니다. 이를 위해 deinit 메서드를 사용할 수 있습니다.

class Person {
    var name: String

    init(name: String) {
        self.name = name
    }

    deinit {
        print("\(name) is being deallocated.")
    }
}

var person: Person? = Person(name: "John")
person = nil // prints "John is being deallocated."

옵셔널체이닝과 nil 병합 연산자

옵셔널 체이닝은 옵셔널 값에 대해서 안전하게 접근할 수 있도록 하는 방법 중 하나입니다. 옵셔널 체이닝을 사용하면 옵셔널 값이 nil일 경우 강제 언래핑을 하지 않고 바로 nil을 반환합니다.

예시로, 아래 코드에서 person 변수는 Person 타입의 옵셔널 값입니다. 이 때 person 변수가 nil이라면 nil이 반환됩니다. 그렇지 않으면 person의 job 속성에 접근합니다. 이 때 job 속성도 옵셔널 값이므로 옵셔널 체이닝으로 nil을 반환하거나 실제 값에 안전하게 접근합니다.

struct Person {
    var name: String
    var job: String?
}

var person: Person?
person = Person(name: "John", job: "Developer")

let job = person?.job
print(job) // Optional("Developer")

nil 병합 연산자는 옵셔널 값이 nil일 경우, 기본값을 반환하는 역할을 합니다. 예를 들어, job 변수에 값이 있다면 그 값을 사용하고, 값이 없으면 "Unemployed" 문자열을 사용하도록 할 수 있습니다.

let job = person?.job ?? "Unemployed"
print(job) // Developer

타입캐스팅

[ 업캐스팅과 다운캐스팅 ]

// 업캐스팅과 다운캐스팅 예시
class Vehicle {
    func drive() {
        print("Driving!")
    }
}

class Car: Vehicle {
    override func drive() {
        print("Driving a car!")
    }

    func honk() {
        print("Honk!")
    }
}

class Bicycle: Vehicle {
    override func drive() {
        print("Riding a bicycle!")
    }

    func ringBell() {
        print("Ring ring!")
    }
}

let vehicle1: Vehicle = Car() // 업캐스팅
let vehicle2: Vehicle = Bicycle() // 업캐스팅

vehicle1.drive() // "Driving a car!" 출력
//vehicle1.honk() // 에러 발생 - Vehicle 클래스는 honk 메서드를 가지고 있지 않음

vehicle2.drive() // "Riding a bicycle!" 출력
//vehicle2.ringBell() // 에러 발생 - Vehicle 클래스는 ringBell 메서드를 가지고 있지 않음

if let car = vehicle1 as? Car {
    car.honk() // 가능 - 다운캐스팅 수행
} else {
    print("Not a car!")
}

if let bicycle = vehicle2 as? Bicycle {
    bicycle.ringBell() // 가능 - 다운캐스팅 수행
} else {
    print("Not a bicycle!")
}

let vehicle3: Vehicle = Vehicle()
let car2 = vehicle3 as? Car // nil 반환 - 다운캐스팅 실패
//let car2 = vehicle3 as! Car // 런타임 에러 발생 - 다운캐스팅 실패

assertguard

assert와 guard는 Swift에서 오류 처리를 위한 두 가지 주요 방법입니다.

[ assert ]

assert는 디버깅을 위해 사용됩니다. 특정 조건이 충족되지 않을 경우 런타임 오류를 발생시키며, 이를 통해 코드의 버그를 찾을 수 있습니다. assert는 다음과 같이 사용됩니다.

assert(조건, "오류 메시지")

여기서 조건은 참/거짓 값을 반환하는 표현식입니다. 만약 조건이 거짓이라면, 런타임 오류가 발생하며 "오류 메시지"를 출력합니다. assert는 보통 디버깅 모드에서만 동작하며, 릴리스 모드에서는 무시됩니다.

[ guard ]

guard는 조건이 충족되지 않았을 때 실행할 코드를 작성하는 데 사용됩니다. 일반적으로 guard는 함수나 메소드의 시작 부분에서 사용되며, 조건이 충족되지 않으면 빠른 종료(early exit)를 수행하여 코드의 중복을 줄이고 가독성을 높이는 데에 도움을 줍니다. guard는 다음과 같이 사용됩니다.

guard 조건 else {
    // 조건이 거짓일 때 실행할 코드
    return // 또는 break, continue 등의 제어문을 사용하여 빠른 종료
}
// 조건이 참일 때 실행할 코드

여기서 조건은 참/거짓 값을 반환하는 표현식입니다. 만약 조건이 거짓이라면, else 블록의 코드가 실행됩니다. 이때 빠른 종료를 수행하여 함수나 메소드의 실행을 중단할 수 있습니다. 조건이 참일 때는 else 블록을 건너뛰고 다음 코드를 실행합니다.

다음은 assert와 guard의 예시 코드입니다.

// assert 예시
let x = 10
assert(x > 0, "x는 0보다 커야 합니다.") // 통과

let y = -1
assert(y > 0, "y는 0보다 커야 합니다.") // 오류 발생

// guard 예시
func process(name: String?, age: Int?) {
    guard let name = name, let age = age, age >= 18 else {
        print("유효하지 않은 입력입니다.")
        return
    }
    print("\(name) (\(age)세) 님의 처리를 진행합니다.")
}

process(name: "홍길동", age: 20) // "홍길동 (20세) 님의 처리를 진행합니다." 출력됨
process(name: "John", age: 16) // "유효하지 않은 입력입니다." 출력됨
process(name: nil, age: 30) // "유효하지 않은 입력입니다." 출력됨

프로토콜

Swift에서 프로토콜(protocol)은 메소드, 속성, 이니셜라이저 등의 요구사항을 정의하는 추상적인 타입입니다. 프로토콜을 사용하면 다양한 형식에서 동일한 동작을 보장하며, 코드 재사용성과 유연성을 높일 수 있습니다.

프로토콜은 인터페이스와 비슷한 역할을 합니다. 클래스, 구조체, 열거형 등의 타입은 프로토콜을 채택하고, 프로토콜에서 요구하는 요구사항을 구현함으로써 해당 프로토콜을 준수할 수 있습니다. 이러한 방식으로 프로토콜은 더 큰 규모의 소프트웨어 시스템에서의 모듈성과 유연성을 제공합니다.

protocol Vehicle {
    var speed: Double { get set }
    var maxSpeed: Double { get }
    func accelerate()
}

class Car: Vehicle {
    var speed: Double = 0
    let maxSpeed: Double = 200

    func accelerate() {
        speed += 10
        if speed > maxSpeed {
            speed = maxSpeed
        }
    }
}

class Bicycle: Vehicle {
    var speed: Double = 0
    let maxSpeed: Double = 30

    func accelerate() {
        speed += 5
        if speed > maxSpeed {
            speed = maxSpeed
        }
    }
}

class Driver {
    var name: String
    var vehicle: Vehicle

    init(name: String, vehicle: Vehicle) {
        self.name = name
        self.vehicle = vehicle
    }

    func drive() {
        print("\(name) is driving a \(type(of: vehicle)) at \(vehicle.speed) km/h")
    }

    func speedUp() {
        vehicle.accelerate()
    }
}

let car = Car()
let bicycle = Bicycle()
let driver1 = Driver(name: "John", vehicle: car)
let driver2 = Driver(name: "Jane", vehicle: bicycle)

driver1.speedUp()
driver1.speedUp()
driver1.speedUp()
driver1.drive() // "John is driving a Car at 30 km/h" 출력

driver2.speedUp()
driver2.speedUp()
driver2.speedUp()
driver2.drive() // "Jane is driving a Bicycle at 15 km/h" 출력

extention

Swift에서 extension은 기존 클래스, 구조체, 열거형 등의 기능을 확장하는 데 사용됩니다. extension을 사용하면 원래의 코드를 수정하지 않고도 새로운 기능을 추가할 수 있으며, 코드의 가독성과 유지보수성을 높일 수 있습니다.

extension TypeName {
    // 추가할 기능 구현
}

여기서 TypeName은 확장할 대상의 타입 이름입니다. TypeName에는 클래스, 구조체, 열거형 등의 타입이 올 수 있습니다. extension 내부에서는 기존 타입에 없는 새로운 프로퍼티, 메소드, 초기화자 등을 추가할 수 있습니다.

예를 들어, 다음과 같이 String 타입에 문자열을 반대로 뒤집는 기능을 추가할 수 있습니다.

extension String {
    func reversed() -> String {
        return String(self.reversed())
    }
}

이제 String 타입의 모든 인스턴스에서 reversed() 메소드를 호출하여 문자열을 뒤집을 수 있습니다.

let str = "Hello, World!"
print(str.reversed()) // !dlroW ,olleH 출력됨

오류처리방법 throw 와 do-catch

[ throw ]

Swift에서 오류를 던지려면, Error 프로토콜을 준수하는 열거형을 정의하고 해당 오류 케이스를 정의해야 합니다. 이후 throw 키워드를 사용하여 오류를 던질 수 있습니다.

예를 들어, 다음과 같이 나눗셈 함수에서 0으로 나누려고 할 때 발생하는 오류를 처리할 수 있습니다.

enum DivisionError: Error {
    case divideByZero
}

func divide(_ dividend: Int, by divisor: Int) throws -> Int {
    guard divisor != 0 else {
        throw DivisionError.divideByZero
    }
    return dividend / divisor
}

do {
    let result = try divide(10, by: 0)
    print(result)
} catch DivisionError.divideByZero {
    print("Cannot divide by zero")
}

위 코드에서는 DivisionError라는 열거형을 정의하여 나누기 오류를 처리합니다.  divide 함수에서는 guard문을 사용하여 0으로 나누는 경우 DivisionError.divideByZero 오류를 던집니다. try 키워드를 사용하여 함수를 호출하면, 함수에서 오류가 발생할 경우 catch 블록에서 해당 오류를 처리할 수 있습니다.

[ do-catch ]

let input = "1234"
if let number = Int(input) {
    print(number)
} else {
    print("Invalid input")
}

Int(input)에서 오류가 발생할 가능성이 있기 때문에, 이 코드를 do-catch 구문으로 감싸서 오류를 처리할 수 있습니다

고차함수

map: 컨테이너 내의 각 요소에 대해 함수를 적용한 후 새로운 컨테이너를 반환합니다.

let numbers = [1, 2, 3, 4, 5]
let mappedNumbers = numbers.map { $0 * 2 } // [2, 4, 6, 8, 10]

filter: 주어진 함수의 조건을 만족하는 요소로 이루어진 새로운 컨테이너를 반환합니다.

let numbers = [1, 2, 3, 4, 5]
let filteredNumbers = numbers.filter { $0 % 2 == 0 } // [2, 4]

reduce: 컨테이너 내의 요소들을 결합하여 단일 값을 만듭니다.

let numbers = [1, 2, 3, 4, 5]
let reducedNumber = numbers.reduce(0, +) // 15

sort: 컨테이너 내의 요소들을 정렬합니다.

let numbers = [5, 2, 1, 4, 3]
let sortedNumbers = numbers.sorted() // [1, 2, 3, 4, 5]

flatMap: 컨테이너 내의 각 요소에 대해 함수를 적용하고 nil 값을 제거한 후 새로운 컨테이너를 반환합니다.

let numbers = [[1, 2], [3, nil], [4, 5]]
let flatMappedNumbers = numbers.flatMap { $0 } // [1, 2, 3, 4, 5]