Протокол UIAppearance появился в iOS 5 в 2011 году, в те далёкие времена, когда у Instagram не было приложения под Android, а сериальному Неду Старку ещё не отрубили голову.


Насчёт Эддарда

Мейстеры мне прислали ворона с новостью, что на момент выхода iOS 5 — уже отрубили. Но чтобы не спойлерить красную свадьбу или что-то ещё, пожалуй, оставлю всё как есть.


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


В качестве небольшой вводной — выдержки из документации, которая всегда была, есть и должна быть основным доверенным источником информации.


You can customize the appearance of instances of a class by sending appearance modification messages to the class’s appearance proxy.

To support appearance customization, a class must conform to the UIAppearanceContainer protocol and relevant accessor methods must be marked with UI_APPEARANCE_SELECTOR

Для себя я понимаю это так: если хочешь изменить внешний вид по умолчанию для всех объектов класса, реализующего UIAppearance, проверь, помечено ли свойство с помощью UI_APPEARANCE_SELECTOR, и вперёд. Соответственно, если свойство не является UI_APPEARANCE_SELECTOR, то это сделать не получится.


Но "не получится" не значит "нельзя попытаться", поэтому я предлагаю всем любопытным провести простой эксперимент: откройте первый попавшийся проект и добавьте в метод application:didFinishLaunchingWithOptions: следующую строчку:


UIView.appearance().isHidden = true

Прикиньте, что произойдёт, и запустите.


Я, как зануда, ожидал бы возможные варианты:


  • ничего не происходит;
  • ничего не происходит, но в консоль вываливается лог типа "isHidden is not UI_APPEARANCE_SELECTOR, stupid";
  • приложение ловит критическую ошибку или ассерт с подобным сообщением.

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


Я ещё мог бы смириться с тем, если бы такие фокусы можно было проворачивать только с теми свойствами, которые влияют на внешний вид, но это можно делать со всеми свойствами!


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


UITableView.appearance().delegate = nil

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


Разработчики получат много удовольствия, пытаться понять, что случилось! Наверняка, можно придумать и что-то ещё более забавное!


Я очень надеюсь, что подобные вещи всё-таки не остались без внимания Apple (информации на эту тему, правда, я не нашёл), и подобные фокусы вскрываются где-нибудь на этапе автоматической проверки сборки в AppstoreConnect. Но проверять, если честно, не хочется.


Такие дела, ребята. Буду рад дискуссии, если тема кому-то тоже интересна.


P. S. Верните мне мой 2011!

Комментарии (3)


  1. yvm
    23.05.2019 14:55
    +1

    Спойлеры порой поджидают в самых неожиданных местах…


  1. storoj
    24.05.2019 01:23

    UIAppearance запоминает все NSInvocation, произошедшие с appearance-объектом, и потом воспроизводит их на реальных инстансах. Поэтому всё должно работать с вообще любыми вызовами любых селекторов. Но отсюда побочный эффект: если к примеру сделать два раза "setColor:" с разными цветами, то и на реальном инстансе произойдёт оба этих вызова. Поэтому, например, переключение разноцветных тем в приложении сделать не получится: при переключениях тем эти NSInvocation будут накапливаться и накапливаться. В принципе, всё это можно понять просто распечатав appearance-proxy объект в отладчике (или распечатав его _ivarDescription, уже не вспомнить)


    1. tralf Автор
      24.05.2019 13:53

      Спасибо за дополнение.
      Однако, это всего лишь ответ на вопрос «почему это происходит». Ответа на вопрос «как это вообще допустили» он, к сожалению, не даёт.

      Самый главный момент, от которого у меня бомбит, — это бесполезность флага UI_APPEARANCE_SELECTOR, который по сути — не более, чем просто пометка, что с помеченным свойством можно спокойно использовать Appearance, а с не помеченным — тоже можно, но никто ничего не гарантирует.

      Но в отличие от тех же force unwrap в Swift, которые тоже можно использовать на свой страх и риск, неправильное использование свойства через Appearance, легко может поломать не только твой код, но и код в компонентах, которыми ты не владеешь. Без регистрации, смс и свизлинга.