Swift 공부 6

Paul Mo·2023년 10월 26일
0

Protocols and Extensions

프로토콜을 선언하려면 'protocol'을 사용한다.

protocol ExampleProtocol {
     var simpleDescription: String { get }
     mutating func adjust()
}

class, enum 및 structure 모두 프로토콜을 채택할 수 있다.

class SimpleClass: ExampleProtocol {
     var simpleDescription: String = "A very simple class."
     var anotherProperty: Int = 69105
     func adjust() {
          simpleDescription += "  Now 100% adjusted."
     }
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription


struct SimpleStructure: ExampleProtocol {
     var simpleDescription: String = "A simple structure"
     mutating func adjust() {
          simpleDescription += " (adjusted)"
     }
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription

'SimpleStructure'의 선언에서 'mutating' 키워드가 있다. mutating은 구조체를 수정하는 메서드를 표시하는데, 'SimpleClass'의 선언은 클래스의 메서드는 항상 클래스를 수정할 수 있기 때문에 'mutating'으로 표시할 필요가 없다.

'extension'을 사용하여 기존 type에 새로운 기능(메서드 및 계산 속성)을 추가할 수 있다. extension을 사용하여 다른 곳에서 선언된 type, 라이브러리 또는 프레임워크에서 가져온 type에 프로토콜 준수를 추가할 수도 있다.

extension Int: ExampleProtocol {
    var simpleDescription: String {
        return "The number \(self)"
    }
    mutating func adjust() {
        self += 42
    }
 }
print(7.simpleDescription)
// Prints "The number 7"

protocol 이름은 다른 named type과 마찬가지로 사용할 수 있다. 예를 들어, 서로 다른 type이지만 모두 단일 프로토콜을 준수하는 객체 컬렉션을 만들려면 protocol 이름을 사용할 수 있다. 하지만 protocol type의 박스 된 값을 사용할 때 프로토콜 정의 외부의 메서드는 사용할 수 없다.

let protocolValue: any ExampleProtocol = a
print(protocolValue.simpleDescription)
// Prints "A very simple class.  Now 100% adjusted."
// print(protocolValue.anotherProperty)  // Uncomment to see the error

변수 protocolValue는 실행 시간에 SimpleClass의 type을 가지더라도 컴파일러는 그것을 ExampleProtocol의 주어진 type으로 처리한다. 이것은 class가 프로토콜 준수 외에도 구현하는 메서드나 속성에 실수로 액세스 할 수 없음을 의미한다. 프로토콜을 준수하는 범위 내에서만 메서드와 속성에 접근할 수 있다.

Error Handling

'Error' 프로토콜을 채택하는 모든 type을 사용하여 오류를 나타낼 수 있다.

enum PrinterError: Error {
    case outOfPaper
    case noToner
    case onFire
}

오류를 던지려면 'throw'를 사용하고, 오류를 던질 수 있는 함수를 표시하기 위해 'throws'를 사용한다. 함수에서 오류를 던지면 함수가 즉시 반환되고 함수를 호출한 코드에서 오류를 처리한다.

func send(job: Int, toPrinter printerName: String) throws -> String {
    if printerName == "Never Has Toner" {
        throw PrinterError.noToner
    }
    return "Job sent"
}

Swift에서는 오류를 처리하는 여러 가지 방법이 있다. 하나는 do-catch를 사용하는 방법이다. do 블록 내에서 코드 앞에 try를 작성하여 오류를 던질 수 있는 코드를 표시한다. catch 블록 내에서 오류는 자동으로 error라는 이름이 부여되지만 다른 이름을 지정할 수도 있다. Swift의 do-catch는 Javascript의 try-catch와 같은 방법으로 이해하면 될 것 같다.

do {
    let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
    print(printerResponse)
} catch {
    print(error)
}
// Prints "Job sent"

특정 오류를 처리하는 여러 개의 catch 블록을 사용할 수 있다. switch에서 case 뒤와 마찬가지로 catch 뒤에 패턴을 작성한다.

do {
    let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
    print(printerResponse)
} catch PrinterError.onFire {
    print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
    print("Printer error: \(printerError).")
} catch {
    print(error)
}
// Prints "Job sent"

오류를 처리하는 또 다른 방법은 결과를 옵셔널로 변환하는 'try?'를 사용하는 것이다. 함수에서 오류를 던지면 특정 오류가 폐기되고 결과는 nil이 되는데, 그렇지 않으면 결과는 함수가 반환한 값을 포함하는 옵션이 된다.

let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")

'func' 내부에 'defer'를 사용하여 함수가 반환되기 직전에 실행되는 코드 블록을 작성할 수도 있다. 이 코드는 함수가 오류를 던지든 던지지 않든 실행된다. 'defer'를 사용하여 설정(setup) 및 정리(cleanup) 코드를 서로 인접하게 작성할 수 있으며, 실행 시간이 다른 시점에 있더라도 실행해야 하는 경우에 유용하다.

ar fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]


func fridgeContains(_ food: String) -> Bool {
    fridgeIsOpen = true
    defer {
        fridgeIsOpen = false
    }


    let result = fridgeContent.contains(food)
    return result
}
if fridgeContains("banana") {
    print("Found a banana")
}
print(fridgeIsOpen)
// Prints "false"

Generics

<> 안에 이름을 작성하여 제네릭 함수 또는 type을 만들 수 있다.

func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
    var result: [Item] = []
    for _ in 0..<numberOfTimes {
         result.append(item)
    }
    return result
}
makeArray(repeating: "knock", numberOfTimes: 4)

함수 및 메서드의 제네릭 형식, class, enum 및 structure의 제네릭 형식을 만들 수 있다.

// Reimplement the Swift standard library's optional type
enum OptionalValue<Wrapped> {
    case none
    case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)

body 바로 앞에 'where'를 사용하여 요구 사항 목록을 지정할 수 있다. 예를 들어 type이 프로토콜을 구현하도록, 두 type이 동일하도록 또는 클래스가 특정 슈퍼 클래스를 가져야 하도록 요구할 수 있다.

func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
    where T.Element: Equatable, T.Element == U.Element
{
    for lhsItem in lhs {
        for rhsItem in rhs {
            if lhsItem == rhsItem {
                return true
            }
        }
    }
   return false
}
anyCommonElements([1, 2, 3], [3])

<T: Equatable>은 ... where T: Equatable과 동일한 코드다.


참조: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/guidedtour/#Protocols-and-Extensions

profile
프론트 엔드 개발자

0개의 댓글

관련 채용 정보