15

Ho iniziato a usare swiftLint e ho notato che una delle migliori pratiche per Swift è evitare il cast forzato. Tuttavia ho usato molto durante la manipolazione tableView, CollectionView per le celle:Il cast di forza è davvero pessimo e dovrebbe sempre evitarlo?

let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellID, forIndexPath: indexPath) as! MyOffersViewCell 

Se questo non è la migliore pratica, qual è il modo giusto per gestire questa situazione? Suppongo di poterlo usare se lasciato con as ?, ma questo significa che per le altre condizioni dovrò restituire una cella vuota? È accettabile?

if let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellID, forIndexPath: indexPath) as? MyOffersViewCell { 
     // code 
} else { 
     // code 
} 
+1

direi utilizzando forzare lo srotolamento è accettabile. Finché sai cosa stai facendo. Ma nella tua situazione particolare, l'utilizzo dello scompartimento opzionale sarà migliore. Puoi controllare se la cella restituita da 'dequeueReusableCellWithReuseIdentifier' è il tipo di' MyOffersViewCell'. Se è così, fai quello che vuoi, se no, restituisci semplicemente "UITableViewCell", nessun problema. –

risposta

28

Questa domanda è probabilmente opinione basa, in modo da prendere la mia risposta con un grano di sale, ma non direi che la forza abbattuto è sempre male; devi solo considerare la semantica e come si applica in una determinata situazione.

as! SomeClass è un contratto, in pratica dice "Garantisco che questa cosa è un'istanza di SomeClass". Se si scopre che non è SomeClass, verrà generata un'eccezione perché hai violato il contratto.

È necessario considerare il contesto in cui si sta utilizzando questo contratto e quali azioni appropriate è possibile intraprendere se non si utilizza il downcast forzato.

Nell'esempio si dà, se dequeueReusableCellWithIdentifiernon ti dà un MyOffersViewCell allora probabilmente avete configurato male qualcosa a che fare con l'identificatore riutilizzo delle cellule e un'eccezione vi aiuterà a trovare quel problema.

Se si è utilizzato un downcast condizionale, si otterrà zero e si dovrà gestirlo in qualche modo - Registrare un messaggio? Lanciare un'eccezione? Rappresenta certamente un errore irrecuperabile e qualcosa che vuoi trovare durante lo sviluppo; non ti aspetteresti di dover gestire questo dopo il rilascio. Il tuo codice non inizierà improvvisamente a restituire diversi tipi di celle. Se si lascia che il codice si arresti in modo anomalo, verrà indirizzato direttamente alla riga in cui si è verificato il problema.

Ora, considerare un caso in cui si accede ad alcuni JSON recuperati da un servizio Web. Potrebbe esserci un cambiamento nel servizio web al di fuori del tuo controllo, quindi gestirlo con più eleganza potrebbe essere bello. La vostra applicazione potrebbe non essere in grado di funzionare, ma almeno si può mostrare un avviso, piuttosto che semplicemente schiantarsi:

male - si blocca se JSON non è una matrice

let someArray=myJSON as! NSArray 
... 

Meglio - Handle non valido JSON con un avviso

guard let someArray=myJSON as? NSArray else { 
    // Display a UIAlertController telling the user to check for an updated app.. 
    return 
} 
+2

se questa è la tua opinione, allora va bene, ma personalmente preferirei che un'app che sto usando mi dia qualche indicazione che c'è un problema piuttosto che schiantarsi e riportarmi al trampolino. Cosa succede se il server non è * danneggiato * di per sé, ma piuttosto, come suggerisco nella mia risposta, fornito da una terza parte che modifica unilateralmente l'API senza preavviso? Questo non è bello, ma potrebbe succedere. Esistono altri modi in cui un'applicazione può segnalare problemi senza ricorrere all'invio di registri di arresti anomali. – Paulw11

+2

Se si sta utilizzando SwiftLint e si desidera semplicemente disattivare la violazione: lasciare someArray = (myJSON as? NSArray)! – ataranlen

5

"Forza Fusioni" ha il suo posto, quando si sapere che quello che stai gettando a è di quel tipo ad es ampio.

Dire sappiamo che myView ha una visualizzazione secondaria che è una UILabel con il tag 1, siamo in grado di andare avanti e la forza verso il basso cast UIView a UILabel sicurezza:

myLabel = myView.viewWithTag(1) as! UILabel 

In alternativa, l'opzione più sicura è quella di utilizzare una guardia.

guard let myLabel = myView.viewWithTag(1) as? UILabel else { 
    ... //ABORT MISSION 
} 

Quest'ultimo è più sicuro in quanto, ovviamente, gestisce qualsiasi caso negativo, ma il primo è più semplice. Quindi, in realtà, si tratta di preferenze personali, considerando se è qualcosa che potrebbe essere cambiato in futuro o se non sei sicuro se ciò che stai scartando sarà ciò che vorresti lanciare in quella situazione, una guardia sarebbe sempre la scelta giusta.

In sintesi: Se si sa esattamente che cosa sarà allora è possibile forzare getto altrimenti se c'è la minima possibilità che potrebbe essere qualcos'altro utilizzare una guardia

6

Oltre alla risposta di Paulw11, questo modello è del tutto valido, sicuro e utile a volte:

if myObject is String { 
    let myString = myObject as! String 
} 

Si consideri l'esempio dato da Apple: una serie di Media casi, che può contenere sia Song o Movie oggetti (due sottoclassi di media):

let mediaArray = [Media]() 

// (populate...) 

for media in mediaArray { 
    if media is Song { 
     let song = media as! Song 
     // use Song class's methods and properties on song... 
    } 
    else if media is Movie { 
     let movie = media as! Movie 
     // use Movie class's methods and properties on movie... 
    } 

EDIT: Dopo aver utilizzatoSwiftlint per un po ', ora sono un convertito totale alla Zero Forza-Scartare Cult (in linea con @ il commento di Kevin di seguito).

Così, al giorno d'oggi farei questo:

for media in mediaArray { 
    if let song = media as? Song { 
     // use Song class's methods and properties on song... 

    } else if let movie = media as? Movie { 
     // use Movie class's methods and properties on movie... 
    } 
} 

... o meglio, utilizzare flatMap() per trasformare mediaArray in due (possibilmente vuoto) digitato rispettivamente array di tipi [Song] e [Movie]. Ma questo è al di fuori della portata della domanda (force-unwrap) ...

Inoltre, non forzerò lo srotolamento nemmeno quando si eliminano le celle di visualizzazione tabella. Se la cella in dequeued non può essere trasmessa alla sottoclasse UITableViewCell appropriata, significa che c'è qualcosa di sbagliato nei miei storyboard, quindi non è una condizione di runtime da cui posso recuperare (piuttosto un errore di sviluppo che deve essere rilevato e risolto) quindi I cauzione con fatalError().

+2

Preferirei molto a se lasciarlo scartare invece di forzare il casting lì. 'se lascia myString = myObject as? String' or' se let song = media come? Canzone {} else se lascia film = media come? Movie'. Mentre quello schema è sicuro, lo scompartimento opzionale può essere eseguito senza scomposizione forzata – Kevin

+1

Sicuro. Immagino sia una questione di stile/preferenza, e in effetti è quello che finisco per fare tutto il tempo (sono un enorme fan di "se/let" - "guard/let/else"). Ho appena richiamato questo esempio da una documentazione Apple ... –

0

Nei casi in cui si è veramente sicuri che l'oggetto debba essere del tipo specificato, sarebbe corretto eseguire il cast. Comunque, io uso la seguente funzione globale in questi casi per ottenere un risultato più significativo nei registri che è ai miei occhi un approccio migliore: l'utilizzo

public func castSafely<T>(_ object: Any, expectedType: T.Type) -> T { 
    guard let typedObject = object as? T else { 
     fatalError("Expected object: \(object) to be of type: \(expectedType)") 
    } 
    return typedObject 
} 

Esempio:

class AnalysisViewController: UIViewController { 

    var analysisView: AnalysisView { 
     return castSafely(self.view, expectedType: AnalysisView.self) 
    } 

    override func loadView() { 
     view = AnalysisView() 
    } 
}