Downcasting opsional di Swift: as? Ketik, atau sebagai! Tipe?

95

Diberikan yang berikut di Swift:

var optionalString: String?
let dict = NSDictionary()

Apa perbedaan praktis antara dua pernyataan berikut:

optionalString = dict.objectForKey("SomeKey") as? String

vs.

optionalString = dict.objectForKey("SomeKey") as! String?
sdduursma
sumber
1
Aslo See The as! Operator dari Apple
Madu

Jawaban:

142

Perbedaan praktisnya adalah ini:

var optionalString = dict["SomeKey"] as? String

optionalStringakan menjadi variabel tipe String?. Jika tipe yang mendasarinya adalah sesuatu selain dari Stringini, ini tidak akan berbahaya hanya ditetapkan nilke opsional.

var optionalString = dict["SomeKey"] as! String?

Ini mengatakan, saya tahu hal ini adalah a String?. Ini juga akan mengakibatkan optionalStringmenjadi tipe String?, tetapi akan macet jika tipe yang mendasarinya adalah sesuatu yang lain.

Gaya pertama kemudian digunakan dengan if letuntuk membuka bungkus opsional dengan aman:

if let string = dict["SomeKey"] as? String {
    // If I get here, I know that "SomeKey" is a valid key in the dictionary, I correctly
    // identified the type as String, and the value is now unwrapped and ready to use.  In
    // this case "string" has the type "String".
    print(string)
}
vacawama
sumber
Bukankah metode pertama selalu lebih baik? Keduanya mengembalikan opsional tipe String? Sepertinya metode kedua melakukan hal yang sama seperti yang pertama tetapi mungkin macet jika downcast tidak berhasil. Jadi mengapa harus menggunakannya?
Sikander
6
Ya @Sikander, yang pertama selalu lebih baik. Saya tidak akan pernah menggunakan yang kedua.
vacawama
14

as? Types- berarti proses pengecoran turun adalah opsional. Proses bisa berhasil atau tidak (sistem akan mengembalikan nihil jika down casting gagal). Cara apapun tidak akan crash jika down casting gagal.

as! Type?- Di sini proses pengecoran harus berhasil ( !menunjukkan bahwa). Tanda tanya penutup menunjukkan apakah hasil akhirnya bisa nihil atau tidak.

Info lebih lanjut tentang "!" dan "?"

Mari kita ambil 2 kasus

  1. Mempertimbangkan:

    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as? UITableViewCell

    Disini kita tidak tahu apakah hasil dari down casting cell dengan identifier "Cell" ke UITableViewCell berhasil atau tidak. Jika tidak berhasil maka mengembalikan nil (jadi kami menghindari crash di sini). Di sini kita dapat melakukan seperti yang diberikan di bawah ini.

    if let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as? UITableViewCell {
        // If we reached here it means the down casting was successful
    }
    else {
        // unsuccessful down casting
    }

    Jadi mari kita mengingatnya seperti ini - Jika ?itu berarti kita tidak yakin apakah nilainya nihil atau tidak (tanda tanya muncul ketika kita tidak tahu sesuatu).

  2. Bandingkan itu dengan:

    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell. 

    Di sini kami memberi tahu kompiler bahwa pengecoran down harus berhasil. Jika gagal sistem akan macet. Jadi kita berikan !ketika kita yakin nilainya bukan nihil.

jishnu bala
sumber
11

Untuk memperjelas apa yang dikatakan vacawama, berikut adalah contohnya ...

Swift 3.0:

import UIKit

let str_value:    Any   = String("abc")!
let strOpt_value: Any?  = String("abc")!
let strOpt_nil:   Any?  = (nil as String?)
let int_value:    Any   = Int(1)
let intOpt_value: Any?  = Int(1)
let intOpt_nil:   Any?  = (nil as Int?)

// as String
//str_value     as String // Compile-Time Error: 'Any' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//strOpt_value  as String // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//strOpt_nil    as String // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//int_value     as String // Compile-Time Error: 'Any' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//intOpt_value  as String // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//intOpt_nil    as String // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?

// as? String
  str_value     as? String // == "abc"
  strOpt_value  as? String // == "abc"
  strOpt_nil    as? String // == nil
  int_value     as? String // == nil
  intOpt_value  as? String // == nil
  intOpt_nil    as? String // == nil

// as! String
  str_value     as! String // == "abc"
  strOpt_value  as! String // == "abc"
//strOpt_nil    as! String // Run-Time Error: unexpectedly found nil while unwrapping an Optional value.
//int_value     as! String // Run-Time Error: Could not cast value of type 'Swift.Int' to 'Swift.String'.
//intOpt_value  as! String // Run-Time Error: Could not cast value of type 'Swift.Int' to 'Swift.String'.
//intOpt_nil    as! String // Run-Time Error: unexpectedly found nil while unwrapping an Optional value.

// as String?
//str_value     as String? // Compile-Time Error: cannot convert value of type 'Any' to type 'String?' in coercion
//strOpt_value  as String? // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//strOpt_nil    as String? // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//int_value     as String? // Compile-Time Error: cannot convert value of type 'Any' to type 'String?' in coercion
//intOpt_value  as String? // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//intOpt_nil    as String? // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?

// as? String?
//str_value     as? String? // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  strOpt_value  as? String? // == "abc"
  strOpt_nil    as? String? // == nil
//int_value     as? String? // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  intOpt_value  as? String? // == nil
  intOpt_nil    as? String? // == nil

// as! String?
//str_value     as! String? // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  strOpt_value  as! String? // == "abc"
  strOpt_nil    as! String? // == nil
//int_value     as! String? // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
//intOpt_value  as! String? // Run-Time Error: Could not cast value of type 'Swift.Int' to 'Swift.String'.
  intOpt_nil    as! String? // == nil

// let _ = ... as String
//if let _ = str_value    as String { true } // Compile-Time Error: 'Any' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = strOpt_value as String { true } // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = strOpt_nil   as String { true } // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = int_value    as String { true } // Compile-Time Error: 'Any' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = intOpt_value as String { true } // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = intOpt_nil   as String { true } // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?

// let _ = ... as? String
if let _ = str_value    as? String { true } // true
if let _ = strOpt_value as? String { true } // true
if let _ = strOpt_nil   as? String { true } // false
if let _ = int_value    as? String { true } // false
if let _ = intOpt_value as? String { true } // false
if let _ = intOpt_nil   as? String { true } // false

// let _ = ... as! String
//if let _ = str_value    as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = strOpt_value as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = strOpt_nil   as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = int_value    as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = intOpt_value as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = intOpt_nil   as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'

// let _ = ... as String?
//if let _ = str_value    as String? { true } // Compile-Time Error: cannot convert value of type 'Any' to type 'String?' in coercion
//if let _ = strOpt_value as String? { true } // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//if let _ = strOpt_nil   as String? { true } // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//if let _ = int_value    as String? { true } // Compile-Time Error: cannot convert value of type 'Any' to type 'String?' in coercion
//if let _ = intOpt_value as String? { true } // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//if let _ = intOpt_nil   as String? { true } // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?

// let _ = ... as? String?
//if let _ = str_value    as? String? { true } // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  if let _ = strOpt_value as? String? { true } // true
  if let _ = strOpt_nil   as? String? { true } // true
//if let _ = int_value    as? String? { true } // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  if let _ = intOpt_value as? String? { true } // false
  if let _ = intOpt_nil   as? String? { true } // true

// let _ = ... as! String?
//if let _ = str_value    as! String? { true } // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  if let _ = strOpt_value as! String? { true } // true
  if let _ = strOpt_nil   as! String? { true } // false
//if let _ = int_value    as! String? { true } // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
//if let _ = intOpt_value as! String? { true } // Run-Time Error: Could not cast value of type 'Swift.Int' to 'Swift.String'.
  if let _ = intOpt_nil   as! String? { true } // false

Swift 2.0:

import UIKit

let str:    AnyObject   = String("abc")
let strOpt: AnyObject?  = String("abc")
let strNil: AnyObject?  = (nil as String?)
let int:    AnyObject   = Int(1)
let intOpt: AnyObject?  = Int(1)
let intNil: AnyObject?  = (nil as Int?)

str    as? String // == "abc"
strOpt as? String // == "abc"
strNil as? String // == nil
int    as? String // == nil
intOpt as? String // == nil
intNil as? String // == nil

str    as! String? // Compile-Time Error: Cannot downcast from 'AnyObject' to a more optional type 'String?'
strOpt as! String? // == "abc"
strNil as! String? // == nil
int    as! String? // Compile-Time Error: Cannot downcast from 'AnyObject' to a more optional type 'String?'
intOpt as! String? // Run-Time Error: Could not cast value of type '__NSCFNumber' to 'NSString'
intNil as! String? // == nil
Senseful
sumber
+1 untuk contoh Anda tetapi dapatkah Anda menjelaskan kepada saya dengan contoh yang sama untuk digunakan seperti! sebagai pengganti? sementara downcasting sebagai biarkan cell = tableView.dequeueReusableCellWithIdentifier ("Cell") sebagai! UITableViewCell..saya kira sebagai? sudah cukup mengapa kebutuhan sebagai!
Anish Parajuli 웃
biarkan cell = tableView.dequeueReusableCellWithIdentifier ("Cell") sebagai? UITableViewCell. - disini kita tidak tahu apakah hasil dari down casting cell dengan identifier "Cell" ke UITableViewCell nihil atau tidak. Jika nill maka ia mengembalikan nill (jadi kami menghindari crash di sini).
jishnu bala
menarik, intNil as! String? // ==niltidak menyebabkan crash !!! ???, sebagai Opsional <Int>. Tidak ada yang berbeda dari Opsional <String> .None
onmyway133
mengapa engkau tertekan as?ke String? Mengapa Anda tidak merendahkannya String?? Mengapa tidak kau tertunduk as!ke String?
Madu
Mencoba melakukan taman bermain ini di Swift 3, tetapi Anda harus menggunakan Anyalih-alihAnyObject
Honey
9
  • as digunakan untuk upcasting dan tipe casting untuk tipe bridged
  • as? digunakan untuk casting yang aman, kembalikan nihil jika gagal
  • as! digunakan untuk memaksa casting, crash jika gagal

catatan:

  • as! tidak dapat mengubah tipe mentah menjadi opsional

Contoh:

let rawString: AnyObject = "I love swift"
let optionalString: AnyObject? = "we love swift"
let nilString: AnyObject? = (nil as String?)

let rawInt: AnyObject = Int(3)
let optionalInt: AnyObject? = Int(3)
let nilInt: AnyObject? = (nil as Int?)

Contoh

var age: Int? = nil
var height: Int? = 180

Dengan menambahkan ? segera setelah tipe data Anda memberi tahu compiler bahwa variabel mungkin berisi angka atau tidak. Rapi! Perhatikan bahwa tidak masuk akal untuk mendefinisikan konstanta Opsional - Anda dapat menyetel nilainya hanya sekali dan oleh karena itu Anda akan dapat mengatakan apakah nilainya akan menjadi nil atau tidak.

Kapan kita harus menggunakan "?" dan kapan "!"

katakanlah kita memiliki aplikasi sederhana berbasis UIKit. kami memiliki beberapa kode di pengontrol tampilan kami dan ingin menampilkan pengontrol tampilan baru di atasnya. dan kami perlu memutuskan untuk menampilkan tampilan baru di layar menggunakan pengontrol navigasi.

Seperti yang kita ketahui, setiap instance ViewController memiliki pengontrol navigasi properti. Jika Anda membangun aplikasi berbasis pengontrol navigasi, properti pengontrol tampilan utama aplikasi Anda disetel secara otomatis dan Anda dapat menggunakannya untuk mendorong atau memunculkan pengontrol tampilan. Jika Anda menggunakan template proyek aplikasi tunggal - tidak akan ada pengontrol navigasi yang dibuat secara otomatis untuk Anda, sehingga pengontrol tampilan default aplikasi Anda tidak akan menyimpan apa pun di properti navigationController.

Saya yakin Anda sudah menebak bahwa ini persis kasus untuk tipe data Opsional. Jika Anda memeriksa UIViewController Anda akan melihat bahwa properti tersebut didefinisikan sebagai:

var navigationController: UINavigationController? { get }

Jadi mari kita kembali ke kasus penggunaan kita. Jika Anda mengetahui fakta bahwa pengontrol tampilan Anda akan selalu memiliki pengontrol navigasi, Anda dapat melanjutkan dan membukanya secara paksa:

controller.navigationController!.pushViewController(myViewController, animated: true)

Saat Anda meletakkan! di belakang nama properti Anda memberi tahu kompiler Saya tidak peduli bahwa properti ini opsional, saya tahu bahwa ketika kode ini dijalankan akan selalu ada penyimpanan nilai jadi perlakukan Opsional ini seperti tipe data normal. Bukankah itu bagus? Apa yang akan terjadi jika tidak ada pengontrol navigasi ke pengontrol tampilan Anda? Jika saran Anda bahwa akan selalu ada nilai yang disimpan di navigationController salah? Aplikasi Anda akan mogok. Sederhana dan jelek seperti itu.

Jadi, gunakan! hanya jika Anda 101% yakin bahwa ini aman.

Bagaimana jika Anda tidak yakin bahwa akan selalu ada pengontrol navigasi? Lalu Anda bisa menggunakan? bukannya!:

controller.navigationController?.pushViewController(myViewController, animated: true)

Apa itu? di belakang nama properti memberitahu kompiler adalah Saya tidak tahu apakah properti ini berisi nil atau nilai, jadi: jika memiliki nilai, gunakan itu, dan jika tidak, pertimbangkan seluruh ekspresi nil.Secara efektif? memungkinkan Anda menggunakan properti itu kalau-kalau ada pengontrol navigasi. Tidak, jika pemeriksaan dalam bentuk apa pun atau coran apa pun. Sintaks ini sempurna saat Anda tidak peduli apakah Anda memiliki pengontrol navigasi atau tidak, dan ingin melakukan sesuatu hanya jika ada.

Terima kasih banyak untuk Fantageek

swiftBoy
sumber
8

Mereka adalah dua bentuk Downcasting yang berbeda di Swift.

( as?) , yang dikenal sebagai Formulir Bersyarat , mengembalikan nilai opsional dari jenis yang Anda coba untuk downcast.

Anda dapat menggunakannya ketika Anda tidak yakin apakah downcast akan berhasil. Bentuk operator ini akan selalu mengembalikan nilai opsional, dan nilainya akan menjadi nol jika downcast tidak memungkinkan. Ini memungkinkan Anda untuk memeriksa downcast yang berhasil.


( as!) , yang dikenal sebagai Bentuk Paksa , mencoba downcast dan memaksa-membuka hasilnya sebagai aksi gabungan tunggal.

Anda harus menggunakannya HANYA ketika Anda yakin bahwa masa downcast akan selalu berhasil. Bentuk operator ini akan memicu error runtime jika Anda mencoba downcast ke jenis kelas yang salah.

Untuk lebih jelasnya, silakan periksa bagian Type Casting di dokumentasi Apple.

Scott Zhu
sumber
4

Mungkin contoh kode ini akan membantu seseorang memahami prinsip:

var dict = [Int:Any]()
dict[1] = 15

let x = dict[1] as? String
print(x) // nil because dict[1] is an Int

dict[2] = "Yo"

let z = dict[2] as! String?
print(z) // optional("Yo")
let zz = dict[1] as! String // crashes because a forced downcast fails


let m = dict[3] as! String?
print(m) // nil. the forced downcast succeeds, but dict[3] has no value
smileBot
sumber
Juga, misalkan z2 = dikt [2] sebagai! String // "Yo" (bukan opsional)
Jay
0

Mungkin paling mudah untuk mengingat pola untuk operator ini di Swift karena: !menyiratkan "ini mungkin menjebak," sementara? menunjukkan "ini mungkin nihil."

lihat: https://developer.apple.com/swift/blog/?id=23

Zgpeace
sumber
-1

Saya pemula di Swift dan menulis contoh ini mencoba menjelaskan seperti yang saya mengerti tentang 'pilihan'. Jika saya salah tolong perbaiki saya.

Terima kasih.


class Optional {

    var lName:AnyObject! = "1"

    var lastName:String!
}

let obj = Optional()

print(obj.lName)

print(obj.lName!)

obj.lastName = obj.lName as? String

print(obj.lastName)

(1): obj.lastName = obj.lName as! String

vs.

(2): obj.lastName = obj.lName as? String

Jwb: (1) Disini programmer pasti yakin “obj.lName”berisi object bertipe string. Jadi berikan saja nilai itu “obj.lastName”.

Nah, jika programmer benar berarti "obj.lName"objek bertipe string, maka tidak masalah. "obj.lastName" akan disetel ke nilai yang sama.

Tapi kalau programmer salah artinya "obj.lName" bukan objek bertipe string yaitu berisi beberapa objek tipe lain seperti "NSNumber" dll. Kemudian CRASH (Run Time Error).

(2) Programmer tidak yakin yang “obj.lName”berisi objek tipe string atau objek tipe lainnya. Jadi atur nilai itu menjadi “obj.lastName”jika itu adalah tipe string.

Nah, jika programmer benar berarti “obj.lName”objek bertipe string, maka tidak masalah. “obj.lastName”akan disetel ke nilai yang sama.

Tetapi jika programmer salah berarti obj.lName bukanlah objek bertipe string yaitu berisi beberapa objek tipe lain seperti "NSNumber"dll. Kemudian “obj.lastName”akan diset ke nilai nil. Jadi, No Crash (Happy :)

iPhoneBuddy
sumber