UIAppearance оказался не так прост

    Протокол 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!

    • +10
    • 1,6k
    • 3
    Поделиться публикацией

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

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

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

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

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

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

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

        Самое читаемое