LEARN X · ЗА 18 МИН

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
Поддержать проект