web-dev-qa-db-ja.com

Swift関数にネストされたクロージャからスロー

エラーをスローする関数があります。この関数にはinside a完了ハンドラからエラーをスローする必要があるクロージャ。それは可能ですか?

これが私のコードです。

enum CalendarEventError: ErrorType {
    case UnAuthorized
    case AccessDenied
    case Failed
}

func insertEventToDefaultCalendar(event :EKEvent) throws {
    let eventStore = EKEventStore()
    switch EKEventStore.authorizationStatusForEntityType(.Event) {
    case .Authorized:
        do {
            try insertEvent(eventStore, event: event)
        } catch {
            throw CalendarEventError.Failed
        }

    case .Denied:
        throw CalendarEventError.AccessDenied

    case .NotDetermined:
        eventStore.requestAccessToEntityType(EKEntityType.Event, completion: { (granted, error) -> Void in
            if granted {
                //insertEvent(eventStore)
            } else {
                //throw CalendarEventError.AccessDenied
            }
        })
    default:
    }
}
26
shannoga

スローするクロージャを定義すると、次のようになります。

enum MyError: ErrorType {
    case Failed
}

let closure = {
    throw MyError.Failed
}

次に、このクロージャーのタイプは() throws -> ()であり、パラメーターとしてこのクロージャーを受け取る関数は、同じパラメータータイプでなければなりません。

func myFunction(completion: () throws -> ()) {
}

この関数は、completionクロージャを同期的に呼び出すことができます。

func myFunction(completion: () throws -> ()) throws {
    completion() 
}

関数のシグネチャにthrowsキーワードを追加するか、try!を使用して補完を呼び出す必要があります。

func myFunction(completion: () throws -> ()) {
    try! completion() 
}

または非同期:

func myFunction(completion: () throws -> ()) {
    dispatch_async(dispatch_get_main_queue(), { try! completion() })
}

最後のケースでは、エラーをキャッチすることができません。

したがって、eventStore.requestAccessToEntityTypeメソッドのcompletionクロージャで、メソッド自体のシグネチャにthrowsがない場合、またはcompletionが非同期で呼び出される場合、throwこのクロージャーから。

エラーをスローする代わりにコールバックに渡す関数の次の実装をお勧めします。

func insertEventToDefaultCalendar(event: EKEvent, completion: CalendarEventError? -> ()) {
    let eventStore = EKEventStore()
    switch EKEventStore.authorizationStatusForEntityType(.Event) {
    case .Authorized:
        do {
            try insertEvent(eventStore, event: event)
        } catch {
            completion(CalendarEventError.Failed)
        }

    case .Denied:
        completion(CalendarEventError.AccessDenied)

    case .NotDetermined:
        eventStore.requestAccessToEntityType(EKEntityType.Event, completion: { (granted, error) -> Void in
            if granted {
                //insertEvent(eventStore)
            } else {
                completion(CalendarEventError.AccessDenied)
            }
        })
    default:
    }
}
19
mixel

スローは同期であるため、スローしたい非同期関数には、次のようなスローする内部クロージャーが必要です。

func insertEventToDefaultCalendar(event :EKEvent, completion: (() throws -> Void) -> Void) {
    let eventStore = EKEventStore()
    switch EKEventStore.authorizationStatusForEntityType(.Event) {
    case .Authorized:
        do {
            try insertEvent(eventStore, event: event)
            completion { /*Success*/ }
        } catch {
            completion { throw CalendarEventError.Failed }
        }

        case .Denied:
            completion { throw CalendarEventError.AccessDenied }

        case .NotDetermined:
            eventStore.requestAccessToEntityType(EKEntityType.Event, completion: { (granted, error) -> Void in
                if granted {
                    let _ = try? self.insertEvent(eventStore, event: event)
                    completion { /*Success*/ }
                } else {
                    completion { throw CalendarEventError.AccessDenied }
                }
        })
        default:
            break
    }
}

次に、呼び出しサイトで次のように使用します。

   insertEventToDefaultCalendar(EKEvent()) { response in
        do {
            try response()
            // Success
        }
        catch {
            // Error
            print(error)
        }
    }
7
Rafael Nobre

この場合、これは不可能です。その完了ハンドラはthrows(およびrethrowsを持つメソッド)で宣言する必要がありますが、これはできません。

これらすべてのスローは、Objective-CのNSError **の異なる表記法にすぎないことに注意してください(inoutエラーパラメータ)。 Objective-Cコールバックにはinoutパラメーターがないため、エラーを渡す方法はありません。

エラーを処理するには、別の方法を使用する必要があります。

一般に、Obj-CのNSError **またはSwiftのthrowsは、エラー処理が同期的に機能するため、非同期メソッドではうまく機能しません。

6
Sulthan

requestAccessToEntityTypeは、非同期で動作します。最終的に完了ハンドラが実行されると、関数はすでに返されています。したがって、提案されている方法でクロージャーからエラーをスローすることはできません。

承認部分がイベント挿入とは別に処理されるようにコードをリファクタリングし、承認ステータスが期待どおり/必要であることがわかっている場合にのみinsertEventToDefaultCalendarを呼び出す必要があります。

1つの関数ですべてを処理したい場合は、セマフォ(または同様の手法)を使用して、非同期コード部分が関数に対して同期的に動作するようにすることができます。

func insertEventToDefaultCalendar(event :EKEvent) throws {
    var accessGranted: Bool = false

    let eventStore = EKEventStore()
    switch EKEventStore.authorizationStatusForEntityType(.Event) {
    case .Authorized:
        accessGranted = true

    case .Denied, .Restricted:
        accessGranted = false

    case .NotDetermined:
        let semaphore = dispatch_semaphore_create(0)
        eventStore.requestAccessToEntityType(EKEntityType.Event, completion: { (granted, error) -> Void in
            accessGranted = granted
            dispatch_semaphore_signal(semaphore)
        })
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
    }

    if accessGranted {
        do {
            try insertEvent(eventStore, event: event)
        } catch {
            throw CalendarEventError.Failed
        }
    }
    else {
        throw CalendarEventError.AccessDenied
    }
}
0
iOSX

throwでは関数を作成できませんが、ステータスまたはエラーとともにclosureを返します。不明な場合は、コードをいくつか示します。

0
katleta3000