2015-06-18 8 views
6

C'è un modo per specificare che un particolare argomento del metodo ha semantica debole?Semantica argomento metodo debole

Elaborare, questo è un esempio di codice Objective-C, che funziona come previsto:

- (void)runTest { 
    __block NSObject *object = [NSObject new]; 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
     [self myMethod:object]; 
    }); 
    // to make sure it happens after `myMethod:` call 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     object = nil; 
    }); 
} 
- (void)myMethod:(__weak id)arg0 { 
    NSLog(@"%@", arg0); // <NSObject: 0x7fb0bdb1eaa0> 
    sleep(1); 
    NSLog(@"%@", arg0); // nil 
} 

questa è la versione Swift, che non si

public func runTest() { 
    var object: NSObject? = NSObject() 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { 
     self.myMethod(object) 
    } 
    dispatch_async(dispatch_get_main_queue()) { 
     object = nil 
    } 
} 
private func myMethod(arg0: AnyObject?) { 
    println("\(arg0)") //Optional(<NSObject: 0x7fc778f26cf0>) 
    sleep(1) 
    println("\(arg0)") //Optional(<NSObject: 0x7fc778f26cf0>) 
} 

Sono corretto YM ipotesi che non c'è modo che l'arg0 diventi nullo tra le chiamate al metodo nella versione Swift? Grazie!

Aggiornamento un utente Apple Dev.Forums ha sottolineato che sleep non è una buona funzione da utilizzare e le spedizioni consecutive potrebbero causare condizioni di gara. Mentre quelli potrebbero essere ragionevoli preoccupazioni, questo è solo un codice di esempio, l'obiettivo della domanda è passare argomenti deboli.

+0

Non è possibile al momento, è possibile farlo prima di Swift 2.0 con '[weak yourObject]' all'inizio della chiusura. Vedi anche [here] (http://stackoverflow.com/questions/24717460/cant-make-weak-reference-to-closure-in-swift) dove mostrano alcune soluzioni alternative – Kametrixom

+0

Capisco che la chiusura debole potrebbe essere una soluzione ma non risolve il problema, non voglio che tutti i consumatori dell'API siano a conoscenza dei miei dettagli di implementazione. È possibile in Swift 2? –

+0

In che modo i consumatori possono essere consapevoli dell'implementazione dell'API in questo modo? – Kametrixom

risposta

1

C'è un modo per specificare che un particolare argomento del metodo ha semantica debole?

Questo non è ciò che sta facendo l'esempio del codice Objective-C. Hai una semantica accidentalmente quasi debole e hai un comportamento indefinito (condizioni di competizione) che i veri riferimenti deboli non hanno.

myMethod può inviare un messaggio in la-la-terra in qualsiasi punto di sequenza (il primo NSLog dichiarazione o la seconda, o anche nel mezzo della NSLog da qualche parte ... anche se ARC non Elide la conservano di arg0 voi stai ancora correndo la versione principale della coda o peggio, mantenendo un oggetto zombi).

Dichiarazione qualcosa come __block significa semplicemente allocare una fessura nell'ambiente mucchio per il blocco (perché dispatch_async è garantito per lasciare che il blocco sfuggire promuoverà da un blocco stack allocato ad un blocco mucchio, e uno degli slot di archiviazione in quell'ambiente heap block sarà per la variabile __block. In ARC il blocco avrà automaticamente Block_copy chiamato, forse più appropriato denominato Block_copy_to_heap).

Ciò significa che entrambe le istanze di esecuzione del blocco punteranno a questa stessa posizione di memoria.

Se aiuta, immagina questo codice davvero stupido che ha una condizione di gara evidente. Ci sono 1000 blocchi in coda contemporaneamente che tentano tutti di modificare unsafe. Siamo quasi sicuri di eseguire le dichiarazioni sgradevoli all'interno del blocco if perché il nostro compito e il confronto non sono atomici e stiamo combattendo per la stessa posizione di memoria.

static volatile size_t unsafe = 0; 

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 

    dispatch_apply(1000, queue, ^(size_t instance) { 
     unsafe = instance; 
     if (unsafe != instance) { 
      FORMAT_ALL_STORAGE(); 
      SEND_RESIGNATION_EMAIL(); 
      WATCH_THE_WORLD_BURN(); 
     } 
    }); 

Il tuo esempio Swift non ha lo stesso problema perché il blocco che non modifica il valore probabilmente cattura (e mantiene) l'oggetto in modo non vede la modifica dal l'altro blocco.

Spetta al creatore di una chiusura gestire le conseguenze della gestione della memoria in modo da non poter creare un contratto API che non imponga cicli di conservazione in una chiusura, oltre a contrassegnare qualcosa come @noescape nel qual caso Swift ha vinto " t fare qualsiasi ritenzione/rilascio o altra gestione della memoria perché il blocco non sopravvive al frame dello stack corrente. Ciò preclude la spedizione asincrona per ovvi motivi.

Se si desidera presentare un contratto API che risolva ciò, è possibile che un tipo adotti un protocollo protocol Consumer: class { func consume(thing: Type) } quindi all'interno dell'API mantieni un riferimento debole alle istanze Consumer.

Un'altra tecnica è di accettare una versione al curry di una funzione di esempio e debolmente catturare self:

protocol Protocol: class { } 
typealias FuncType =() -> Void 
var _responders = [FuncType]() 

func registerResponder<P: Protocol>(responder: P, usingHandler handler: (P) ->() -> Void) { 
    _responders.append({ [weak responder] in 
     guard let responder = responder else { return } 
     handler(responder)() 
    }) 
} 

class Inst: Protocol { 
    func myFunc() { 

    } 
} 
let inst = Inst() 
registerResponder(inst, usingHandler: Inst.myFunc) 
+0

Non sono sicuro che questo risponda alla domanda – doozMen

+0

Tecnicamente si è corretti perché la domanda originale si basa su un fraintendimento di ciò che stava effettivamente accadendo e contiene errori di sicurezza di thread e memoria; non esiste un parametro del metodo debole che ho cercato di spiegare chiaramente nella mia risposta. Ho pensato che sarebbe stato utile spiegare in dettaglio in modo che tutti potessero imparare. – russbishop

+0

@russbishop Se "non esiste un parametro del metodo debole", allora perché il compilatore consente la parola chiave '__weak' sul parametro del metodo? Normalmente, Clang genera errori se si mettono i kewords nei posti sbagliati. –

6

Swift non ha “args deboli” ... ma questa è probabilmente solo perché Swift (3.0) args sono immutabili (equivalente a let s) e weak le cose in Swift devono essere sia un var sia un Optional.

Detto questo, c'è davvero un modo abbastanza facile da realizzare, un equivalente debole uso args- un weak var locale (che libera l'arg-var per essere rilasciato). Questo funziona perché Swift non si blocca su Vars fino alla fine dell'ambito corrente (come fa il C++ così rigorosamente); ma piuttosto rilascia le vars dall'ambito dopo l'ultimo utilizzo di esse (il che rende lldb -ing a volte un PitA, ma qualunque).

Il seguente esempio funziona in modo coerente a Swift 3.0.2 su Xcode 8.2.1 su MacOS 10.11.6:

class Test 
{ 
    func runTest() { 
     var object:NSObject? = NSObject() 
     myMethod(arg0: object) 

     DispatchQueue.main.asyncAfter(
      deadline: DispatchTime.now() + 1.0, 
      qos: .userInteractive, 
      flags: DispatchWorkItemFlags.enforceQoS 
     ){ 
      object = nil 
     } 
    } 

    func myMethod(arg0:AnyObject?) { 
     weak var arg0Weak = arg0 
     // `arg0` get “released” at this point.  Note: It's essential that you 
     // don't use `arg0` in the rest of this method; only use `arg0Weak`. 

     NSLog("\(arg0Weak)"); // Optional(<NSObject: 0x600000000810>) 

     DispatchQueue.main.asyncAfter(
      deadline: DispatchTime.now() + 2.0, 
      qos: .userInteractive, 
      flags: DispatchWorkItemFlags.enforceQoS 
     ){ 
      NSLog("\(arg0Weak)"); // nil 
     } 
    } 
} 

Test().runTest() 

Si noti che se provate questo in un parco giochi, il parco giochi si concluderà l'esecuzione prima della DispatchQueue s incendio. Il modo più semplice per eseguire l'eseguibile indefinitamente (cosa ho fatto) è creare una nuova applicazione Cocoa e incollare tutto il codice sopra nello func applicationDidFinishLaunching(_:Notification) { … }(sì, verbatim- Swift consente le definizioni di classe annidate all'interno dei metodi).


In risposta alla che hai ottenuto rispetto all'uso di dispatch_async & sleep nel tuo esempio, per dimostrare che args deboli sono infatti il ​​vero affare ecco una completezza main.m variante -source filo-lezioni-sicurezza del test che è single-threaded e senza coda:

#import <Foundation/Foundation.h> 


@interface Test : NSObject 
- (void)runTest; 
- (void)myMethod:(__weak id)arg0 callback:(void (^)())callback; 
@end 


int main(int argc, const char * argv[]) { 
    @autoreleasepool { 
     [[Test new] runTest]; 
    } 
    return 0; 
} 


@implementation Test 

- (void)runTest { 
    __block NSObject *object = [NSObject new]; 
    [self myMethod:object callback:^{ 
     object = nil; 
    }]; 
} 

- (void)myMethod:(__weak id)arg0 callback:(void (^)())callback { 
    NSLog(@"%@", arg0); // <NSObject: 0x100400bc0> 
    callback(); 
    NSLog(@"%@", arg0); // (null) 
} 

@end 
+0

Credo che si possa anche evitare di riutilizzare attenuando l'argomento: es. Var arg0 = arg0 –

+0

Il mio test fallisce per questo caso: https://gist.github.com/garvankeeley/e85c2adb8e3079e4fe3c17ae62df478c –

+0

"Funziona perché Swift non si blocca su vars fino alla fine dello scope corrente "-> hai documenti per questo? –

2

nessun modo con la sintassi del linguaggio per ora.

Penso che questa soluzione alternativa sia la più vicina per ora.

public struct Weak<T> where T: AnyObject { 
    public weak var object: T? 

    public init(_ object: T?) { 
     self.object = object 
    } 
} 

func run(_ a: Weak<A>) { 
    guard let a = a.object else { return } 
}