<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0"
  xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
  xmlns:atom="http://www.w3.org/2005/Atom">

<channel>

<title>// by kei_sidorov: posts tagged private frameworks</title>
<link>https://sidorov.tech/tags/private-frameworks/</link>
<description>Личный блог для статеек</description>
<author>Кирилл Сидоров</author>
<language>en</language>
<generator>E2 (v3576; Aegea)</generator>

<itunes:owner>
<itunes:name>Кирилл Сидоров</itunes:name>
<itunes:email></itunes:email>
</itunes:owner>
<itunes:subtitle>Личный блог для статеек</itunes:subtitle>
<itunes:image href="" />
<itunes:explicit></itunes:explicit>

<item>
<title>Копаем внутрь SpringBoard</title>
<guid isPermaLink="false">3</guid>
<link>https://sidorov.tech/all/kopaem-vnutr-springboard/</link>
<pubDate>Sun, 15 Nov 2020 17:29:35 +0000</pubDate>
<author>Кирилл Сидоров</author>
<comments>https://sidorov.tech/all/kopaem-vnutr-springboard/</comments>
<description>
&lt;p&gt;Я, наверное как и многие iOS и macOS разработчики, каждый год жду WWDC чтобы увидеть новые API, новые инструменты и улучшения существующих. Но помимо всего, связанного с разработкой, я жду саму ОС — хочу увидеть что для меня, как для обычного пользователя ОС, изменилось.&lt;/p&gt;
&lt;p&gt;В этом году произошло два больших изменения домашнего экрана которые у всех на слуху: добавили возможность размещать виджеты и представили библиотеку приложений. В первый же день я удалил весь хлам, бережливо расфасованный по папочкам «Other», «Utility» с домашнего экрана, оставив только то, чем пользуюсь каждый несколько раз в день на регулярной основе. Я уже давно не ищу глазами иконку нужного приложения, а пользуюсь поиском Spotlight, поэтому Apps Library стала для меня фишкой номер один в этом релизе.&lt;/p&gt;
&lt;p&gt;Помимо удобного для меня механизма организации и поиска приложений, я обратил внимание на верхний бар со строкой поиска. Мой взгляд зацепился за блюр который, который я ранее не встречал в системе. Это градиентный блюр, радиус размытия которого плавно меняется от 0 до заданного значения. Выглядит реально круто, особенно в динамике, просто попробуйте поскройлить список! Мне стало жутко интересно, можно ли и как сделать своими руками.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://sidorov.tech/pictures/all-library-gif.gif" width="717" height="468" alt="" /&gt;
&lt;/div&gt;
&lt;h2&gt;Немного про блюр в iOS&lt;/h2&gt;
&lt;p&gt;Немного отвлечемся и поговорим про блюр. Несмотря на то, что Apple предоставляет разработчикам богатое API, позволяющее сделать блюр и другие эффекты для изображения, возможность делать это с живым UI существенно ограничены. Конечно всегда можно отрендерить слой, наложить на изображение эффекты и фильтры и показать это в ImageView, но как несложно догадаться, производительность такого решения будет оставлять лучшего. Всё, что остаётся — использовать дарованный с барского плеча UIVisualEffectView, который предоставляет возможность сделать эффект матового стекла с помощью блюра и поиграться с Vibrancy эффектом.&lt;/p&gt;
&lt;div class="callout"&gt;&lt;div class="pin"&gt;&lt;p&gt;☝️&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;Несмотря на то, что Apple предоставляет разработчикам богатое API, позволяющее сделать блюр и другие эффекты для изображения, возможность делать это такое с живым UI существенно ограничены.&lt;/p&gt;
&lt;/div&gt;&lt;h2&gt;Начинаем раскопки&lt;/h2&gt;
&lt;p&gt;Итак мы поняли, что из коробки сделать ничего не получиться. Что делать, куда копать?&lt;/p&gt;
&lt;p&gt;Без джейлбрейка мы не можем подключиться к чужому процессу и сдампить иерархию вьюх чтобы покопаться и найти нужную нам вьюху. Придется искать в слепую. Благо у нас есть симулятор и возможность покопаться в некоторых системных файлах.&lt;/p&gt;
&lt;p&gt;Первым делом определимся где будем искать. Библиотека App Library находится на домашнем экране, значит нам нужно найти что-то связанное с процессом SpringBoard — приложением, которое ответственное за домашний экран, запуск приложений и всё с этим связанное. Попробуем его найти:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://sidorov.tech/pictures/search_springboard@2x.png" width="874" height="391" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Удача! Попробуем загрузить бинарник в &lt;a href="https://www.hopperapp.com"&gt;Hopper Disassembler&lt;/a&gt; чтобы посмотреть что там внутри. Надежды на то, что внутри что-то полезное мало, т.к. размер бинарника всего 140 КБайт. Дизассемблер встречает нас диалоговым окном с просьбой выбрать архитектуру, т.к. &lt;a href="https://en.wikipedia.org/wiki/Fat_binary"&gt;бинарник жирный&lt;/a&gt;.&lt;/p&gt;
&lt;div class="callout"&gt;&lt;div class="pin"&gt;&lt;p&gt;☝️&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;Ага! Значит Apple, начиная с Xcode 12 поставляет «потроха» скомпилированные под классический x86_64 и под новый ARM (Apple Silicon). Сразу становится понятным почему Xcode 12 занимает так много места!&lt;/p&gt;
&lt;/div&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://sidorov.tech/pictures/hopper_sb@2x.png" width="1000" height="654" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Как и ожидалось, внутри исполняемого файла ничего полезного нет. Лишь несколько процедур отвественных за старт. Значит интересное нам прячется где-то в системных фреймворках. Посмотрим с чем слинкован исполняемый файл.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://sidorov.tech/pictures/otool@2x.png" width="1146" height="355" alt="" /&gt;
&lt;/div&gt;
&lt;h2&gt;Приватные фреймворки&lt;/h2&gt;
&lt;p&gt;Много анализировать не приходится, «кишки» SpringBoard’а находятся в одноименном приватном фреймворке. Вернемся вверх по дереву файлов до iOS.simruntime и поищем его относительно этой директории. А вот и папка с приватными библиотеками и нужный нам фреймворк! Грузим в дизассемблер.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://sidorov.tech/pictures/search_sym@2x.png" width="1000" height="654" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Сразу попытаемся найти какие-нибудь символы, связанные с AppLibrary. Что-то есть, но не жирно. Поиграемся с поиском попробуем найти вью контроллер этой библиотеки. Есть зацепка! По строке &lt;samp&gt;LibraryViewController&lt;/samp&gt; находится сеттеры и геттеры этого контроллера для корневого SBIconController’а (не трудно догадаться его назначение), но реализацией тут не пахнет. Если её тут нет, значит она есть где-то в другом фреймворке, другого не дано. Попробуем посмотреть с otool.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://sidorov.tech/pictures/sb_otool@2x.png.jpg" width="2560" height="1453" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Ого! Этот фреймворк использует 170 внешних зависимостей! Методом перебора не справимся, нужен другой способ. В сегменте Mach-O файлов (коим является бинарник фреймворка) есть сегмент External Symbols, который содержит список внешних символов, которые содержаться во внешних библиотеках. Возвращаемся в Hopper и попробуем найти символ используя список отфильтрованный по &lt;samp&gt;libraryviewcontroller&lt;/samp&gt;.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://sidorov.tech/pictures/objc_symb_ext_ref@2x.png" width="1000" height="654" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Вот и символ &lt;samp&gt;_OBJC_CLASS_$_SBHLibraryViewController&lt;/samp&gt;, но нажатию на который мы можем увидеть, что объявлен он в соседнем фреймворке SpringBoardHome. Грузим его в Hopper! Теперь найдем и покопаемся в классе &lt;samp&gt;SBHLibraryViewController&lt;/samp&gt;.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://sidorov.tech/pictures/blur_init@2x.png" width="1000" height="654" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Ноете что у вас MassiveViewController? Взгляните на заголовок этого класса: &lt;a href="https://gist.github.com/kei-sidorov/ed4b9969ba238e25f019eef1aad05d99"&gt;147 методов&lt;/a&gt; вам готовы насмеять в лицо вашему вьюконтроллеру:)&lt;/p&gt;
&lt;div class="callout"&gt;&lt;div class="pin"&gt;&lt;p&gt;☝️&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;Сдампить все все методы всех Objective-C классов внутри Mach-O файла поможет &lt;a href="https://github.com/nygard/class-dump"&gt;class-dump&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Ладно, первым делом глянем метод &lt;samp&gt;init&lt;/samp&gt;, переключившись в режим псевдокода — Hopper настолько хорош, что может попытаться сделать человекопонятный псевдокод из машинного.&lt;/p&gt;
&lt;h2&gt;Первая зацепка&lt;/h2&gt;
&lt;p&gt;Метод init дергает &lt;samp&gt;initWithCategoryMapProvider:&lt;/samp&gt; который по сути инициализирует родительский контроллер &lt;samp&gt;SBNestingViewController&lt;/samp&gt; через &lt;samp&gt;initWithNib:bundle:&lt;/samp&gt; и определяет очередь неких событий. В &lt;samp&gt;initWithNib:bundle:&lt;/samp&gt; передаются два nil’a что говорит о том, что инициализация вью происходит в коде. Вспоминаем жизней цикл вью контроллера (наканец-то пригодилось!) — для загрузки вью обычно используется &lt;samp&gt;loadView&lt;/samp&gt;, посмотрим что там.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://sidorov.tech/pictures/load_view@2x.png" width="683" height="548" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Ага, метод переопределен и тут вызывается &lt;samp&gt;_setupIconTableViewController&lt;/samp&gt;.&lt;/p&gt;
&lt;p&gt;Смотрим что внутри. Метод массивный, сильно углубляться не хочется. Посмотрим какие вьюхи или контроллеры там создаются. Просто поищем вхождения строки alloc.&lt;/p&gt;
&lt;p&gt;Находится три потенциальных класса &lt;samp&gt;SBHIconLibraryTableViewController&lt;/samp&gt;, &lt;samp&gt;SBHLibraryPodFolderController&lt;/samp&gt;, &lt;samp&gt;SBHLibrarySearchController&lt;/samp&gt;. Самым перспективным кажется последний, т.к. строка в топ баре, который я хочу повторить есть поиска. Увы, никаких зацепок найти не удаётся. Следующим на очереди был &lt;samp&gt;SBHLibraryPodFolderController&lt;/samp&gt;, но и там ничего интересного найти не удалось. Наверное я бы искал еще долго, если бы по счастливой случайности/запарке я не стал стирать название целиком, а стер бекспейсом только слово Controller. Оказывается, есть еще  &lt;samp&gt;SBHLibraryPodFolderView&lt;/samp&gt; у которого есть метод-геттер &lt;samp&gt;navigationBar&lt;/samp&gt;! Да! Это то, что нам нужно; похоже мы нашли нужный нам класс — &lt;samp&gt;SBHFeatherBlurNavigationBar&lt;/samp&gt;!&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://sidorov.tech/pictures/init_w_frame@2x.png" width="697" height="658" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;И почему я раньше просто не поискал по строке Blur?! Ладно, не время себя винить, глянем что внутри. Первым делом видим initWithFrame, где создается &lt;samp&gt;SBHFeatherBlurView&lt;/samp&gt; методом &lt;samp&gt;initWithRecipe:&lt;/samp&gt; с параметром &lt;samp&gt;0x2&lt;/samp&gt;.&lt;/p&gt;
&lt;p&gt;Hoper в сторону! Настало время экспериментов. Создадим новый проект в Xcode предварительно указав язык ObjectiveC. Накинем UIScrollView и какой нибудь контент для эксперимента. Не будем напрямую линковать фреймворк, загрузим его в рантайме.&lt;/p&gt;
&lt;p&gt;Далее воспользуемся возможностями рантайма ObjectiveC и создадим инстанс искомого класса. Добавим его на нашу вью и проверим верна ли наша догадка.  Специально для статьи я сделал пример похожий на оригинальный экран, чтобы было с чем сравнить. Чтож, запустим на симуляторе.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="objc"&gt;
#import "FeatherBlurView.h"
#import &lt;dlfcn.h&gt;

#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"

// Объявим интерфейс класса из SpribngBoardHome для удобной инициализации
@interface SBHFeatherBlurView : UIView
- (instancetype) initWithRecipe:(int)arg1;
@end

@implementation FeatherBlurView

+ (void)load {
    const char *path = [SBH_PATH cStringUsingEncoding:NSUTF8StringEncoding];
    dlopen(path, RTLD_NOW);
}

- (instancetype) init {
    self = [super init];
    if (self) {
        SBHFeatherBlurView *view = [NSClassFromString(@"SBHFeatherBlurView") alloc];
        view = [view initWithRecipe:0x2];
        view.translatesAutoresizingMaskIntoConstraints = NO;
        [self addSubview:view];
        [[view.topAnchor constraintEqualToAnchor:self.topAnchor] setActive:YES];
        [[view.bottomAnchor constraintEqualToAnchor:self.bottomAnchor] setActive:YES];
        [[view.leftAnchor constraintEqualToAnchor:self.leftAnchor] setActive:YES];
        [[view.rightAnchor constraintEqualToAnchor:self.rightAnchor] setActive:YES];
    }
    return self;
}

@end
&lt;/code&gt;
&lt;/pre&gt;
&lt;h2&gt;Первая победа!&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/kei-sidorov/feather-blur-sample"&gt;📦 Тестовый проект доступен на GitHub&lt;/a&gt;&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://sidorov.tech/pictures/Result@2x.png" width="600" height="582" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Йоу! Всё получилось и работает как я и хотел! Можете &lt;a href="https://github.com/kei-sidorov/feather-blur-sample"&gt;скачать тестовый проект&lt;/a&gt; и поиграться самостоятельно.&lt;/p&gt;
&lt;div class="callout"&gt;&lt;div class="pin"&gt;&lt;p&gt;🖖&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;Время подвести некоторые выводы и подчеркнуть интересные факты&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Изучать внутренности ОС не так сложно и очень интересно&lt;/li&gt;
&lt;li&gt;Знания Objective-C и жизненного цикла вью контроллера могут пригодиться в 2k20&lt;/li&gt;
&lt;li&gt;Все фреймворки и утилиты внутри Xcode поставляются в виде жирных бинарников, собранных под ARM и x86_64, это частично объясняет увеличенный почти вдвое размер Xcode&lt;/li&gt;
&lt;li&gt;Несмотря на 2020 год и актуальный Swift версии 5.4, Apple активно использует Objective-C в системных фреймворках. Даже код для управления виджетами (которые, на секунду SwiftUI-only) написан на Objective-C&lt;/li&gt;
&lt;li&gt;В Apple не бояться Massive View Controller и глубокого наследования&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Но неужели так всё просто и такой блюр можно использовать в своих проектах?! К сожалению, нет. Дело даже не в использовании приватных библиотек, что запрещает Apple, а дело в том, что у нас в принципе нет возможности использовать внешние фреймворки находясь в песочнице. То есть даже сейчас запустить на девайсе это не получиться. Но это не повод опускать руки! Уже в следующей статье мы поковыряемся во внутренностях этой вью и попытаемся воссоздать её так, чтобы код можно было запустить и на девайсе! Stay tuned!&lt;/p&gt;
&lt;h2&gt;iOS 15&lt;/h2&gt;
&lt;p&gt;&lt;samp&gt;SBHFeatherBlurView&lt;/samp&gt; был перемещен в новый фреймворк &lt;samp&gt;SpringBoardFoundation&lt;/samp&gt; и переименован в &lt;samp&gt;SBFFeatherBlurView&lt;/samp&gt;.&lt;/p&gt;
</description>
</item>


</channel>
</rss>