Objective-C — język programowania z dynamicznym wysyłaniem komunikatów, stworzony przez Brada Coxa w latach 80. XX wieku. Apple wybrało Objective-C jako główny język dla NeXTSTEP, a następnie dla iOS SDK. Programming With Objective-C — wprowadzenie do składni komunikatów i zarządzania pamięcią.
Najważniejsze
Objective-C — język programowania rozszerzający C o możliwości programowania obiektowego w stylu Smalltalk. Kod w Objective-C jest kompilowany przez LLVM lub GCC do natywnego kodu maszynowego, zachowując pełną kompatybilność wsteczną z C — każdy kod C jest poprawny w Objective-C.
Apple przejęło NeXT (wraz z Objective-C) w 1997 roku. Język stał się podstawą Cocoa i Cocoa Touch — frameworków dla macOS i iOS. Od 2014 roku Apple promuje Swift jako zamiennik, ale Objective-C pozostaje krytycznie ważny dla projektów legacy i niektórych frameworków systemowych. Według Apple (WWDC 2024), około 35% aplikacji w App Store nadal zawiera kod Objective-C.
Kluczowa cecha — dynamiczny runtime. W przeciwieństwie do Swifta, gdzie wywołania metod są rozwiązywane na etapie kompilacji, Objective-C wysyła komunikaty w czasie wykonania przez funkcję objc_msgSend. Pozwala to na nadpisywanie metod w locie (method swizzling), dynamiczne dodawanie klas i używanie forward invocation dla nieistniejących selektorów.
Każdy kod C jest poprawny w Objective-C. Nadbudowa obiektowa dodaje klasy (@interface/@implementation), kategorie, protokoły (@protocol) i dynamiczne typowanie (id). Pliki mają rozszerzenia .m (implementacja) i .h (nagłówki).
Składnia komunikatów — główna różnica Objective-C od języków C-podobnych. Zamiast object.method(argument) używa się [object method:argument]. Każdy komunikat przechodzi przez objc_msgSend, który dynamicznie szuka implementacji metody w runtime.
// Składnia komunikatów z nazwanymi parametrami
NSString *greeting = [NSString stringWithFormat:@"Hello, %@", name];
// Zagnieżdżone komunikaty
NSArray *sortedArray = [[array sortedArrayUsingSelector:@selector(compare:)] copy];
// Sprawdzanie typu przez instrospekcję
if ([object isKindOfClass:[UIView class]]) {
UIView *view = (UIView *)object;
view.backgroundColor = [UIColor redColor];
}Dynamic dispatch umożliwia nadpisywanie metod w runtime (method swizzling) — potężna, ale niebezpieczna technika. Na przykład frameworki takie jak AFNetworking i Aspects używają swizzling do przechwytywania wywołań URLSession. Apple ostrzega: swizzling może zakłócić działanie frameworków systemowych przy nieprawidłowym użyciu.
Categories — unikalna możliwość Objective-C dodawania metod do istniejących klas (w tym systemowych, takich jak NSString i UIView) bez dziedziczenia i bez dostępu do kodu źródłowego. Kategorie są deklarowane przez @interface ClassName (CategoryName).
// Plik UIColor+Hex.h — kategoria dla UIColor
@interface UIColor (Hex)
+ (instancetype)colorWithHex:NSUIntegerhex;
@end
// Plik UIColor+Hex.m — implementacja
@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) — szczególny rodzaj kategorii, zadeklarowany w pliku .m bez nazwy: @interface ClassName (). W przeciwieństwie do kategorii, extensions mogą dodawać nie tylko metody, ale także ivar (zmienne instancji) i property. Extensions są używane do ukrywania wewnętrznego API przed zewnętrznymi modułami.
Blocks — anonimowe funkcje w Objective-C, przechwytujące zmienne z zewnętrznego zakresu widoczności. Składnia bloków: ^(int x) { return x * 2; }. Blocks są używane w UIKit dla handlerów callback, w Grand Central Dispatch dla zadań asynchronicznych oraz w kolekcjach NSArray/NSDictionary dla operacji funkcyjnych.
// Deklaracja typu bloku
typedef void (^CompletionBlock)(BOOL success, NSError *error);
// Blok jako parametr
- (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);
}
});
}Zarządzanie pamięcią w blokach jest krytycznie ważne. Bloki przechwytują self silną referencją, co tworzy retain cycle przy bezpośrednim użyciu. Rozwiązanie — __weak typeof(self) weakSelf = self, z następczą weryfikacją wewnątrz bloku. Ten problem został całkowicie rozwiązany w Swift przez listy przechwytywania [weak self].
Objective-C przeszedł drogę od MRC (Manual Reference Counting) do ARC (Automatic Reference Counting). W MRC programista ręcznie wywoływał retain (zwiększenie licznika), release (zmniejszenie) i autorelease (opóźnione release). Błędy w tych wywołaniach prowadziły do wycieków pamięci lub crashy z powodu dangling pointers.
| Operacja | MRC | ARC |
|---|---|---|
| Tworzenie obiektu | [[Object alloc] init] | [[Object alloc] init] |
| Utrzymanie referencji | [object retain] | Automatycznie |
| Zwolnienie | [object release] | Automatycznie |
| Autozwolnienie | [object autorelease] | Automatycznie (nie wymagane) |
| Weak referencja | __weak nie istniało | __weak (automatyczne nil) |
ARC został wprowadzony w Xcode 4.2 i LLVM 3.0 (2011). Kompilator automatycznie wstawia retain/release na etapie kompilacji, analizując lifetime obiektów. ARC nie jest garbage collection — to statyczna analiza z automatycznymi wstawkami. Objective-C ARC jest kompatybilny z Swift ARC: oba używają tego samego systemu zliczania referencji na poziomie runtime.
Wspólne używanie Objective-C i Swift w jednym projekcie — powszechna praktyka w projektach legacy. Apple udostępnia Bridging Header — automatycznie generowany plik, przez który Swift widzi klasy Objective-C, a Objective-C widzi klasy Swift dziedziczące po NSObject.
// ProjectName-Bridging-Header.h
// Swift widzi te nagłówki automatycznie
#import "LegacyManager.h"
#import "NetworkClient.h"
#import "DataStore.h"Migracja z Objective-C na Swift — proces etapowy. Nowe pliki są pisane w Swift, stare stopniowo refaktorowane. Bridging Header jest automatycznie generowany przy dodaniu pierwszego pliku Swift do projektu Objective-C. Dla odwrotnej widoczności (ObjC → Swift) Xcode generuje plik
// Klasa Swift widoczna z Objective-C
@objc class SwiftRouter: NSObject {
@objc func navigateToProfile(userId: Int) {
// implementacja
}
}Ograniczenia: Swift value-typy (struct, enum) nie są widoczne z Objective-C bezpośrednio — trzeba je opakowywać w class z @objc. Generics w Swift są ograniczenie dostępne z Objective-C. Zalecane podejście — pisać nowy kod w Swift, a istniejący kod Objective-C refaktorować tylko przy zmianie odpowiedniej funkcjonalności.
Główna zaleta Objective-C nad Swift — pełny dostęp do runtime. Method swizzling, zamiana implementacji metod w czasie wykonania, umożliwia nadpisywanie systemowych metod bez dziedziczenia. Biblioteki takie jak Aspects i JRSwizzle używają tej możliwości dla AOP (programowania aspektowego) — monitorowania, analityki i logowania.
Forward invocation — kolejna możliwość runtime: jeśli obiekt nie odpowiada na selektor, system wywołuje forwardInvocation:, umożliwiając przekierowanie komunikatu do innego obiektu. To podstawa wzorca Proxy w Objective-C (NSProxy), używany do opóźnionej inicjalizacji i obiektów rozproszonych.
Apple zaleca minimalizowanie używania sztuczek runtime w nowym kodzie, preferując statyczne typowanie Swifta. Jednak w projektach legacy znajomość objc_msgSend, method_exchangeImplementations i objc_getAssociatedObject jest niezbędna do utrzymania istniejącej bazy kodu.
Objective-C obsługuje property jako lukier składniowy nad ivar z getterami/setterami. Modyfikatory atomic/nonatomic, strong/weak/copy, readonly/readwrite i assign/retain określają zachowanie pamięci i bezpieczeństwo wątkowe. Key-Value Observing (KVO) — mechanizm obserwacji zmian właściwości wbudowany w runtime: każdy obiekt może subskrybować zmiany właściwości innego obiektu przez addObserver.
Często zadawane pytania
Wywołanie metody wygląda jak [object selector:argument]. Komunikat przechodzi przez objc_msgSend, który dynamicznie znajduje implementację w runtime. Selektor to nazwa metody (@selector(methodName)), a nie wskaźnik do funkcji. To umożliwia swizzling i forward invocation.
Category (@interface ClassName (Name)) dodaje metody do dowolnej klasy (w tym systemowych) bez dziedziczenia. Extension (@interface ClassName ()) jest deklarowane w pliku .m i może dodawać property i ivar. Category nie może dodawać ivar, extension — może.
Blocks — anonimowe funkcje z składnią ^(parametry) { ciało }, przechwytujące zmienne z kontekstu. Odpowiednik lambd w C++ i closures w Swift. Wymagają __weak do zapobiegania retain cycles przy przechwytywaniu self. Używane w UIKit, GCD i Foundation.
MRC — ręczne zliczanie referencji: programista wywołuje retain, release, autorelease. ARC — automatyczne: kompilator wstawia retain/release na podstawie analizy statycznej. ARC nie jest GC — obiekty są zwalniane natychmiast po wyzerowaniu licznika. ARC obsługuje modyfikatory __weak i __strong.
Tak, przez Bridging Header. Swift widzi Objective-C przez ten nagłówek, Objective-C widzi Swift przez
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również