[swift2] Swift Guard

Swift 2015. 9. 11. 21:33

Guard 문을 이해하기 쉽게 설명하고 있는 글을 발견하여 소개합니다. 원문을 조금 의역하고 예제를 수정했습니다.

원문: Swift's Guard

Swift2.0에 추가된 guard 구문은 멋집니다. 실 세계에서 가드가 하는 것과 같은 유사한 역할을 하지요. 학교 경비는 학교에 소속된 학생들만 교문을 입장하도록 허용합니다. 소속되지 않는 학생이 들어오려고 하면, 저지하고 되돌려 보냅니다. guard 문은 뒤따르는 조건이 충족되지 않을 경우, 로직실행을 멈추고 함수를 반환합니다.

guard 문의 사용법에 대해 몇가지 알아보겠습니다.


1. 함수안에서 Guard

  • 함수 안에서 guard 문은 올바른 입력에 대해서만 함수의 실행을 허용합니다.
func guardInFunction(string: String?) {
    guard string != nil else { return }
    print(string!, "second param", separator: "  ", terminator: "\n")
}

guardInFunction("test")

guardInFunction(nil)

위 함수는 nil 입력을 가드합니다. 만일 입력이 nil이면 guard문은 else 절을 수행하고 다른 구문이 수행되기 전에 함수를 return합니다.


2. 옵셔널 바인딩(Optional Binding)

옵셔널 바인딩을 guard문과 조합하여 사용하면, 이전의 코드를 다음과 같이 재작성 할 수 있습니다.

func guardWithOptionalBinding(string: String?) {
    guard let letString = string else { return }
    print(letString, "second param", separator: "  ", terminator: "\n")
}

guardWithOptionalBinding("test")

인자 string이 nil이면 else 절을 수행하고 그렇지 않으면 함수는 정상흐름을 계속하고, string을 출력합니다. if-let 문법을 사용한 옵셔널 바인딩을 사용해봤다면 무엇인가 다르다는 것을 느낄수 있을 것입니다. 옵셔널 바인딩된 상수를 조건문 범위 바깥에서도 사용할 수 있습니다. if-let의 경우에는 if문 조건을 만족하는 경우 if 블럭 내부에서만 사용할 수 있습니다.

guard 문의 경우에 조건이 true이면, 상수의 사용범위는 함수의 나머지 영역이 됩니다. guard문 이후 어디에서라도 사용할수 있습니다.


3. 복합 옵셔널 바인딩(Compound Optional Binding)

다음 예제처럼 복합해서 옵셔널 바인딩을 사용할 수 있습니다. 모든 파라미터가 nil이 아닐경우에 조건을 충족하고 출력문을 수행할 수 있습니다.

func guardWithCompoundOptionalBinding(firstName: String?, lastName:String?, dateOfBirth:String?, country:String? ) {
    guard let fname = firstName, let lname = lastName, let dob = dateOfBirth, let cnt = country else { return }

    print("\(fname) \(lname) \(dob) \(cnt)", separator: "", terminator: "\n")
}

guardWithCompoundOptionalBinding("sungbae", lastName: "kim", dateOfBirth: "", country: "korea")


4. 제어흐름 이동(Transferring Control)

위 예제에서 주목할 만한 것은, 조건이 충족되지 않으면 함수로부터 반환되는 것입니다. 이것은 guard 문이 조건을 만족하지 않으면 제어흐름의 이동시키는 구문을 가져야 하기 때문입니다. 위 예제에서 우리는 return문을 사용했습니다. 루프의 경우에는 continue와 break를 사용할 수 있습니다. 또한 우리는 error를 throw 할 수 있습니다. 예제를 보겠습니다.

func guardTransferringControl() {
    var strings:Array<String> = ["Safe", "Coding", "why", "using", "guard", "statement"]

    for string in strings {
        guard string != "why" else {
            continue
        }
        print("string", separator: "", terminator: "\n")
    }
}

guardTransferringControl()

조건문을 만족하지 않으면, else 구문에서 제어흐름을 이동시키기 위해 continue를 사용했습니다.

이것이 guard 문이 하는 일입니다.

[swift2] Swift에서 에러 처리 (Error Handling)

Swift 2015. 7. 10. 00:55


(본 포스트는 The Swift Programming Language - Swift 2 Prerelease. 의 Error Handling 장을 번역한 문서입니다.)

에러처리는 프로그램에서 에러조건에 대응하고 회복하는 과정입니다. Swift는 런타임에 회복 가능한 에러를 던지고, 잡고,
전파하고, 조작하는 first-class(1급) support를 제공합니다. 몇몇 함수와 메소드는 항상 완전한 실행과 유용한 정보를
출력한다고 보장할 수 없습니다. Optional은 값의 부재를 표현하기 위해 사용되지만 함수가 실패했을때 실패원인을 이해하고
거기에 맞춰 코드를 대응하는게 더 유용할때가 있습니다.

디스크의 파일로부터 데이터를 읽고 처리하는 작업을 예로 들어보겠습니다.
경로에 파일이 없거나 읽기 권한이 없거나, 파일이 호환되지 않는 포맷으로 인코딩 되어있는 것 등 여러가지 이유로 작업이
실패할 수 있습니다. 이러한 상황의 차이를 구별하고 필요하다면 사용자와 커뮤니케이션해야 에러를 해결하고 회복할수 있습니다.


1. 에러의 표현 (Representing Errors)

스위프트에서 에러는 ErrorType 프로토콜을 구현하는 타입값으로 표현됩니다.
Swift enumerations는 특히 관련있는 에러조건(에러의 원인에 대한 정보를 연관값으로 가진채) 그룹을 모델링하는데
적합합니다. 이러한 이유로, Swift enumerations는 ErrorType을 자동으로 구현하고, 컴파일러에 의해 생성된 구현체를 갖습니다.

자판기의 에러조건을 표현하기 위한 예입니다.

enum VendingMachineError: ErrorType {
    case InvalidSelection
    case InsufficientFunds(required: Double)
    case OutOfStock

}

이 예제에서 자판기는 다음과 같은 이유로 실패할수 있습니다.

  • 1. 요청한 아이템이 유효한 선택이 아닌 경우. InvalidSelection 으로 표현.
  • 2. 요청한 아이템 비용이 입금한 비용보다 더 큰 경우. InsufficentFunds로 표현.
    연관된 Double 값은 트랜잭션을 완료하기 위해 필요한 추가요금.
  • 3. 요청한 아이템의 재고가 없는 경우. OutOfStock으로 표현.


2. 에러 던지기 (Throwing Errors)

함수 또는 메서드가 에러를 던질수 있다는것을 나타내기 위해, 선언부의 파라미터 다음에 throws 키워드를 사용합니다.
만일 return 타입이 있으면, throws 키워드는 리턴 화살표 앞에 표기합니다. 함수, 메소드, 클로져는 명시적으로 표시하지
않으면 에러를 던질 수 없습니다.

func canThrowErrors() throws -> String

func canThrowErrors() -> String

throws를 선언한 함수의 몸체 어느 시점에서도 throw 문을 사용하여 에러를 던질수 있습니다.
아래 예제에서 vend(itemNamed:) 함수는 요청한 아이템의 재고가 없거나, 금액이 부족할 때 에러를 던집니다.

stuct Item {
    var price:Double
    var count:Int
}

var inventory = [
    "Candy Bar": Item(price:1.25, count:7),
    "Chips": Item(price: 1.00, count:4),
    "Pretzels": Item(price: 0.75, count:11)
]

var amountDeposited = 1.00  

func vend(itemNamed name:String) throws {
    guard var item = inventory[name] else {
        throw VendingMachineError.InvalidSelection
    }

    guard item.count > 0 else {
        throw VendingMachineError.OutOfStock
    }

    if amountDeposited >= item.price {
        amountDeposited -= item.price
        --item.count
        inventory[name] = item
    } else {
        let amountRequired = item.price - amountDeposited
        throw VendingMachineError.InsufficentFunds(required: amountRequired)
    }
}

먼저 quard 문은 item 상수와 count 변수를 inventory의 대응하는 값과 바인드하기 위해 사용되었습니다.
item 이 inventory에 없으면 InvalidSelection 에러를 던집니다. 그 다음으로 요청한 item의 count를 체크해
가용성을 결정합니다. count가 0보다 같거나 작으면 OutOfStock 에러를 던집니다. 마지막으로 요청한 아이템의 가격과
현재 입금된 금액을 비교합니다. 입금된 금액이 아이템의 가격보다 크면, 입금액에서 가격만큼 차감되고, inventory에 있는
재고량이 감소합니다. 그리고 함수는 요청한 아이템을 반환합니다. 그렇지 않으면 초과된 금액이 연관값으로 전달되어 InsufficentFunds 에러를 던져집니다. throw 문은 즉시 프로그램의 제어를 이동시키기 때문에 아이템은 구매를 위한
모든 요구조건이 만족 될때만 판매됩니다.
throwing 함수를 호출할 때, 호출부 앞에 try를 붙힐 수 있습니다. 이 키워드는 함수가 에러를 던질수 있고,
try 이후 코드라인들이 실행되지 않을 수 있음을 암시합니다.

let favoriteSnacks = [
    "Alice": "Chips",
    "Bob": "Licorice",
    "Eve": "Pretzels"
]

func buyFavoriteSnack(person:String) throws {
    // a ?? b 구문은 a가 nil이 아니면 a를, nil이면 b를 반환. 
    let snackName = favoriteSnacks[person] ?? "Candy Bar"

    try vend(itemNamed: snackName)
}

buyFavoriteSnack() 함수는 person이 가장 좋아하는 snack을 찾고, 그것을 구매하려고 시도합니다.
좋아하는 snack이 리스트에 없다면 candy bar를 사려고 시도합니다. 에러를 던질수도 있는 vend 함수가 호출되기
때문에 try 를 함수 앞에 사용했습니다. buyFavoriteSnack(_:) 함수는 throwing 함수이기 때문에, vend 함수
던지는 어떤 에러도 buyFavoriteSnack 함수가 호출되는 상위로 전파될수 있습니다..


3. 에러를 잡고 처리하기 (Catching and Handling Errors)

에러를 잡아 처리하기 위해 do-catch 문을 사용할 수 있습니다.

do {
    try function that throws
    ...
} catch pattern {
    statements
}

에러가 던져지면 catch 절에 의해 처리 될때까지 에러는 외부 스코프로 전파됩니다. catch 절은 catch 키워드 다음에
매칭할 에러패턴 그리고 실행할 구문집합으로 구성됩니다. switch 문처럼 컴파일러는 catch절이 모두 포괄되어 있는지
검사합니다. 그러한 결정이 이루어지면 에러는 처리됩니다. 해당 스코프에서는 에러를 처리하거나 또는 함수가 throws로
선언되어야 합니다. 에러가 처리됐다는 것을 보장하기 위해 모든 에러들과 매칭되는 패턴을 가진 catch절을 사용합니다.
catch절이 패턴을 명시하지 않으면 그 절은 로컬상수 error에 모든 에러를 매칭하여 바인딩합니다. 패턴 매칭에 관한 더
자세한 정보는 Patterns 장을 참고하세요.

do {

    try vend(itemNamed: "Candy Bar")

} catch VendingMachineError.InvalidSelection {

    print("Invalid Selection.")

} catch VendingMachineError.OutOfStock {

    print("Out of Stock.")

} catch VendingMachineError.InsufficientFunds(let amountRequired) {

    print("Insufficient funds. Please insert an additions $\(amountRequired).")

}

위 예제에서, vend(itemNamed:) 함수는 에러를 던질 가능성이 있기 때문에 try 표현식 안에 표현되었습니다. 에러가 던져지면, 실행흐름은 즉시, 전파를 계속할지 결정되는 catch 절로 이동합니다. 만일 에러가 던져지지 않았다면,
vend(itemNamed:)의 반환값은 snack에 할당되고 do문의 남아있는 구문들이 실행됩니다.


4. 에러 전파 비활성화 (Disabling Error Propagation)

사실상 런타임에 에러를 던지지 않는 throwing 함수나 메서드를 알수 있는 경우가 있습니다. 이러한 경우에는
보통 사용하는 try 표현 대신에 try!(forced-try) 표현을 사용하여 throwing 함수나 메서드를 호출할 수 있습니다.
try!를 사용하여 throwing 함수나 메서드를 호출하면 error 전파가 비활성화 되고, 에러를 던지지 않는 런타임
assertion 안에서의 호출로 랩핑합니다. 만일 실제로 에러가 thrown 되면, 런타임 에러를 만나게 될것입니다.


5. 클린업 액션 지정하기 (Specifying Clearn-Up Actions)

defer 구문을 사용해서, 코드 실행흐름이 현재 코드블럭을 떠나기 직전에, 일련의 코드 집합을 실행할 수 있습니다.
에러의 발생유무와는 상관없이 반드시 실행되야할 클린업 작업을 처리할수 있습니다. 예를 들면 열려진 파일 식별자를
닫거나, 수동으로 할당된 메모리를 해제하는 작업이 될 수 있습니다.

defer 문은 현재 scope이 끝날 때까지 실행을 연기합니다. defer 키워드와 나중에 실행할 코드 구문들로 구성됩니다.
연기된 구문들은 제어흐름을 벗어나게 하는 break 또는 return, 에러를 던지는 것과 같은 코드를 포함해서는 안됩니다.
연기된 액션들은 기술된 순서의 역순으로 실행됩니다. 즉 처음 defer 문에 있는 코드는 두 번째 defer 문의 코드 다음에
실행됩니다.

func processFile(filename:String) throws {
    if exists(filename) {
        let file = open(filename)
        defer {
            close(file)
        }

        while let = try file.readline() {
            // work with the file
        }
        // close(file) is called here, at the end of the scope.
    }
}

위 예제에서는 open() 함수가 대응하는 close() 함수 호출을 보장하도록 defer문을 사용했습니다. 이 호출은
에러가 던져졌는지 유무와 상관없이 실행됩니다.




[swift2] New in Swift2.0

Swift 2015. 6. 19. 23:02


올 가을 Swift2.0 정식버전이 발표된다고 합니다. Xcode7 beta Release Notes를 통해 그 변경사항을 잠시 알아볼까요?
원문: Xcode7 beta Release Notes


Swift Language Features

1. 에러처리

에러를 throw, catch 그리고 관리하는 함수를 생성할 수 있습니다. file-not-found 에러 혹은 네트워크 타임아웃
같은 회복가능한 에러를 처리하고, 의도를 명확하게 드러낼 수 있습니다. Swift 에러 처리는 NSError와 상호동작합니다.

2.가용성 체크

선택된 deployement target 보다 더 신버전의 운영체제에서 지원하는 API를 호출하면
Swift는 컴파일 타임에 에러를 보고합니다. 런타임에 영속적으로 사용 불가능한 API를 검사하려면,
if 또는 guard 문에서 새롭게 추가된 available()조건을 사용하세요.

if #available(iOS 8.0, OSX 10.10, *) {
    // Use Handoff APIs when available.
    let activity = NSUserActivity(activityType:"com.example.ShoppingList.view")
     activity.becomeCurrent()
} else {
    // Fall back when Handoff APIs not available.
} 

3. @available() 속성

함수 선언부에 @available() 속성을 사용하여 가용성 정보를 명시할 수 있습니다.

// iOS 8.0 이상, OSX 10.10 이상, 다른 플랫폼의 모든 버전에서 사용가능합니다.
@available(iOS 8.0, OSX 10.10, *)
func startUserActivity() -> NSUserActivity {
    ...
}

4. 프로토콜 Extensions

프로토콜 타입을 위한 Extensions을 작성할 수 있습니다. 특정 프로토콜를 따르는 어떠한 타입에도 메서드와 속성을 추가
할 수 있게되어 더 많은 코드를 재사용 할수 있습니다. 유연한 인터페이스 원칙에 따라 호출 측의 dot 메서드 문법을 더 자연스럽게 하고,
제네릭 코드 정의를 더 단순하게 합니다.

5. 프로토콜 디폴트 구현

프로토콜은 프로토콜 extension 명세에 따른 기본 구현체를 가질 수 있습니다. mixin, trait 같은 패턴구현이 가능해집니다.

func xyz() throws {

6. defer 구문

defer 구문은 scope를 빠져나올때 클린업 코드를 실행합니다. 특히 새 에러처리 모델과 결합하여 사용하면 유용합니다.

let f = fopen("x.txt", "r")
defer { fclose(f) } 
try foo(f)      // 에러가 발생하면 f가 닫힘

let f2 = fopen("y.txt", "r") 
defer { fclose(f2) }
try bar(f, f2)   // 에러가 발생하면 f2가 닫히고, f가 닫힘
} 
// 정상흐름에선 f2가 닫히고, 그다음 f가 닫힘

7. guard 구문

scope를 조기에 빠져나갈 수 있게합니다.
(의도하지 않는 조건에선 조기에 scope를 빠져나가게 하여, 다음 코드를 신경쓰지 않도록 해준다는 내용입니다.)

guard let z = bar() else { return }
use(z)

scope를 빠져나가기 위해 else 블럭이 필요합니다. (예를 들면 return,throw,break,continue와 함께)

8. 패턴매칭 향상

switch/case 패턴매칭이 if/case, while/case, guard/case, 그리고 for-in/case를 포함하여
많은 새로운 조건 제어구문에서 사용가능합니다. for/in 문은 where 절을 가질수 있습니다.

9. do 구문

do 구문을 사용하여 새로운 scope를 정의할수 있습니다.

do {
    //new scope
 do {
     //another scope
 }
}

10. 테스트성 향상

Swift2.0 프레임워크와 앱들의 테스트(코드)들은 내부 루틴을 public 으로 만들지 않고 작성되었습니다.
테스트 소스코드에서 모든 것을 public으로 만들어 내부 루틴을 사용할 수 있게 만들어주는 다음 구문을 사용하세요.

@testable import { ModuleName }  

앱과 프레임워크 타겟은 "Enable Testability" 빌드설정을 활성화하고 컴파일 해야합니다.
Enable Testability 설정은 앱과 프레임워크의 모든 내부 심볼들까지 exporting 할지 결정하여 최적화를 막을 수 있기때문에
Debug configuration 에서만 사용되야 합니다.

11. C 함수포인터 Native 지원

함수포인터를 인자로 갖는 C함수는 clusers 또는 글로벌 함수를 사용하여 호출할 수 있습니다.

// 표준C qsort
var array = [3, 14, 15, 9, 2, 6, 5]
qsort(&array, array.count, sizeofValue(array[0])) { a, b in
    return Int32(UnsafePointer<Int>(a).memory - UnsafePointer<Int>(b).memory)
}
print(array)

12. 진단기능 향상

var 대신 let 사용을 권장하기 위한 새로운 warning과 unused variables warning이 추가되었습니다. invalid mutation warning 진다는 보다 더 정밀해졌으며, unreachable switch cases 절도 warning을
발생시킵니다.

13.enum 다중 제네릭 연관값을 지원

enum Either<T, U> {
    case Left(T), Right(U)
}

13. enum 프린팅 구문 - enum case와 playload 출력 (가능한 경우에만)

@objc enum과 다중 payload를 갖는 enum 에는 적용되지 않습니다.

14. 제네릭 타입의 public extension 허용

    public extension Array { … }

15. 제네릭 클래스로부터 non 제네릭 클래스 상속이 가능

16. swift 문자열 접합연산 - 컴파일 타임에 최적화를 보장

17. 실패 가능한 Convenience 생성자에서 self.init 호출전에 nil 리턴 허용

여전히 Designated 생성자들은 nil을 리턴하기 전에 모든 저장속성들을 초기화 해야합니다.

18. 중첩함수는 재귀적으로 자기자신과 다른 중첩함수들을 참조 가능

19. if 문에 라벨링 가능

라벨이 있는 break 문은 매칭되는 if문 밖으로 jump 하는데 사용됩니다.
라벨이 없는 break 문은 if문을 종료시키지 못하지만, 루프문과 스위치 구문은 종료시킵니다.

20. x? 패턴

.Some(x) 와 동일한 의미로 optional에 붙어 x? 패턴을 사용할수 있습니다.

21. 인스턴스 멤버에 @objc 키워드가 붙는 것을 방지해주는 @nonobjc 속성 추가

(Swift 코드를 Objective-C에 노출시킬 때, @nonobjc 속성이 붙은 속성과 메소드는 노출되지 않습니다.)

22. 표준 라이브러리에 readLine() 추가





'Swift' 카테고리의 다른 글

[swift] 문자열 Indexing과 Slicing  (0) 2015.06.23
[swift] Map, Filter, Reduce  (0) 2015.06.22
[swift2] New in Swift2.0  (0) 2015.06.19
[swift] Apple Swift Blog  (0) 2015.06.10
[swift] Swift 기초 - 4  (0) 2014.10.29
[swift] Swift 기초 - 3  (1) 2014.10.29