2017-08-29 1 views
4

Ist es möglich, einen Abschluss zu erzwingen? Genauso wie eine Funktion mit einem Rückgabewert immer zurückkehren MUSS, wäre es ace, wenn es eine Möglichkeit gäbe, eine Closure dazu zu bringen, die Syntax zu enthalten, die notwendig ist, um immer abgeschlossen zu werden.Schnellverschlüsse - erzwingen, dass ein Verschluss immer geschlossen wird

Zum Beispiel dieser Code nicht kompilieren, da die Funktion nicht immer einen Wert zurück:

func isTheEarthFlat(withUserIQ userIQ: Int) -> Bool { 
    if userIQ > 10 { 
     return false 
    } 
} 

In genau der gleichen Art und Weise, ich möchte eine Funktion mit einem Verschluss definieren, die kompiliert auch nicht, wenn die Schließung nie zurückkehrt. Zum Beispiel könnte der Code unten nie completionHandler zurück:

func isTheEarthFlat(withUserIQ userIQ: Int, completionHandler: (Bool) -> Void) { 
    if userIQ > 10 { 
     completionHandler(false) 
    } 
} 

Der obige Code kompiliert, aber ich frage mich, ob es ein Schlüsselwort ist, die erzwingt, dass der Verschluss in allen Fällen einen Abschluss-Handler sendet. Vielleicht hat es etwas mit der Void in der obigen Funktion zu tun?

+1

Nicht ein Sprachniveau, obwohl ich eine interessante Vorstellung davon, wie es erreichen könnte . Lassen Sie mich ein paar Sachen ausprobieren – Alexander

+0

Können Sie nicht einfach den Completion-Handler am Ende der Methode aufrufen? – Sweeper

+0

@Sweeper Die meisten unserer Completion-Handler werden für den Serverzugriff verwendet, sodass sie (1) alle aufgetretenen Fehler und (2) die Daten vom Server zurückgeben. Da ein Serverfehler nicht derselbe Fehler ist, den wir dem Benutzer zeigen wollen, haben wir oft eine Menge von Funktionen, die entscheiden, was der Fehler ist, und ebenso wollen wir vielleicht die vom Server zurückgegebenen Daten auf eine bestimmte Art und Weise manipulieren. Ich möchte nur, dass der Compiler sicherstellt, dass der Completion-Handler in allen Zweigen des Codes aufgerufen wird - genau so, wie ein Rückgabewert in einer normalen Funktion zurückgegeben werden muss. – theDuncs

Antwort

2

Nein, es gibt kein Sprachkonstrukt, das zu einem Compilerfehler führen würde, wenn Sie den Completion-Handler unter allen möglichen Bedingungen wie einer return-Anweisung vergessen (oder nicht benötigen).

Es ist eine interessante Idee, die eine nützliche Erweiterung der Sprache darstellen könnte. Vielleicht als required Schlüsselwort irgendwo in der Parameterdeklaration.

+0

Ich habe den Koolaid getrunken: https://Stackoverflow.com/a/45945054/3141234 – Alexander

2

Es gibt kein spezielles Schlüsselwort für das, was Sie wollen. Aber es ist ein interessanter Ansatz, den Sie in Betracht ziehen können, das wird nicht kompilieren:

func isTheEarthFlat(withUserIQ userIQ: Int, completionHandler: (Bool) -> Void) { 
    let result: Bool 
    defer { 
     completionHandler(result) 
    } 
    if userIQ > 10 { 
     result = false 
    } 
} 

, die tun und ist completionHandler gezwungen wird, aufgerufen werden:

func isTheEarthFlat(withUserIQ userIQ: Int, completionHandler: (Bool) -> Void) { 
    let result: Bool 
    defer { 
     completionHandler(result) 
    } 
    if userIQ > 10 { 
     result = false 
    } else { 
     result = true 
    } 
} 

nicht sicher, dass es ein gutes Muster benutzen.

2

Hier ist eine interessante Technik, an die ich gedacht habe. Sie definieren GuarenteedExecution und GuarenteedExecutionResult Typen.

Ein GuarenteedExecution ist eine Umhüllung um einen Verschluss, der in einem Kontext verwendet werden soll, in dem die Ausführung des Verschlusses garantiert werden muss.

Die GuarenteedExecutionResult ist das Ergebnis der Ausführung einer GuarenteedExecution. Der Trick besteht darin, eine gewünschte Funktion zu haben, z.B. isTheEarthFlat, geben Sie eine GuarenteedExecutionResult zurück. Die einzige Möglichkeit, eine GuarenteedExecutionResult Instanz zu erhalten, besteht darin, execute(argument:) über eine GuarenteedExecution anzurufen. Die für die Gewährleistung einer Rückgabe verantwortlichen Typprüfungsfunktionen werden nun verwendet, um die Ausführung von GuarenteedExecution zu gewährleisten.

struct GuarenteedExecutionResult<R> { 
    let result: R 

    fileprivate init(result: R) { self.result = result } 
} 

struct GuarenteedExecution<A, R> { 
    typealias Closure = (A) -> R 

    let closure: Closure 

    init(ofClosure closure: @escaping Closure) { 
     self.closure = closure 
    } 

    func execute(argument: A) -> GuarenteedExecutionResult<R> { 
     let result = closure(argument) 
     return GuarenteedExecutionResult(result: result) 
    } 
} 

Beispiel für die Verwendung in einer seperate Datei (um keinen Zugang zu GuarenteedExecutionResult.init):

let guarenteedExecutionClosure = GuarenteedExecution(ofClosure: { 
    print("This must be called!") 
}) 

func doSomething(guarenteedCallback: GuarenteedExecution<(),()>) 
    -> GuarenteedExecutionResult<()> { 
    print("Did something") 
    return guarenteedCallback.execute(argument:()) 
} 

_ = doSomething(guarenteedCallback: guarenteedExecutionClosure) 
+0

Interessante Idee, das einzige Problem mit diesem ist, müssen Sie eine andere 'GuaranteedExecution' für jede Anzahl von Parametern definieren, die Sie möchten . z.B. Was ist, wenn die Schließung vom Typ '(A, B) -> R',' (A, B, C) -> R' usw. ist? – Paolo

+0

@Paolo Ja, aber Sie könnten stattdessen ein Tupel verwenden. A '((X, Y, Z)) -> R)' entspricht einem '(A) -> R', wobei A' (X, Y, Z) 'ist – Alexander

Verwandte Themen