2014-08-31 7 views
15
protocol P : class { 
    var value:Int {get} 
} 

class X : P { 
    var value = 0 

    init(_ value:Int) { 
     self.value = value 
    } 
} 

var ps:[P] = [X(1), X(2)] 
for p in ps { 
    if let x = p as? X { // works for a single variable 
     ... 
    } 
} 

if let xs = ps as? [X] { // doesn't work for an array (EXC_BAD_ACCESS) 
    ... 
} 

Se P è una classe anziché un protocollo, il codice funziona correttamente. Qual è la differenza tra classe e protocollo? Sono entrambi implementati come puntatori nell'heap, vero? Il codice sopra può essere compilato con successo, ma si blocca in fase di runtime. Cosa significa questo errore EXC_BAD_ACCESS?L'array digitato per protocollo non può essere downcast a tipo concreto array


Grazie a @Antonio, ma ancora non capisco come funziona questo codice di esempio.

let someObjects: [AnyObject] = [ 
    Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"), 
    Movie(name: "Moon", director: "Duncan Jones"), 
    Movie(name: "Alien", director: "Ridley Scott") 
] 
for movie in someObjects as [Movie] { 
    println("Movie: '\(movie.name)', dir. \(movie.director)") 
} 

AnyObject è un caso speciale?

di riferimento: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TypeCasting.html#//apple_ref/doc/uid/TP40014097-CH22-XID_498


protocol P { 

} 

@objc class X : P { 

} 

@objc class Y : X { 

} 

var xs:[X] = [Y(), Y()] 
var ps:[P] = [Y(), Y()] 


xs as? [Y] // works 
ps as? [Y] // EXC_BAD_ACCESS 

ho provato questo codice in un parco giochi. Poiché si tratta di un codice swift puro, penso che non abbia nulla a che fare con @objc.

+0

Ho aggiornato la mia risposta: la precedente era chiaramente sbagliata. – Antonio

+0

Ancora non capisco perché l'operatore 'is' e' as? 'Funzioni per una singola variabile, ma non per una matrice. Ma questo può essere risolto con 'flagMap' ora. –

risposta

16

Ignorando opzionale vincolante per un attimo e utilizzando un assegnazione diretta: è riportata

let x = ps as [X] 

il seguente errore di runtime:

fatal error: array element cannot be bridged to Objective-C 

Ciò significa che gli afflitti da numerosi protocolli di matrice di adottanti richiede il binding obj-c. Questo può essere facilmente risolto dichiarando protocollo come objc compatibile:

@objc protocol P : class { 
    var value:Int {get} 
} 

Con questo semplice modifica, il codice ora funziona ed eccezione fase di esecuzione viene sollevata.

Ora il come è risolto, ma lasciando la perché un problema aperto. Non ho ancora una risposta, ma cercherò di approfondire ciò.

Addendum: capire il "perché"

Ho trascorso qualche tempo indagando su questo tema, e in seguito è quello che ho imparato con.

Abbiamo un protocollo e una classe adottarlo:

protocol P {} 
class X : P {} 

Creiamo un array di P:

var array = [P]() 

Convertire la matrice vuota [X] opere:

array as [X] // 0 elements 

Se aggiungiamo un elemento all'array, si verifica un errore di runtime:

array.append(X()) 
array as [X] // Execution was interrupted, reason: ... 

L'uscita della console dice che:

fatal error: array element cannot be bridged to Objective-C 

Così esprimono un array di oggetti di protocollo per un allineamento della sua adopter richiede colmare. Che giustifica il motivo per cui @objc corregge il problema:

@objc protocol P {} 
class X : P {} 

var array = [P]() 
array.append(X()) 
array as [X] // [X] 

Spulciando la documentazione, ho scoperto il motivo che ciò accada.

Per eseguire il cast, il runtime deve verificare se X è conforme al protocollo P. Il documentation afferma chiaramente che:

È possibile verificare la conformità del protocollo solo se il protocollo è contrassegnato con l'attributo @objc

Per verificare che (non che non mi fido della documentazione), ho usato questo codice nel parco giochi:

protocol P {} 
class X : P {} 

let x = X() 
let y = x is P 

ma ottengo un errore diverso, affermando che:

Playground execution failed: <EXPR>:18:11: error: 'is' test is always true 
let y = x is P 

scrittura che in un progetto di "regolare" invece otteniamo quello che ci aspettavamo:

protocol P {} 
class X {} 

func test() { 
    let x = X() 
    let y = x is P 
} 

Cannot downcast from 'X' to [email protected] protocol type 'P' 

Conclusione: in ordine per una matrice protocollo digitato per essere abbattuta a un array di tipo concreto, il protocollo deve essere contrassegnato con la @objc attributo. Il motivo è che il runtime utilizza l'operatore is per verificare la conformità del protocollo, che di conseguenza la documentazione è disponibile solo per i protocolli a ponte.

+0

(Per quanto riguarda i tuoi commenti alla domanda ora cancellata http://stackoverflow.com/questions/25589605/swift-forced-upwrapping-of-array-of-optionals: è perfettamente OK rispondere alla tua stessa domanda e alle persone sono incoraggiati a condividere le loro conoscenze in questo modo. Consulta http://stackoverflow.com/help/self-answer per ulteriori informazioni.) –

+0

Grazie @ Martin - Ho sempre visto persone pubblicare commenti come i miei, quindi ho pensato che fosse non autorizzato. Ha fatto una ricerca (probabilmente troppo veloce) e non è riuscito a trovare nulla che dicesse esplicitamente che fosse permesso o scoraggiato. Cercherò di raggiungerlo con le mie apoligie ... – Antonio

+0

Questo è sbagliato. Se 'A' è una sottoclasse di' B', '[A]' è implicitamente convertibile in '[B]', e '[B]' può essere espressamente castato in '[A]'.Né "Int" né "UInt" sono sottoclassi l'una dell'altra (in realtà, nessuna di esse è una classe, per non parlare delle sottoclassi). – newacct