web-dev-qa-db-ja.com

SwiftにおけるGenericsとAnyObjectの違い

ジェネリック引数を取り、述語に基づいて配列をフィルタリングするこのmyFilter関数を考えてみます。これは、Swiftが提供するfilter()関数と同じです。

func myFilter<T>(source: [T], predicate:(T) -> Bool) -> [T] {
  var result = [T]()
  for i in source {
    if predicate(i) {
      result.append(i)
    }
  }
  return result
}

これはどう違うのですか、

func myFilter(source: [AnyObject], predicate:(AnyObject) -> Bool) -> [AnyObject] {
  var result = [AnyObject]()
  for i in source {
    if predicate(i) {
      result.append(i)
    }
  }
  return result
}

後者の例でもジェネリックのポイントを達成していませんか?

42
avismara

ジェネリックはタイプセーフです。つまり、文字列をジェネリックとして渡し、整数として使用しようとすると、コンパイラーは文句を言い、コンパイルできなくなります(これは良いことです)。 (Swiftが静的型付けを使用しているために発生し、コンパイラerror

AnyObjectを使用する場合、コンパイラはオブジェクトが文字列として処理できるのか、整数として処理できるのかわかりません。それはあなたがそれであなたがやりたいことを何でもできるようにします(これは悪いことです)。

例えば文字列whenを渡そうとすると、以前に使用した整数型のアプリケーションがクラッシュします。 (これは、Swiftが動的型付けを使用しているために発生し、onlyが- ランタイムcrash

Genericsは基本的にコンパイラーに指示します:

「後でタイプを指定するので、指定したすべての場所でそのタイプを強制するをしてほしい。」

AnyObjectは基本的にコンパイラーに指示します:

「この変数について心配する必要はありませんここでタイプを強制する必要はありません何でもしたいのですが。」

81
Icaro

:イカロの答えはまだ受け入れられます。私は彼の説明を拡張しています。

TL; DR:イカロの答えを確認してください。

AnyObjectの使用についてIcaroは正しく次のように述べています。

この変数について心配する必要はありません。ここでタイプを強制する必要はないので、私がやりたいことは何でもできます。

これは何を意味するのでしょうか?質問のコード例を見てみましょう(一歩進んで、質問の意味を変更せずにAnyObjectAnyに変更しました)。

_func myFilter(source: [Any], predicate:(Any) -> Bool) -> [Any] {
  var result = [Any]()
  for i in source {
    if predicate(i) {
      result.append(i)
    }
  }
  return result
}
_

つまり、myFilter関数は、sourceとクロージャpredicateの2つの引数を取ります。よく見ると、ソース配列の内容は何でもかまいません。また、クロージャpredicateの引数も何でもかまいません。これらの "ANYTHING"に名前を付けるとしたら(ANYTHING1とANYTHING2と言います)、このアプローチでは、ANYTHING1がANYTHING2と等しい必要はありません。

これの意味をじっくりと考えてみましょう...

たとえば、整数の配列から偶数を除外したいので、Anyフィルターを使用してみましょう

_var ints = [1,2,3,4,5] as [Any]
var predicate = { (a : Any) -> Bool in
    return (a as! Int) % 2 == 0
}

let evens = myFilter(source: ints, predicate:predicate)
_

うわー、うまくいきましたね。みんな笑顔?番号。

次の行に注目してください。

_return (a as! Int) % 2 == 0
_

私はaを強制的にダウンキャストしています。 aInt以外の場合、この行はクラッシュしました。しかし、その使用法は正当化されています。結局のところ、Intsだけをフィルターで除外したいので、Intsの配列だけを使用するのに十分なほどスマートです。

しかし、たとえば、私は世間知らずのプログラマなので、次のようにします。

_var ints = [1,2,3,4,5,"6"] as [Any]
var predicate = { (a : Any) -> Bool in
    return (a as! Int) % 2 == 0
}

let evens = myFilter(source: ints, predicate:predicate)
_

これは問題なくコンパイルされますが、ランタイムでクラッシュします。コンパイラがこの行を教えてくれる方法があったら...

_var ints = [1,2,3,4,5,"6"]
_

...欠陥がありました、私たちはクラッシュしなかったでしょう。私はそれをすぐに修正したでしょう!

結局、あります。ジェネリック。イカロをもう一度引用すると、

後でタイプを指定しますが、指定したすべての場所でそのタイプを強制してください。

_func myFilter<T>(source: [T], predicate:(T) -> Bool) -> [T] {
    var result = [T]()
    for i in source {
        if predicate(i) {
            result.append(i)
        }
    }
    return result
}

var ints = [1,2,3,4,5,6]
var predicate = { (a : Int) -> Bool in
    return a % 2 == 0
}

let evens = myFilter(source: ints, predicate:predicate)
_

この新しいフィルターは素晴らしいです。それは私にさせません:

let evens = myFilter(source: ints, predicate:predicate)predicatesourceのタイプが一致しないため。コンパイル時エラー。

ジェネリックスはこのようにジェネリックです。この特定の例では、myFilter関数を作成する時点で、sourceの型やpredicateが取る引数を指定する必要はありません。Tです。しかし、sourceがANYTHINGの配列であると言ったら、predicateが受け入れる引数が同じANYTHINGであることを確認する必要があります。以前のANYTHING1、ANYTHING2の命名法の背景により、ジェネリクスはANYTHING1がANYTHING2と等しいことを強制していると言えます

13
avismara

最初の関数でTがnot AnyObjectのような型であるがtype変数;であると考えてください。つまり、最初の関数では任意のタイプの値の配列を最初のパラメーターとして渡すことができますが、述語はthat特定のタイプの値のみを2番目のパラメーターとして処理します。つまり、文字列の配列と文字列の述語、または整数の配列と整数の述語を渡すことができますが、整数の配列と文字列の述語を渡すことはできません。したがって、関数の本体は、型に関係するものに対して正しいことが保証されています。

代わりに、2番目の例では、任意の型の値と任意の(おそらく異なる!)型を操作する述語を渡すことができます。そのため、最初のパラメーターの値を使用して関数の本体で述語が呼び出される場合、次に、動的型エラーが発生する可能性があります。幸い、Swithタイプチェッカーは、この可能性を防ぐために、述語の呼び出しをタイプエラーとしてマークします。

1
Renzo