Подтвердить что ты не робот

Разница между Generics и AnyObject в Swift

Рассмотрим эту функцию myFilter, которая принимает общий аргумент и фильтрует массив на основе предиката. Это то же самое, что и функция filter(), предоставляемая Swift.

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
}

Разве мы не достигаем точки дженериков даже в последнем примере?

4b9b3361

Ответ 1

Дженерики безопасны по типу, то есть если вы передаете строку как общий и попытаетесь использовать в качестве целого числа, компилятор будет жаловаться, и вы не сможете скомпилировать ваш (что хорошо). (Это происходит из-за того, что Swift использует Static typing и может дать вам ошибку компилятора)

Если вы используете AnyObject, компилятор понятия не имеет, может ли объект обрабатываться как String или Integer. Это позволит вам делать то, что вы хотите с ним (что плохо).

например. если вы попытаетесь передать String, когда это ранее использовавшееся Integer, приложение будет аварийно завершено. (Это происходит из-за того, что Swift использует Динамическое типирование и только даст вам сбой во время выполнения)

Generics в основном сообщает компилятору:

"Я дам вам тип позже, и я хочу, чтобы вы применяли это тип везде, где я указываю."

AnyObject в основном сообщает компилятору:

"Не беспокойтесь об этой переменной , не нужно применять какой-либо тип здесь, чтобы я сделал все, что захочу.

Ответ 2

Примечание: ответ Икаро все равно будет принятым ответом, я просто расширяю его объяснение.

TL; DR: проверьте ответ Icaro.

Об использовании AnyObject Икаро справедливо ставит:

Не беспокойтесь об этой переменной, не нужно принуждать любого типа здесь я делаю все, что хочу.

Что это значит? Возьмем пример кода в вопросе (я сделал шаг вверх и изменил AnyObject на Any без изменения значения вопроса):

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. Если вы посмотрите внимательно, содержимое исходного массива может быть НИЧЕГО. А аргумент замыкания predicate также может быть НИЧЕГО. Если бы мы назовем эти "НИЧЕГО" - скажем НИЧЕГО 1 и НИЧЕГО2 - этот подход не требует, чтобы НИЧЕГО 1 было равно НИЧЕГО2.

Пусть сидят сложа руки и размышляют над последствиями этого...

Скажем, мы хотим отфильтровать evens из массива целых чисел и использовать наш фильтр 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. Эта строка выйдет из строя, если a будет чем-то иным, чем Int. Но его использование оправдано; в конце концов, мы хотим просто отфильтровать Int, и я достаточно умен, чтобы использовать только массив Int s.

Но, поскольку, скажем, я наивный программист, я делаю это:

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"]

... был неисправен, у нас не было бы крушения. Я бы сразу исправил это!

Оказывается, есть. Обобщения. Чтобы снова процитировать Icaro,

Я дам вам тип позже, и я хочу, чтобы вы тип, который я указываю везде.

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), потому что типы predicate и source не совпадают. Ошибка времени компиляции.

Generics является таким общим: в этом конкретном примере - в то время как в точке написания функции myFilter вам не нужно указывать тип source или аргумент, который predicate принимает, это Т, это НИЧЕГО. Но как только я скажу, что source является массивом НИЧЕГО, вам нужно убедиться, что аргумент, который принимает predicate, тот же НИЧЕГО. На фоне нашей предыдущей номенклатуры ANYTHING1, ANYTHING2 мы можем сказать, что generics заставляет ANYTHING1 быть равно ANYTHING2

Ответ 3

Учтите, что в первой функции T не является типом, как и AnyObject, но переменной типа; это означает, что в первой функции вы можете передать массив значений любого типа в качестве первого параметра, но предикат, который работает только с значениями этого конкретного типа в качестве второго параметра. То есть вы можете передать массив строк и предикат для строк или массив целых чисел и предикат для целых чисел, в то время как вы не можете передать массив целых чисел и предикат на строки. Таким образом, тело функции гарантировано будет правильным для того, что касается типов.

Вместо этого во втором примере вы можете передать значение любого типа и предиката, который работает на любом (возможно, другом!) типе, так что, если предикат будет вызываться в теле функции со значением первый параметр, тогда может произойти ошибка динамического типа. К счастью, программа Swith typechecker отмечает вызов предиката как ошибку типа, чтобы предотвратить эту возможность.