← Назад к вопросам

Что запускается перед функцией main при запуске приложения?

1.6 Junior🔥 101 комментариев
#UIKit и верстка

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

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Загрузка и инициализация приложения до main()

В iOS/macOS приложениях, написанных на Objective-C или Swift, действительно существует значительный объем кодa, который выполняется до входа в функцию main(). Этот процесс обеспечивает подготовку среды выполнения и является фундаментальным для работы приложения. Его можно разделить на несколько ключевых этапов.

1. Точка входа: _start() и системная загрузка

Самым первым выполняется код, который даже не принадлежит вашему приложению. Динамический линкер dyld (Dynamic Link Editor) загружает исполняемый файл и все зависящие от него динамические библиотеки (включая фундаментальные libSystem.dylib, содержащую саму стандартную библиотеку C libc).

  • Операционная система создает процесс и передает управление точке входа исполняемого файла, которая обычно называется _start() (она находится в скомпилированном бинарнике вашего приложения).
  • _start() подготавливает аргументы командной строки (argc, argv) и среду, а затем вызывает стандартную функцию main() языка C. Но в iOS/macOS приложениях эта функция переопределена.

2. Роль dyld

dyld выполняет критически важную работу:

  • Рекурсивная загрузка зависимостей: Загружает все фреймворки и библиотеки, указанные в LC_LOAD_DYLIB команд файла Mach-O.
  • Связывание (Linking): Выполняет разрешение символов (поиск реальных адресов функций и переменных) — как "ленивое" (lazy binding), так и "не-ленивое" (non-lazy binding).
  • Запуск статических инициализаторов: Выполняет функции, помеченные как конструкторы (например, функции с атрибутом __attribute__((constructor)) в C, или статические инициализаторы глобальных объектов C++/Swift).

3. Фактическая функция main() в iOS приложении

В коде, сгенерированном Xcode для приложения, main() выглядит очень просто. Ее основная задача — вызвать UIApplicationMain (или NSApplicationMain для macOS).

// Традиционный Objective-C main.m
#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        // Эта функция запускает главный цикл приложения.
        // Она НИКОГДА не возвращает управление.
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
// В Swift, начиная с версии 5.3 @main атрибут заменяет явный main.swift файл
// Точкой входа становится класс, помеченный @main

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    // ... 
}

Ключевой момент: Сама функция UIApplicationMain является частью фреймворка UIKit/AppKit и вызывается из main(), но до того, как она запустит главный цикл приложения (run loop), происходит еще несколько важных шагов.

4. Инициализация Objective-C Runtime и Swift Runtime

Это один из самых важных этапов, происходящих внутри UIApplicationMain, но до вызова делегата.

  • Objective-C Runtime:
    *   **Регистрация классов:** Динамически загружаются и регистрируются все классы из скомпилированных бинарников.
    *   **Настройка категорий (Categories):** Методы из категорий "пришиваются" к оригинальным классам.
    *   **Настройка селекторов:** Уникализация строк-селекторов (`@selector()`).
    *   **Вызов `+load` методов (УСТАРЕЛО):** Раньше вызывались все классовые и категорийные методы `+load`. **Важно:** Начиная с iOS 12/macOS 10.13, `+load` больше не поддерживается для Swift классов, а его использование в Objective-C не рекомендуется. Основной механизм инициализации сейчас — это `+initialize`.

  • Swift Runtime:
    *   Инициализация глобальных переменных Swift (которые могут иметь сложные конструкторы).
    *   Настройка таблиц виртуальных методов (Witness Tables для протоколов, VTables для классов).
    *   Вызов инициализаторов статических/глобальных структур и классов Swift.

5. Вызов +initialize методов

Перед первым использованием любого класса Objective-C/Swift (наследующего от NSObject) у него вызывается классовый метод +initialize. Это последний шаг подготовки среды выполнения перед началом работы прикладного кода. Все вызовы +initialize гарантированно завершаются до того, как будет вызван первый метод делегата приложения.

6. Начало работы прикладного кода

Только после всего описанного выше UIApplicationMain:

  1. Создает экземпляр главного класса приложения (UIApplication).
  2. Создает экземпляр вашего AppDelegate (или класса, помеченного @main).
  3. Вызывает у него первый метод жизненного цикла — application(_:didFinishLaunchingWithOptions:).

Краткая последовательность в виде списка

  1. Система загружает бинарник и запускает dyld.
  2. dyld загружает зависимости и выполняет связывание.
  3. dyld вызывает статические конструкторы C/C++ и инициализирует глобальные/статические данные Swift.
  4. Вызывается стандартная main() функция приложения.
  5. UIApplicationMain запускает инициализацию Objective-C/Swift Runtime (регистрация классов и т.д.).
  6. Вызываются все необходимые +initialize методы классов.
  7. UIApplicationMain создает экземпляр приложения и AppDelegate.
  8. Вызывается application(_:didFinishLaunchingWithOptions:) — с этого момента начинается выполнение вашего кода.

Таким образом, функция main() — это лишь формальный рубеж в длинной цепочке подготовки, которая обеспечивает корректную работу всех высокоуровневых механизмов Objective-C и Swift, таких как динамическая диспетчеризация, отложенная загрузка библиотек и богатая система времени выполнения.