{
    "version": "https:\/\/jsonfeed.org\/version\/1",
    "title": "\/\/ by kei_sidorov: posts tagged private frameworks",
    "_rss_description": "Личный блог для статеек",
    "_rss_language": "en",
    "_itunes_email": "",
    "_itunes_categories_xml": "",
    "_itunes_image": "",
    "_itunes_explicit": "",
    "home_page_url": "https:\/\/sidorov.tech\/tags\/private-frameworks\/",
    "feed_url": "https:\/\/sidorov.tech\/tags\/private-frameworks\/json\/",
    "icon": "https:\/\/sidorov.tech\/user\/userpic@2x.jpg?1729508804",
    "author": {
        "name": "Кирилл Сидоров",
        "url": "https:\/\/sidorov.tech\/",
        "avatar": "https:\/\/sidorov.tech\/user\/userpic@2x.jpg?1729508804"
    },
    "items": [
        {
            "id": "3",
            "url": "https:\/\/sidorov.tech\/all\/kopaem-vnutr-springboard\/",
            "title": "Копаем внутрь SpringBoard",
            "content_html": "<p>Я, наверное как и многие iOS и macOS разработчики, каждый год жду WWDC чтобы увидеть новые API, новые инструменты и улучшения существующих. Но помимо всего, связанного с разработкой, я жду саму ОС — хочу увидеть что для меня, как для обычного пользователя ОС, изменилось.<\/p>\n<p>В этом году произошло два больших изменения домашнего экрана которые у всех на слуху: добавили возможность размещать виджеты и представили библиотеку приложений. В первый же день я удалил весь хлам, бережливо расфасованный по папочкам «Other», «Utility» с домашнего экрана, оставив только то, чем пользуюсь каждый несколько раз в день на регулярной основе. Я уже давно не ищу глазами иконку нужного приложения, а пользуюсь поиском Spotlight, поэтому Apps Library стала для меня фишкой номер один в этом релизе.<\/p>\n<p>Помимо удобного для меня механизма организации и поиска приложений, я обратил внимание на верхний бар со строкой поиска. Мой взгляд зацепился за блюр который, который я ранее не встречал в системе. Это градиентный блюр, радиус размытия которого плавно меняется от 0 до заданного значения. Выглядит реально круто, особенно в динамике, просто попробуйте поскройлить список! Мне стало жутко интересно, можно ли и как сделать своими руками.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/sidorov.tech\/pictures\/all-library-gif.gif\" width=\"717\" height=\"468\" alt=\"\" \/>\n<\/div>\n<h2>Немного про блюр в iOS<\/h2>\n<p>Немного отвлечемся и поговорим про блюр. Несмотря на то, что Apple предоставляет разработчикам богатое API, позволяющее сделать блюр и другие эффекты для изображения, возможность делать это с живым UI существенно ограничены. Конечно всегда можно отрендерить слой, наложить на изображение эффекты и фильтры и показать это в ImageView, но как несложно догадаться, производительность такого решения будет оставлять лучшего. Всё, что остаётся — использовать дарованный с барского плеча UIVisualEffectView, который предоставляет возможность сделать эффект матового стекла с помощью блюра и поиграться с Vibrancy эффектом.<\/p>\n<div class=\"callout\"><div class=\"pin\"><p>☝️<\/p>\n<\/div><p>Несмотря на то, что Apple предоставляет разработчикам богатое API, позволяющее сделать блюр и другие эффекты для изображения, возможность делать это такое с живым UI существенно ограничены.<\/p>\n<\/div><h2>Начинаем раскопки<\/h2>\n<p>Итак мы поняли, что из коробки сделать ничего не получиться. Что делать, куда копать?<\/p>\n<p>Без джейлбрейка мы не можем подключиться к чужому процессу и сдампить иерархию вьюх чтобы покопаться и найти нужную нам вьюху. Придется искать в слепую. Благо у нас есть симулятор и возможность покопаться в некоторых системных файлах.<\/p>\n<p>Первым делом определимся где будем искать. Библиотека App Library находится на домашнем экране, значит нам нужно найти что-то связанное с процессом SpringBoard — приложением, которое ответственное за домашний экран, запуск приложений и всё с этим связанное. Попробуем его найти:<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/sidorov.tech\/pictures\/search_springboard@2x.png\" width=\"874\" height=\"391\" alt=\"\" \/>\n<\/div>\n<p>Удача! Попробуем загрузить бинарник в <a href=\"https:\/\/www.hopperapp.com\">Hopper Disassembler<\/a> чтобы посмотреть что там внутри. Надежды на то, что внутри что-то полезное мало, т.к. размер бинарника всего 140 КБайт. Дизассемблер встречает нас диалоговым окном с просьбой выбрать архитектуру, т.к. <a href=\"https:\/\/en.wikipedia.org\/wiki\/Fat_binary\">бинарник жирный<\/a>.<\/p>\n<div class=\"callout\"><div class=\"pin\"><p>☝️<\/p>\n<\/div><p>Ага! Значит Apple, начиная с Xcode 12 поставляет «потроха» скомпилированные под классический x86_64 и под новый ARM (Apple Silicon). Сразу становится понятным почему Xcode 12 занимает так много места!<\/p>\n<\/div><div class=\"e2-text-picture\">\n<img src=\"https:\/\/sidorov.tech\/pictures\/hopper_sb@2x.png\" width=\"1000\" height=\"654\" alt=\"\" \/>\n<\/div>\n<p>Как и ожидалось, внутри исполняемого файла ничего полезного нет. Лишь несколько процедур отвественных за старт. Значит интересное нам прячется где-то в системных фреймворках. Посмотрим с чем слинкован исполняемый файл.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/sidorov.tech\/pictures\/otool@2x.png\" width=\"1146\" height=\"355\" alt=\"\" \/>\n<\/div>\n<h2>Приватные фреймворки<\/h2>\n<p>Много анализировать не приходится, «кишки» SpringBoard’а находятся в одноименном приватном фреймворке. Вернемся вверх по дереву файлов до iOS.simruntime и поищем его относительно этой директории. А вот и папка с приватными библиотеками и нужный нам фреймворк! Грузим в дизассемблер.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/sidorov.tech\/pictures\/search_sym@2x.png\" width=\"1000\" height=\"654\" alt=\"\" \/>\n<\/div>\n<p>Сразу попытаемся найти какие-нибудь символы, связанные с AppLibrary. Что-то есть, но не жирно. Поиграемся с поиском попробуем найти вью контроллер этой библиотеки. Есть зацепка! По строке <samp>LibraryViewController<\/samp> находится сеттеры и геттеры этого контроллера для корневого SBIconController’а (не трудно догадаться его назначение), но реализацией тут не пахнет. Если её тут нет, значит она есть где-то в другом фреймворке, другого не дано. Попробуем посмотреть с otool.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/sidorov.tech\/pictures\/sb_otool@2x.png.jpg\" width=\"2560\" height=\"1453\" alt=\"\" \/>\n<\/div>\n<p>Ого! Этот фреймворк использует 170 внешних зависимостей! Методом перебора не справимся, нужен другой способ. В сегменте Mach-O файлов (коим является бинарник фреймворка) есть сегмент External Symbols, который содержит список внешних символов, которые содержаться во внешних библиотеках. Возвращаемся в Hopper и попробуем найти символ используя список отфильтрованный по <samp>libraryviewcontroller<\/samp>.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/sidorov.tech\/pictures\/objc_symb_ext_ref@2x.png\" width=\"1000\" height=\"654\" alt=\"\" \/>\n<\/div>\n<p>Вот и символ <samp>_OBJC_CLASS_$_SBHLibraryViewController<\/samp>, но нажатию на который мы можем увидеть, что объявлен он в соседнем фреймворке SpringBoardHome. Грузим его в Hopper! Теперь найдем и покопаемся в классе <samp>SBHLibraryViewController<\/samp>.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/sidorov.tech\/pictures\/blur_init@2x.png\" width=\"1000\" height=\"654\" alt=\"\" \/>\n<\/div>\n<p>Ноете что у вас MassiveViewController? Взгляните на заголовок этого класса: <a href=\"https:\/\/gist.github.com\/kei-sidorov\/ed4b9969ba238e25f019eef1aad05d99\">147 методов<\/a> вам готовы насмеять в лицо вашему вьюконтроллеру:)<\/p>\n<div class=\"callout\"><div class=\"pin\"><p>☝️<\/p>\n<\/div><p>Сдампить все все методы всех Objective-C классов внутри Mach-O файла поможет <a href=\"https:\/\/github.com\/nygard\/class-dump\">class-dump<\/a>.<\/p>\n<\/div><p><br \/><\/p>\n<p>Ладно, первым делом глянем метод <samp>init<\/samp>, переключившись в режим псевдокода — Hopper настолько хорош, что может попытаться сделать человекопонятный псевдокод из машинного.<\/p>\n<h2>Первая зацепка<\/h2>\n<p>Метод init дергает <samp>initWithCategoryMapProvider:<\/samp> который по сути инициализирует родительский контроллер <samp>SBNestingViewController<\/samp> через <samp>initWithNib:bundle:<\/samp> и определяет очередь неких событий. В <samp>initWithNib:bundle:<\/samp> передаются два nil’a что говорит о том, что инициализация вью происходит в коде. Вспоминаем жизней цикл вью контроллера (наканец-то пригодилось!) — для загрузки вью обычно используется <samp>loadView<\/samp>, посмотрим что там.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/sidorov.tech\/pictures\/load_view@2x.png\" width=\"683\" height=\"548\" alt=\"\" \/>\n<\/div>\n<p>Ага, метод переопределен и тут вызывается <samp>_setupIconTableViewController<\/samp>.<\/p>\n<p>Смотрим что внутри. Метод массивный, сильно углубляться не хочется. Посмотрим какие вьюхи или контроллеры там создаются. Просто поищем вхождения строки alloc.<\/p>\n<p>Находится три потенциальных класса <samp>SBHIconLibraryTableViewController<\/samp>, <samp>SBHLibraryPodFolderController<\/samp>, <samp>SBHLibrarySearchController<\/samp>. Самым перспективным кажется последний, т.к. строка в топ баре, который я хочу повторить есть поиска. Увы, никаких зацепок найти не удаётся. Следующим на очереди был <samp>SBHLibraryPodFolderController<\/samp>, но и там ничего интересного найти не удалось. Наверное я бы искал еще долго, если бы по счастливой случайности\/запарке я не стал стирать название целиком, а стер бекспейсом только слово Controller. Оказывается, есть еще  <samp>SBHLibraryPodFolderView<\/samp> у которого есть метод-геттер <samp>navigationBar<\/samp>! Да! Это то, что нам нужно; похоже мы нашли нужный нам класс — <samp>SBHFeatherBlurNavigationBar<\/samp>!<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/sidorov.tech\/pictures\/init_w_frame@2x.png\" width=\"697\" height=\"658\" alt=\"\" \/>\n<\/div>\n<p>И почему я раньше просто не поискал по строке Blur?! Ладно, не время себя винить, глянем что внутри. Первым делом видим initWithFrame, где создается <samp>SBHFeatherBlurView<\/samp> методом <samp>initWithRecipe:<\/samp> с параметром <samp>0x2<\/samp>.<\/p>\n<p>Hoper в сторону! Настало время экспериментов. Создадим новый проект в Xcode предварительно указав язык ObjectiveC. Накинем UIScrollView и какой нибудь контент для эксперимента. Не будем напрямую линковать фреймворк, загрузим его в рантайме.<\/p>\n<p>Далее воспользуемся возможностями рантайма ObjectiveC и создадим инстанс искомого класса. Добавим его на нашу вью и проверим верна ли наша догадка.  Специально для статьи я сделал пример похожий на оригинальный экран, чтобы было с чем сравнить. Чтож, запустим на симуляторе.<\/p>\n<pre class=\"e2-text-code\"><code class=\"objc\">\r\n#import \"FeatherBlurView.h\"\r\n#import <dlfcn.h>\r\n\r\n#define SBH_PATH @\"\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/iPhoneOS.platform\/Library\/Developer\/CoreSimulator\/Profiles\/Runtimes\/iOS.simruntime\/Contents\/Resources\/RuntimeRoot\/System\/Library\/PrivateFrameworks\/SpringBoardHome.framework\/SpringBoardHome\"\r\n\r\n\/\/ Объявим интерфейс класса из SpribngBoardHome для удобной инициализации\r\n@interface SBHFeatherBlurView : UIView\r\n- (instancetype) initWithRecipe:(int)arg1;\r\n@end\r\n\r\n@implementation FeatherBlurView\r\n\r\n+ (void)load {\r\n    const char *path = [SBH_PATH cStringUsingEncoding:NSUTF8StringEncoding];\r\n    dlopen(path, RTLD_NOW);\r\n}\r\n\r\n- (instancetype) init {\r\n    self = [super init];\r\n    if (self) {\r\n        SBHFeatherBlurView *view = [NSClassFromString(@\"SBHFeatherBlurView\") alloc];\r\n        view = [view initWithRecipe:0x2];\r\n        view.translatesAutoresizingMaskIntoConstraints = NO;\r\n        [self addSubview:view];\r\n        [[view.topAnchor constraintEqualToAnchor:self.topAnchor] setActive:YES];\r\n        [[view.bottomAnchor constraintEqualToAnchor:self.bottomAnchor] setActive:YES];\r\n        [[view.leftAnchor constraintEqualToAnchor:self.leftAnchor] setActive:YES];\r\n        [[view.rightAnchor constraintEqualToAnchor:self.rightAnchor] setActive:YES];\r\n    }\r\n    return self;\r\n}\r\n\r\n@end\r\n<\/code>\n<\/pre>\n<h2>Первая победа!<\/h2>\n<p><a href=\"https:\/\/github.com\/kei-sidorov\/feather-blur-sample\">📦 Тестовый проект доступен на GitHub<\/a><\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/sidorov.tech\/pictures\/Result@2x.png\" width=\"600\" height=\"582\" alt=\"\" \/>\n<\/div>\n<p>Йоу! Всё получилось и работает как я и хотел! Можете <a href=\"https:\/\/github.com\/kei-sidorov\/feather-blur-sample\">скачать тестовый проект<\/a> и поиграться самостоятельно.<\/p>\n<div class=\"callout\"><div class=\"pin\"><p>🖖<\/p>\n<\/div><p>Время подвести некоторые выводы и подчеркнуть интересные факты<\/p>\n<ul>\n<li>Изучать внутренности ОС не так сложно и очень интересно<\/li>\n<li>Знания Objective-C и жизненного цикла вью контроллера могут пригодиться в 2k20<\/li>\n<li>Все фреймворки и утилиты внутри Xcode поставляются в виде жирных бинарников, собранных под ARM и x86_64, это частично объясняет увеличенный почти вдвое размер Xcode<\/li>\n<li>Несмотря на 2020 год и актуальный Swift версии 5.4, Apple активно использует Objective-C в системных фреймворках. Даже код для управления виджетами (которые, на секунду SwiftUI-only) написан на Objective-C<\/li>\n<li>В Apple не бояться Massive View Controller и глубокого наследования<\/li>\n<\/ul>\n<\/div><p><br \/><\/p>\n<p>Но неужели так всё просто и такой блюр можно использовать в своих проектах?! К сожалению, нет. Дело даже не в использовании приватных библиотек, что запрещает Apple, а дело в том, что у нас в принципе нет возможности использовать внешние фреймворки находясь в песочнице. То есть даже сейчас запустить на девайсе это не получиться. Но это не повод опускать руки! Уже в следующей статье мы поковыряемся во внутренностях этой вью и попытаемся воссоздать её так, чтобы код можно было запустить и на девайсе! Stay tuned!<\/p>\n<h2>iOS 15<\/h2>\n<p><samp>SBHFeatherBlurView<\/samp> был перемещен в новый фреймворк <samp>SpringBoardFoundation<\/samp> и переименован в <samp>SBFFeatherBlurView<\/samp>.<\/p>\n",
            "date_published": "2020-11-15T17:29:35+00:00",
            "date_modified": "2022-04-18T05:49:53+00:00",
            "image": "https:\/\/sidorov.tech\/pictures\/all-library-gif.gif",
            "_date_published_rfc2822": "Sun, 15 Nov 2020 17:29:35 +0000",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "3",
            "_e2_data": {
                "is_favourite": true,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/sidorov.tech\/pictures\/all-library-gif.gif",
                    "https:\/\/sidorov.tech\/pictures\/search_springboard@2x.png",
                    "https:\/\/sidorov.tech\/pictures\/hopper_sb@2x.png",
                    "https:\/\/sidorov.tech\/pictures\/otool@2x.png",
                    "https:\/\/sidorov.tech\/pictures\/search_sym@2x.png",
                    "https:\/\/sidorov.tech\/pictures\/sb_otool@2x.png.jpg",
                    "https:\/\/sidorov.tech\/pictures\/objc_symb_ext_ref@2x.png",
                    "https:\/\/sidorov.tech\/pictures\/blur_init@2x.png",
                    "https:\/\/sidorov.tech\/pictures\/load_view@2x.png",
                    "https:\/\/sidorov.tech\/pictures\/init_w_frame@2x.png",
                    "https:\/\/sidorov.tech\/pictures\/Result@2x.png"
                ]
            }
        }
    ],
    "_e2_version": 3576,
    "_e2_ua_string": "E2 (v3576; Aegea)"
}