Objective-C — език за програмиране с динамично изпращане на съобщения, създаден от Брад Кокс през 80-те години на XX век. Apple избра Objective-C като основен език за NeXTSTEP, а след това и за iOS SDK. Programming With Objective-C — въведение в синтаксиса на съобщенията и управлението на паметта.
Основни точки
Objective-C — език за програмиране, който разширява C с възможности за обектно-ориентирано програмиране в стил Smalltalk. Кодът на Objective-C се компилира чрез LLVM или GCC до нативен машинен код, запазвайки пълна обратна съвместимост с C — всеки C код е коректен в Objective-C.
Apple придоби NeXT (заедно с Objective-C) през 1997 г. Езикът стана основа на Cocoa и Cocoa Touch — рамките за macOS и iOS. От 2014 г. Apple популяризира Swift като замяна, но Objective-C остава критично важен за legacy проекти и някои системни рамки. Според Apple (WWDC 2024), около 35% от приложенията в App Store все още съдържат Objective-C код.
Ключова характеристика — динамичен runtime. За разлика от Swift, където извикванията на методи се разрешават на етапа на компилация, Objective-C изпраща съобщения по време на изпълнение чрез функцията objc_msgSend. Това позволява предефиниране на методи в движение (method swizzling), динамично добавяне на класове и използване на forward invocation за несъществуващи селектори.
Всеки C код е коректен в Objective-C. Обектната надстройка добавя класове (@interface/@implementation), категории, протоколи (@protocol) и динамично типизиране (id). Файловете имат разширения .m (имплементация) и .h (заглавни файлове).
Синтаксис на съобщения — основната разлика на Objective-C от C-подобните езици. Вместо object.method(argument) се използва [object method:argument]. Всяко съобщение преминава през objc_msgSend, който динамично търси имплементацията на метода в runtime.
// Синтаксис на съобщения с именувани параметри
NSString *greeting = [NSString stringWithFormat:@"Hello, %@", name];
// Вложени съобщения
NSArray *sortedArray = [[array sortedArrayUsingSelector:@selector(compare:)] copy];
// Проверка на тип чрез интроспекция
if ([object isKindOfClass:[UIView class]]) {
UIView *view = (UIView *)object;
view.backgroundColor = [UIColor redColor];
}Dynamic dispatch позволява предефиниране на методи в runtime (method swizzling) — мощна, но опасна техника. Например рамки като AFNetworking и Aspects използват swizzling за прихващане на извиквания на URLSession. Apple предупреждава: swizzling може да наруши работата на системни рамки при неправилна употреба.
Categories — уникална възможност на Objective-C да добавя методи към съществуващи класове (включително системни, като NSString и UIView) без наследяване и без достъп до изходния код. Категориите се декларират чрез @interface ClassName (CategoryName).
// Файл UIColor+Hex.h — категория за UIColor
@interface UIColor (Hex)
+ (instancetype)colorWithHex:NSUIntegerhex;
@end
// Файл UIColor+Hex.m — имплементация
@implementation UIColor (Hex)
+ (instancetype)colorWithHex:NSUIntegerhex {
CGFloat r = ((hex >> 16) & 0xFF) / 255.0;
CGFloat g = ((hex >> 8) & 0xFF) / 255.0;
CGFloat b = (hex & 0xFF) / 255.0;
return [self colorWithRed:r green:g blue:b alpha:1.0];
}
@endExtensions (Class Extension) — специален вид категория, декларирана в .m файл без име: @interface ClassName (). За разлика от категориите, extensions могат да добавят не само методи, но и ivar (променливи на инстанцията) и property. Extensions се използват за скриване на вътрешно API от външни модули.
Blocks — анонимни функции в Objective-C, които улавят променливи от външния обхват на видимост. Синтаксис на блоковете: ^(int x) { return x * 2; }. Blocks се използват в UIKit за callback обработчици, в Grand Central Dispatch за асинхронни задачи и в колекциите NSArray/NSDictionary за функционални операции.
// Декларация на тип блок
typedef void (^CompletionBlock)(BOOL success, NSError *error);
// Блок като параметър
- (void)fetchDataWithCompletion:(CompletionBlock)completion {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL result = [weakSelf processData];
if (completion) {
completion(result, nil);
}
});
}Управление на паметта в блоковете е критично. Блоковете улавят self със силна референция, което създава retain cycle при директна употреба. Решение — __weak typeof(self) weakSelf = self, с последваща проверка вътре в блока. Този проблем е напълно решен в Swift чрез capture list [weak self].
Objective-C е изминал пътя от MRC (Manual Reference Counting) до ARC (Automatic Reference Counting). В MRC програмистът ръчно извикваше retain (увеличаване на брояча), release (намаляване) и autorelease (отложено освобождаване). Грешките в тези извиквания водеха до изтичане на памет или сривове поради dangling pointers.
| Операция | MRC | ARC |
|---|---|---|
| Създаване на обект | [[Object alloc] init] | [[Object alloc] init] |
| Задържане на референция | [object retain] | Автоматично |
| Освобождаване | [object release] | Автоматично |
| Автоматично освобождаване | [object autorelease] | Автоматично (не се изисква) |
| Weak референция | __weak не съществуваше | __weak (автоматично nil) |
ARC беше въведен в Xcode 4.2 и LLVM 3.0 (2011). Компилаторът автоматично вмъква retain/release на етапа на компилация, анализирайки жизнения цикъл на обектите. ARC не е събиране на боклук (garbage collection) — това е статичен анализ с автоматични вмъквания. Objective-C ARC е съвместим със Swift ARC: и двата използват една и съща система за преброяване на референции на ниво runtime.
Съвместното използване на Objective-C и Swift в един проект — обичайна практика за legacy проекти. Apple предоставя Bridging Header — автоматично генериран файл, чрез който Swift вижда Objective-C класове, а Objective-C вижда Swift класове, наследени от NSObject.
// ProjectName-Bridging-Header.h
// Swift вижда тези заглавни файлове автоматично
#import "LegacyManager.h"
#import "NetworkClient.h"
#import "DataStore.h"Миграция от Objective-C към Swift — поетапен процес. Новите файлове се пишат на Swift, старите постепенно се рефакторират. Bridging Header се генерира автоматично при добавяне на първия Swift файл към Objective-C проект. За обратна видимост (ObjC → Swift) Xcode генерира файла
// Swift клас, видим от Objective-C
@objc class SwiftRouter: NSObject {
@objc func navigateToProfile(userId: Int) {
// имплементация
}
}Ограничения: Swift value типове (struct, enum) не се виждат директно от Objective-C — трябва да се обвият в class с @objc. Generics в Swift са ограничено достъпни от Objective-C. Препоръчителен подход — пишете нов код на Swift, а съществуващия Objective-C код рефакторирайте само при промяна на съответната функционалност.
Основното предимство на Objective-C пред Swift — пълен достъп до runtime. Method swizzling, замяна на имплементации на методи по време на изпълнение, позволява предефиниране на системни методи без наследяване. Библиотеки като Aspects и JRSwizzle използват тази възможност за AOP (аспектно-ориентирано програмиране) — мониторинг, аналитика и логване.
Forward invocation — още една runtime възможност: ако обект не отговаря на селектор, системата извиква forwardInvocation:, позволявайки пренасочване на съобщението към друг обект. Това е основата на Proxy модела в Objective-C (NSProxy), използван за отложена инициализация и разпределени обекти.
Apple препоръчва да се минимизира използването на runtime трикове в нов код, предпочитайки статичното типизиране на Swift. Въпреки това, в legacy проекти познаването на objc_msgSend, method_exchangeImplementations и objc_getAssociatedObject е необходимо за поддръжка на съществуващата кодова база.
Objective-C поддържа property като синтактична захар над ivar с getters/setters. Модификаторите atomic/nonatomic, strong/weak/copy, readonly/readwrite и assign/retain определят поведението на паметта и нишкавата безопасност. Key-Value Observing (KVO) — механизъм за наблюдение на промени в свойствата, вграден в runtime: всеки обект може да се абонира за промени на свойствата на друг обект чрез addObserver.
Често задавани въпроси
Извикването на метод изглежда като [object selector:argument]. Съобщението преминава през objc_msgSend, който динамично намира имплементацията в runtime. Селекторът е името на метода (@selector(methodName)), а не указател към функция. Това позволява swizzling и forward invocation.
Category (@interface ClassName (Name)) добавя методи към всеки клас (включително системни) без наследяване. Extension (@interface ClassName ()) се декларира в .m файл и може да добавя property и ivar. Category не може да добавя ivar, extension — може.
Blocks — анонимни функции със синтаксис ^(параметри) { тяло }, улавящи променливи от контекста. Аналог на lambdas в C++ и closures в Swift. Изискват __weak за предотвратяване на retain cycles при улавяне на self. Използват се в UIKit, GCD и Foundation.
MRC — ръчно преброяване на референции: програмистът извиква retain, release, autorelease. ARC — автоматично: компилаторът вмъква retain/release на базата на статичен анализ. ARC не е GC — обектите се освобождават незабавно при нулиране на брояча. ARC поддържа __weak и __strong модификатори.
Да, чрез Bridging Header. Swift вижда Objective-C чрез този заглавен файл, Objective-C вижда Swift чрез
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също