Приведение типов в Swift: как использовать is, as, as! и as?

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

Что такое тип?

Swift использует типы, такие как классы и структуры, для представления различных типов данных в коде вашего приложения.

  • Int используется для целочисленных значений.
  • Double используется для десятичных значений, например 3.1415
  • String используется для текста, например «Привет».
  • UIButton используется для элемента пользовательского интерфейса в виде кнопки.

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

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

Что из себя представляет приведение типов?

При приведении типа вы относитесь к объекту одного типа как к другому типу.

Опишем три разных класса. Первый класс House имеет одно свойство windows типа Int, а также инициализатор:

class House {
    var windows: Int = 0
 
    init(windows: Int) {
        self.windows = windows
    }
}

Следующий класс Villa является подклассом House, тем самым наследуя его свойство windows. Он также добавляет новое свойство hasGarage:

class Villa: House {
    var hasGarage: Bool = false
 
    init(windows: Int, hasGarage: Bool) {
        self.hasGarage = hasGarage
        super.init(windows: windows)
    }
}

Третий класс Castle также наследуется от House. Он добавляет свойство towers:

class Castle: House {
    var towers: Int = 0
 
    init(windows: Int, towers: Int) {
        self.towers = towers
        super.init(windows: windows)
    }
}

Оба класса Villaи Castle являются подклассами House. В результате у обоих из них есть свойство windows в дополнение к своим собственным свойствам hasGarage и towers.

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

  • Мы можем выполнить восходящиее преобразование и привести экземпляр типа Villa или Castle в House.
  • Мы можем выполнить нисходящее преобразование и привести экземпляр типа House до Villa или Castle.

Посмотрим на пример:

let house: House = Castle(windows: 200, towers: 4)

В приведенном выше коде мы объявляем константу с именем house типа House. Далее мы инициализируем ее экземпляром Castle и определяем 200 окон и 4 башни.

Это называется восходящим преобразованием. Поскольку House и Castle находятся в одной иерархии классов, вы можете назначить экземпляр Castle переменной типа House. Это все равно что сказать: замок — это дом.

Приведение выполняется неявно, однако мы также можем написать:

let house: House = Castle(windows: 200, towers: 4) as House

Мы можем использовать функцию type(of:), чтобы получить значение типа:

print(type(of: house))
// Castle

Как может house быть типа Castle, когда мы четко объявили его с типом House?

print(house.towers)
// error: value of type 'House' has no member 'towers'

Экземпляр house имеет данные, представленные классом Castle. У него есть окна и башни. Но мы описываем его с помощью класса House, и в результате мы не можем получить доступ к свойству towers.

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

Вернемся к неявному приведению:

let house: House = Castle(windows: 200, towers: 4)

Несмотря на то, что house имеет значение типа Castle, оно описывается как тип House, поэтому мы не можем добраться до свойства house.towers.

Используем нисходящее преобразования для приведения house к типу Castle:

let castle: Castle = house as! Castle

Мы используем ключевое слово as! для нисходящего преобразования.

Теперь мы можем получить свойство towers:

print(castle.towers)
// 4

Экземпляры house и castle относятся к одному и тому же объекту:

print(house === castle)
// true

Как использовать is, as, as! и as?

Мы можем использовать 4 различных типа операторов:

  • is — для проверки типа.
  • as для восходящего преобразования.
  • as! для принудительного нисходящего преобразования.
  • as? для опционального нисходящего преобразования.

Проверка типа с помощью is

Мы используем оператор is, чтобы проверить тип экземпляра. Выражение возвращает значение типа Bool, поэтому его можно использовать в условном выражении:

let tintagel: Castle = Castle(windows: 300, towers: 1)
 
if tintagel is Castle {
    print("Это замок!")
} else if tintagel is Villa {
    print("Это вилла!")
}

Приведенное выше выражение возвращает true, поэтому выполняется первое условие.

Когда вы используете is для проверки типа суперкласса, он также возвращает true:

let house: House = Castle(windows: 123, towers: 3)
 
print(house is House)   // true
print(house is Castle)  // true
print(house is Villa)   // false

Восходящее преобразование с помощью as

Восходящее преобразование происходит неявно, поэтому вы можете не использовать ключевое as. Как объяснялось в предыдущей главе, восходящее преобразование возможно для подкласса к суперклассу. Например, Villa является House.

Принудительное преобразование с помощью as!

Принудительное преобразование дает вам возможность преобразовать суперкласс в подкласс.

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

Посмотрим на пример:

let house: House = Castle(windows: 200, towers: 4)
print(house.towers) // Это не работает. House не имеет свойств towers.
 
let castle: Castle = house as! Castle
print(castle.towers) // Теперь house имеет свойства towers.

Опциональное преобразование с помощью as?

Часто бывает удобнее использовать опциональное преобразование с ключевым словом as?. Тогда в случае сбоя возвращается nil.

let house: House = Castle(windows: 200, towers: 4)
print(house.windows) // 200
 
let villa: Villa? = house as? Villa
print(villa?.hasGarage) // nil

Мы пытаемся преобразовать house к Villa и присвоить результат константе villa. Данное преобразование терпит неудачу, потому что значение house имеет типа Castle, а Castle и Villa не могут быть преобразованы друг к другу.

Давайте посмотрим на еще один пример:

var houses = [
    Castle(windows: 100, towers: 3),
    Villa(windows: 20, hasGarage: false),
    Castle(windows: 999, towers: 12),
    House(windows: 3),
    Castle(windows: 93, towers: 8),
    Villa(windows: 42, hasGarage: true)
]

В приведенном выше примере мы создаем массив с экземплярами Castle, House и Villa. Благодаря полиморфизму тип массива houses будет выведен в качестве [House]. Однако в этом массиве есть экземпляры 3 разных классов.

Мы можем написать цикл for-in:

for house in houses {
    if let castle = house as? Castle {
        print("Замок имеет \(castle.windows) окна и \(castle.towers) башни")
    }
    else if let villa = house as? Villa {
        print("Вилла имеет \(villa.windows) окна и \(villa.hasGarage ? "имеет" : "не имеет") гараж")
    }
    else {
        print("Дом с \(house.windows) окнами")
    }
}

В цикле мы используете опциональное приведение и опциональное связывание для проверки типа house, а затем вызываем одно из трех условных выражений.

Приведение типов в практической разработке

Вот для чего мы можем использовать приведение типов:

  • Работа с различными типами контроллеров представления в контроллере панели вкладок (tab bar controller), например, для выполнения действий с контроллером представления определенного типа.
  • Преобразование типов между объектными моделями, например, когда вы получаете объект PFObject из PFUser.
  • Приведение Any к определенному типу, например, при получении объектов через JSON.
  • Преобразования между типами Swift и Objective-C, к примеру, чтобы использовать String в качестве NSString.
  • Преобразование подклассов контроллеров представлений, например, в контроллер табличного представления с настраиваемым подклассом ячеек табличного представления.

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

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as? MyCustomTableViewCell
 
 
    return cell
}

Приведение выполняется успешно до тех пор, пока dequeueReusableCell(…) возвращает экземпляр вашего подкласса. Вам нужно будет принять во внимание, что понижающее преобразование может завершиться ошибкой, поэтому вы должны использовать as? для корректной обработки ошибки, если она произойдет.

Читайте также:
Добавить комментарий

Ваш адрес email не будет опубликован.