Objective-C
Объективный обзор Objective-C за 18 минут: типы, NSString, коллекции, синтаксис сообщений, классы, протоколы, блоки и ARC на одной странице.
Objective-C — надстройка над языком C с динамической объектной системой в стиле Smalltalk. Долгие годы это был основной язык для разработки под macOS и iOS, и он до сих пор живёт в огромном количестве кода Apple. Главная особенность — необычный синтаксис вызова методов через «отправку сообщений» в квадратных скобках. Весь язык — на одной странице, в комментариях к коду.
Структура программы
Точка входа — функция main. Foundation даёт базовые классы (NSString, NSArray и т. д.).
// Однострочный комментарий
/* Многострочный
комментарий */
// #import — как #include в C, но не подключает файл дважды
#import <Foundation/Foundation.h> // стандартная библиотека Foundation
// Точка входа в программу
int main(int argc, const char * argv[]) {
// @autoreleasepool — пул автоосвобождения памяти (нужен для ARC)
@autoreleasepool {
// NSLog печатает в консоль; @"..." — это литерал NSString
NSLog(@"Привет, мир!"); // \n добавляется автоматически
}
return 0; // 0 — программа завершилась успешно
}
Типы и переменные
// Базовые си-типы
int age = 30; // целое
float pi = 3.14f; // дробное одинарной точности
double e = 2.71828; // дробное двойной точности
char letter = 'A'; // одиночный символ
// Типы Foundation (предпочтительны в Objective-C)
NSInteger count = 100; // целое, подстраивается под разрядность (32/64 бит)
NSUInteger size = 42; // беззнаковое целое
CGFloat ratio = 0.5; // дробное для геометрии/UI
// BOOL — логический тип; значения YES и NO (а не true/false)
BOOL isReady = YES;
BOOL isDone = NO;
// id — указатель на любой объект Objective-C (динамический тип)
id anything = @"я могу быть чем угодно";
// Все объекты хранятся по указателю — отсюда звёздочка *
NSString *name = @"Аня"; // указатель на объект NSString
// Форматный вывод: %@ — объект, %ld — NSInteger, %d — int, %f — float
NSLog(@"%@, тебе %ld лет", name, (long)age);
Строки NSString
// Литерал строки — со знаком @ перед кавычками
NSString *greeting = @"Здравствуй";
// Длина строки (количество символов)
NSUInteger len = [greeting length];
// Сборка строки из частей через формат
NSString *full = [NSString stringWithFormat:@"%@, %@!", greeting, @"мир"];
// Конкатенация (создаётся новая строка)
NSString *joined = [greeting stringByAppendingString:@" дорогой друг"];
// Сравнение строк: == сравнивает указатели, isEqualToString — содержимое!
BOOL same = [greeting isEqualToString:@"Здравствуй"]; // YES
// Регистр и поиск
NSString *upper = [greeting uppercaseString]; // ЗДРАВСТВУЙ
BOOL hasPrefix = [greeting hasPrefix:@"Здр"]; // YES
// NSMutableString — изменяемая строка
NSMutableString *mut = [NSMutableString stringWithString:@"Раз"];
[mut appendString:@" два"]; // теперь "Раз два"
Числа NSNumber
В коллекции нельзя положить примитив int напрямую — его «оборачивают» в объект NSNumber.
// Краткие литералы NSNumber через @
NSNumber *num = @42; // int -> NSNumber
NSNumber *flag = @YES; // BOOL -> NSNumber
NSNumber *price = @9.99; // double -> NSNumber
NSNumber *boxed = @(3 + 4); // @(...) оборачивает выражение
// Достаём примитив обратно
int back = [num intValue]; // 42
double d = [price doubleValue]; // 9.99
BOOL b = [flag boolValue]; // YES
// Сравнение значений
BOOL equal = [num isEqualToNumber:@42]; // YES
Коллекции
// NSArray — неизменяемый упорядоченный список
NSArray *fruits = @[@"яблоко", @"банан", @"вишня"]; // литерал @[...]
NSString *first = fruits[0]; // доступ по индексу -> "яблоко"
NSUInteger n = [fruits count]; // 3
BOOL has = [fruits containsObject:@"банан"]; // YES
// NSMutableArray — изменяемый массив
NSMutableArray *list = [NSMutableArray arrayWithArray:fruits];
[list addObject:@"груша"]; // добавить в конец
[list removeObjectAtIndex:0]; // удалить первый
// NSDictionary — словарь ключ-значение (литерал @{...})
NSDictionary *user = @{@"name": @"Иван", @"age": @25};
NSString *uname = user[@"name"]; // доступ по ключу -> "Иван"
// NSMutableDictionary — изменяемый словарь
NSMutableDictionary *m = [NSMutableDictionary dictionary];
m[@"city"] = @"Москва"; // добавить/обновить пару
[m removeObjectForKey:@"city"];
// NSSet — неупорядоченное множество уникальных элементов
NSSet *tags = [NSSet setWithObjects:@"a", @"b", @"a", nil]; // {a, b}
Синтаксис сообщений
Главная особенность Objective-C: методы вызываются как «отправка сообщения» объекту в квадратных скобках.
// [получатель сообщение] — вызвать метод без аргументов
NSUInteger len = [@"привет" length];
// [получатель сообщение:аргумент] — с одним аргументом
[list addObject:@"новый"];
// Несколько аргументов: каждый со своей именованной частью (двоеточием)
// Полное имя метода: stringByReplacingOccurrencesOfString:withString:
NSString *res = [@"a-b-c" stringByReplacingOccurrencesOfString:@"-"
withString:@"+"];
// Вложенные сообщения читаются изнутри наружу
NSString *up = [[@"привет" uppercaseString] stringByAppendingString:@"!"];
// Точечная нотация — синтаксический сахар для геттеров/сеттеров свойств
NSUInteger c = list.count; // эквивалентно [list count]
Классы: интерфейс и реализация
Класс делится на два файла: объявление в .h (@interface) и реализация в .m (@implementation).
// === Файл Person.h — объявление (интерфейс) ===
@interface Person : NSObject // двоеточие — наследование от NSObject
// @property — свойство (генерирует геттер, сеттер и поле хранения)
@property (nonatomic, copy) NSString *name; // copy — для строк
@property (nonatomic, assign) NSInteger age; // assign — для примитивов
@property (nonatomic, strong) Person *friend; // strong — владеющая ссылка
// Метод экземпляра: знак минус (-)
- (void)sayHello;
- (NSString *)greetingFor:(NSString *)who; // возвращает NSString *
// Метод класса (статический): знак плюс (+)
+ (Person *)personWithName:(NSString *)name;
@end
// === Файл Person.m — реализация ===
@implementation Person
- (void)sayHello {
// self — указатель на текущий объект (как this)
NSLog(@"Привет, я %@", self.name);
}
- (NSString *)greetingFor:(NSString *)who {
return [NSString stringWithFormat:@"%@ приветствует %@", self.name, who];
}
+ (Person *)personWithName:(NSString *)name {
Person *p = [[Person alloc] init];
p.name = name;
return p;
}
@end
Создание объектов
// Классическая двухшаговая схема: alloc выделяет память, init инициализирует
Person *p = [[Person alloc] init];
p.name = @"Маша"; // точечная нотация -> вызов сеттера setName:
p.age = 28;
// Вызов методов экземпляра
[p sayHello]; // "Привет, я Маша"
NSString *g = [p greetingFor:@"Петя"]; // "Маша приветствует Петя"
// Вызов метода класса (фабричный метод)
Person *p2 = [Person personWithName:@"Олег"];
// new — сокращение для [[Class alloc] init]
Person *p3 = [Person new];
// nil — пустой объект (аналог null). Сообщения к nil безопасны и возвращают nil/0
Person *nobody = nil;
[nobody sayHello]; // ничего не произойдёт, без падения
Наследование
// Student наследует от Person (получает все его свойства и методы)
@interface Student : Person
@property (nonatomic, copy) NSString *university;
- (void)study;
@end
@implementation Student
- (void)study {
NSLog(@"%@ учится в %@", self.name, self.university); // name — от Person
}
// Переопределение метода родителя
- (void)sayHello {
[super sayHello]; // super — вызвать реализацию родителя
NSLog(@"...и я студент");
}
@end
// Проверка типа во время выполнения
Student *s = [Student new];
BOOL isPerson = [s isKindOfClass:[Person class]]; // YES (Student — это Person)
BOOL canDo = [s respondsToSelector:@selector(study)]; // YES
Протоколы
Протокол — набор методов, которые класс обязуется (или может) реализовать. Аналог интерфейсов в других языках.
// Объявление протокола
@protocol Drawable <NSObject>
@required
- (void)draw; // обязательный метод
@optional
- (NSString *)describe; // необязательный метод
@end
// Класс заявляет соответствие протоколу в угловых скобках
@interface Circle : NSObject <Drawable>
@property (nonatomic, assign) CGFloat radius;
@end
@implementation Circle
- (void)draw {
NSLog(@"Рисую круг радиусом %.1f", self.radius);
}
@end
// Переменная по протоколу: id<Drawable> — любой объект, умеющий draw
id<Drawable> shape = [Circle new];
[shape draw];
// Проверка соответствия протоколу
BOOL ok = [shape conformsToProtocol:@protocol(Drawable)]; // YES
Категории
Категория добавляет методы к уже существующему классу — даже к чужому (например, к NSString), без наследования.
// Расширяем стандартный NSString новым методом
@interface NSString (Helpers) // (ИмяКатегории) после имени класса
- (BOOL)isEmptyOrSpaces;
@end
@implementation NSString (Helpers)
- (BOOL)isEmptyOrSpaces {
NSString *trimmed = [self stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceCharacterSet]];
return trimmed.length == 0;
}
@end
// Теперь метод доступен у любой строки
BOOL empty = [@" " isEmptyOrSpaces]; // YES
Блоки
Блок — это замыкание (анонимная функция), захватывающее окружающие переменные. Объявляется через ^.
// Блок без аргументов
void (^hello)(void) = ^{
NSLog(@"Привет из блока");
};
hello(); // вызов блока
// Блок с аргументами и возвращаемым значением: ^returnType(args)
int (^sum)(int, int) = ^int(int a, int b) {
return a + b;
};
int r = sum(2, 3); // 5
// Блок захватывает внешние переменные (по значению)
NSString *prefix = @"=> ";
void (^printIt)(NSString *) = ^(NSString *msg) {
NSLog(@"%@%@", prefix, msg); // prefix виден внутри
};
printIt(@"тест");
// __block — чтобы блок мог ИЗМЕНЯТЬ внешнюю переменную
__block int counter = 0;
void (^inc)(void) = ^{ counter++; };
inc(); inc(); // counter == 2
// Блоки часто принимают как аргумент (колбэки, перебор)
[@[@1, @2, @3] enumerateObjectsUsingBlock:^(id obj, NSUInteger i, BOOL *stop) {
NSLog(@"%lu: %@", (unsigned long)i, obj);
}];
Управление памятью (ARC)
Сегодня используется ARC (Automatic Reference Counting) — компилятор сам расставляет вызовы retain/release. Раньше это делали вручную.
// При ARC НЕ нужно вызывать retain/release/autorelease вручную
// Объект жив, пока на него есть хотя бы одна strong-ссылка
Person *a = [Person new]; // счётчик ссылок = 1
Person *b = a; // ещё одна strong-ссылка, счётчик = 2
a = nil; // счётчик = 1, объект ещё жив (держит b)
b = nil; // счётчик = 0, объект освобождается автоматически
// Виды ссылок в свойствах:
// strong — владеет объектом, держит его в памяти (по умолчанию для объектов)
// weak — не владеет, обнуляется в nil при удалении объекта (против циклов)
// copy — хранит независимую копию (типично для NSString, блоков)
// assign — простое присваивание (для примитивов: int, BOOL, NSInteger)
@property (nonatomic, weak) id<Drawable> delegate; // weak против retain-цикла
Условия и циклы
NSInteger x = 7;
// if / else if / else — как в C
if (x > 10) {
NSLog(@"много");
} else if (x > 5) {
NSLog(@"средне");
} else {
NSLog(@"мало");
}
// Тернарный оператор
NSString *label = (x % 2 == 0) ? @"чёт" : @"нечет";
// switch
switch (x) {
case 1: NSLog(@"один"); break;
default: NSLog(@"другое"); break;
}
// Классический цикл for
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"i = %ld", (long)i);
}
// while
NSInteger k = 0;
while (k < 3) { k++; }
// for-in — быстрый перебор коллекции
NSArray *items = @[@"a", @"b", @"c"];
for (NSString *item in items) {
NSLog(@"%@", item); // a, b, c
}
// Перебор словаря — по ключам
NSDictionary *dict = @{@"k1": @1, @"k2": @2};
for (NSString *key in dict) {
NSLog(@"%@ = %@", key, dict[key]);
}
nil и nullability
// nil — пустой указатель на объект (NULL для объектов Objective-C)
NSString *empty = nil;
// Отправка сообщения к nil безопасна: вернёт nil/0/NO, без падения
NSUInteger len = [empty length]; // 0, а не краш
// Поэтому проверка на nil — обычная защита
if (empty == nil) {
NSLog(@"строка пустая");
}
// Короче: для объекта nil ложен, не-nil истинен
if (!empty) { NSLog(@"тоже пусто"); }
// Аннотации nullability — подсказки компилятору (особенно для Swift-совместимости)
// nonnull — никогда не nil; nullable — может быть nil
@interface Box : NSObject
- (nonnull NSString *)title; // гарантированно не nil
- (nullable Person *)findByName:(nonnull NSString *)name; // может вернуть nil
@end
// Блоки для целых заголовков:
// NS_ASSUME_NONNULL_BEGIN ... NS_ASSUME_NONNULL_END
// внутри всё nonnull по умолчанию, кроме явно помеченного nullable