Действительно ли сила отлита и она всегда должна ее избегать?

Я начал использовать swiftLint и заметил, что одна из лучших практик для Swift заключается в том, чтобы избежать придания силы. Однако я много использовал его при обработке tableView, collectionView для ячеек:

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

Если это не лучшая практика, какой правильный способ справиться с этим? Я предполагаю, что могу использовать, если пусть с как?, но означает ли это для условия else, что мне нужно будет вернуть пустую ячейку? Это приемлемо?

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

Ответы

Ответ 1

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

as! SomeClass - контракт, он в основном говорит: "Я гарантирую, что эта вещь является экземпляром SomeClass". Если окажется, что это не SomeClass, тогда будет выбрано исключение, потому что вы нарушили контракт.

Вам нужно рассмотреть контекст, в котором вы используете этот контракт, и какие соответствующие действия вы могли бы предпринять, если вы не использовали силу downcast.

В примере, который вы даете, если dequeueReusableCellWithIdentifier не дает вам MyOffersViewCell, то вы, вероятно, неправильно настроили что-то, что связано с идентификатором повторного использования ячеек, и исключение поможет вам найти эту проблему.

Если вы использовали условное нажатие, вы получите нуль и должны как-то это обработать - зарегистрировать сообщение? Выбросить исключение? Это, безусловно, представляет собой неустранимую ошибку и то, что вы хотите найти во время разработки; вы не ожидали, что придется справиться с этим после выпуска. Ваш код не собирается внезапно запускать различные типы ячеек. Если вы просто разрешите сбой кода при отключении силы, он будет указывать прямо на строку, где возникла проблема.

Теперь рассмотрим случай, когда вы получаете доступ к некоторому JSON, полученному из веб-службы. Могут быть изменения в веб-службе, которая не поддается контролю, поэтому обработка этого более изящно может быть приятной. Возможно, ваше приложение не сможет функционировать, но, по крайней мере, вы можете показать предупреждение, а не просто сбой:

BAD - сбой, если JSON не является массивом

 let someArray=myJSON as! NSArray 
 ...

Лучше - обращение с недействительным JSON с предупреждением

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

Ответ 2

В дополнение к ответу Paulw11 этот шаблон полностью действителен, безопасен и полезен иногда:

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

Рассмотрим пример Apple: массив экземпляров Media, который может содержать объекты Song или Movie (оба подкласса Media):

let mediaArray = [Media]()

// (populate...)

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

РЕДАКТИРОВАТЬ: После использования Swiftlint какое-то время я теперь конвертирую в нуль Force-Unwrapping Cult (в соответствии с комментарием @Kevin ниже).

Итак, в наши дни я бы сделал это:

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

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

... или лучше, используйте flatMap(), чтобы превратить mediaArray в два (возможно, пустых) типизированных массива типов [Song] и [Movie] соответственно. Но это выходит за рамки вопроса (force-unwrap)...

Кроме того, я не буду принудительно разворачивать даже при удалении ячеек таблицы. Если декомпрессированная ячейка не может быть отнесена к соответствующему подклассу UITableViewCell, это означает, что в моих раскадровках что-то не так, поэтому я не могу исправить некоторые условия выполнения (скорее, ошибку времени разработки, которая должна быть обнаружена и исправлена), поэтому я беру под залог fatalError().

Ответ 3

"Force Cast" имеет свое место, когда вы знаете, что то, что вы делаете, относится к этому типу, например.

Скажем, мы знаем, что myView имеет поднаблюдение, которое является UILabel с тегом 1, мы можем идти вперед и заставлять бросать из UIView в UILabel безопасность:

myLabel = myView.viewWithTag(1) as! UILabel

В качестве альтернативы более безопасным вариантом является использование охранника.

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

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

Подводя итог: если вы точно знаете, что это будет, тогда вы можете заставить бросить иначе, если есть малейшая вероятность, что это может быть что-то другое, используйте охранник

Ответ 4

В тех случаях, когда вы действительно уверены, что объект должен быть указанного типа, было бы нормально сбрасывать. Тем не менее, я использую следующую глобальную функцию в этих случаях, чтобы получить более значимый результат в журналах, которые на мой взгляд лучше подходят:

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
}

Пример использования:

class AnalysisViewController: UIViewController {

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

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