четверг, 16 октября 2014 г.

Objective-C основные коцепции

Первоначально Objective-C появился как настройка над C, в виде новых синтаксических конструкций заимствованных у Smalltalk, которые переводились препроцессором в обычные функции C. Кроме того библиотека времени выполнения обрабатывала такие вызовы. Так язык C имея в своем синтаксисе только структуру struct, становился обьектно ориентированным языком.
По сути Objective-C является и сейчас настройкой над C, потому что мы можем написать программу на чистом C, и передать компилятору Objective-C.

"Мир" Objective-C воспринимает как наличие обьектов, которые общаются друг с другом путем передачи сообщений - вызовом методов. Состояние обьектов определяются инвариантами и закрыты от внешнего мира, а поведение методами, которые принимают сообщения и могут измененять в результате состояние обьекта.

Файлы модулей *.m (есть применяется сметь Objective-C и C++, то *.mm)
Файлы заголовков *.h

Все создаваемые обьекты размещаются в динамической памяти, от сюда есть специальный тип id, который является под капотом void*, то есть указателем на обьект любого типа.

Чтобы узнать какого же типа текущий обьект NSObject  - базовый класс для всех классов - приносит инвариант isa класса Class(позволяет узнавать имена своего и базового класса, а также инварианты класса и какие прототипы методов реализованые этим обьектом  в нем).
Зарезервированные слова Objective-C, отличающиеся от слов C, начинаются с @.


Вывод в стандартный вывод

NSLog(@"Hello, World!");

Префикс NS от OS NeXTSTEP - это вроде образовательная операционная система над которой трудился Стив Джобс, когда его попросили из собственного Apple Computers, именно там развили Objective-C

@ - этим знаком мы создаем обьект, на который будет ссылаться переменная.

Переменный и стандарный классы

Чтобы создать переменную типа строки мы должны использовать тип NSString*, это мы создаем ссылку на созданный обьект:
NSString* firstName = @"World";

Как всегда в С-мире есть плейсхолдеры, 
NSLog(@"Hello there, %@.", firstName);
%@ - это плейсхолдер для любой переменной, которая передается вместе со строкой.

Есть тип числовой:
NSNumber* age = @28;
NSLog(@"%@", age);

Массив, опять же нужно обратить внимание как мы создаем обьекты в памяти с помощью указания символом @, и потом ссылаемся на общую обвертку типом-ссылкой:
NSArray *apps = @[@"AngryFowl", @"Lettertouch", @"Tweetrobot"];
NSLog(@"%@", apps[1]);
Так создаются неизменяемые массивы, но в Objective-C есть всегда изменяемый брат близнец: например NSMutableArray, NSMutableString


Мапа:
NSDictionary *appRatings = @{@"AngryFowl": @3, @"Lettertouch": @5};
NSLog(@"%@", appRatings[@"AngryFowl"]);



Вызов методов у обьектов

В Objective-C это называется "отправить сообщение", где сообщением называется имя метода
[objectName messageName];

Мессидж, который присущий большинству встроенных обьектов это description, который возвращает строковое представление обьекта.
NSArray *foods = @[@"tacos", @"burgers"];
NSLog(@"%@", [foods description]);
Вернет:
(
    tacos,
    burgers
)

У NSString есть мессидж length, возвращает он длину строки типа (NSUInteger)length, как видим тип у этого проперти без звезды;)

Чтоби воспользоваться логированием мы должны использовать другой плейсхолдер %lu:
NSUInteger cityLength = [city length];
NSLog(@"City has %lu characters", cityLength);

И так тут мы подошли к ключевому вопросу о звезде - Objective-C построен на C, поэтому он пользуется как собственными обьектами, так и обьектами C. Вот как их отличить:
CType var1;
ObCType* var2;

Тут и начинается мешанина, есть C-шные операторы, которые могут принимать только сишные обьекты, среди таких арифметические, поэтому сначала нужно сделать преобразование перед такими операциями:
NSNumber* higgiesAge = @6;                                                                   
NSNumber* phoneLives = @3;

NSUInteger higgiesAgeInt = [higgiesAge unsignedIntegerValue];
NSUInteger phoneLivesInt = [phoneLives unsignedIntegerValue];

NSUInteger higgiesRealAge = higgiesAgeInt * phoneLivesInt;
NSLog(@"Higgie is actually %lu years old.", higgiesRealAge);


Конкатинация строк происходит с помощью метода stringByAppendingString:
NSString* fullName = [[firstName stringByAppendingString:@" "] stringByAppendingString:lastName];

Замена подстроки:
NSString *replaced = [fullName stringByReplacingOccurrencesOfString:firstName withString:lastName];

Клонировние строк:
NSString *firstName = @"Andrii";

NSString *copy = [NSString stringWithString:firstName];

У стринга есть удобный метод для форматирования результирующей строки в сложной конкатинации
NSString *fullName = [NSString stringWithFormat:@"%@ %@", firstName, lastName];

Простые фактори месиджи для создания обьектов и как создаются обьекты

Класси Objective-C имеют часто для создания инстанций месидж-фактори:

NSArray *emptyArray = [NSArray array];
NSDictionary *emptyDict = [NSDictionary dictionary];
На самом деле внутри происходит следующее:
NSString *emptyString = [[NSString alloc] init];
NSArray *emptyArray = [[NSArray alloc] init];
NSDictionary *emptyDictionary = [[NSDictionary alloc] init];
Метод alloc запрашивает выделение в памяти под обьект, а уже init перетирает поточное состояние выделенного участка памяти пустым значением. Если обратиться к обьекту до init будет рантайм еррор. У стринга есть более осмысленный по названию инит с указанным значением:
NSString *copy = [[NSString alloc] initWithString:otherString];

Булинов тип

BOOL mrHiggieIsMean = YES;

if (mrHiggieIsMean) {
  NSLog(@"Confirmed: he is super mean");
}

Алиас на множество значений указанного типа

typedef NS_ENUM(NSInteger, DayOfWeek) {
    DayOfWeekMonday = 1,
    DayOfWeekTuesday = 2,
    DayOfWeekWednesday = 3,
    DayOfWeekThursday = 4,
    DayOfWeekFriday = 5,
    DayOfWeekSaturday = 6,
    DayOfWeekSunday = 7
};

Блоки кода

Что-то на подобии функций, которую мы можем определить, указать ей параметры для аргументов и потом многократно вызывать:
void (^myFirstBlock)(void) = ^{
  NSLog(@"Hello from inside the block");
};

myFirstBlock();

А вот с аргументами:
void (^sumNumbers)(NSUInteger, NSUInteger) = ^(NSUInteger num1, NSUInteger num2){
  NSLog(@"The sum of the numbers is %lu", num1 + num2);
};

sumNumbers(1, 2);

Чаще всегод это используется как лямбда выражение для передачи логики в какой-нибудь метод обьекта:
NSArray *newHats = @[@"Cowboy", @"Conductor", @"Baseball", 
  @"Beanie", @"Beret", @"Fez"];

[newHats enumerateObjectsUsingBlock:
 ^(NSString* hat, NSUInteger index, BOOL* stop){
   NSLog(@"Trying on hat %@", hat);
 }
];

Создание кастомных классов

Тут как в С++, сначало нам нужно определить хедер-файл, определяющий интерфейс TalkingiPhone.h:
@interface TalkingiPhone : NSObject

@property NSString* phoneName;
@property NSString* modelNumber;


- (NSString*) getSpeech;
- (void) speak;
@end

А потом написать реализацию этого интрефейса TalkingiPhone.m::
#import "TalkingiPhone.h"

@implementation TalkingiPhone
-(NSString*) getSpeech;
{
  return [NSString stringWithFormat:@"%@ says \"Hello!\"", self.phoneName];
}
- (void) speak;
{
  NSLog([self getSpeech]);
}
@end
Под капотом для каждого проперти создается сеттеры и геттеры, когда мы пользуемся вроде как обращением к проперти обьекта, на самом деле ме дергаем гетер/сетер.
TalkingiPhone *phone = [[TalkingiPhone alloc] init];
phone.modelNumber = @"5s";
Мы также можем создать поля только на чтение, но все равно есть способ их писать внутри класса, то есть мы убирает сетер для поля
Person.h:
@interface Person : NSObject
@property NSString *firstName;
@property (readonly) NSString *lastName;
@end
Но все же мы можем писать внутри так. Person.m:
#import "Person.h"

@implementation Person

- (void) changeLastName:(NSString *)newLastName;
{
  _lastName = newLastName;
}
@end
Но есть и другой путь где про существование переменной снаружи нельза даже узнать, чтобы сделаеть ее приватной, мы ее делаем не пропертей, а переменной инстанции:
Coffee.h:
@interface Coffee : NSObject {
  NSNumber *_temperature;
}        
@end
Здась подчеркивание в имени только неймконвеншин и ничего более. Но все же мы можем писать внутри так. Coffee.m:
#import "Coffee.h"

@implementation Coffee
- (void)pour;
{
  if([_temperature intValue] < 155){
    NSLog(@"Oh no! The coffee is cold!");
  } else {
    NSLog(@"Mmmm, delicious warm coffee");
  }
}
@end

Конструктор

#import "TalkingiPhone.h"

@implementation TalkingiPhone

- (TalkingiPhone *)init;
{
  _batteryLife = @100;
  return [super init];
}


Когда мы видим рантайм ексепшин NSInvalidArgumentException это означает, что мы передали сообщение обьекту, которое он не обрабатывает(вызвали метод, который не существует в классе)
Мы всегда можем узнать у обьекта умеет ли он обрабатывать указанное сообщение или нет:

if([talkingiPhone respondsToSelector:@selector(decreaseBatteryLife:)]){
  NSLog(@"Yup, talkingiPhone responds to the decreaseBatteryLife: message");
}

Когда ми отправлем сообщение copy обьекту, то его класс должен реализовывать copyWithZone (которий означает, что клон обьекта будет сделан в том же участке памяти(зоне), где находится и первоначальный обьект - это делается для того, что обычно группы обьектов, исчисляются вместе, и более продуктивно с ними работать в одном месте памеяти - например удалить все вросто подряд в участке памяти, а не прыгать по разным участкам). Но насколько я понял зоны уже не имееют своей первоначальной реализации и смысла, а это осталось по традиции.
В Objective-C есть понятие протокола, в случае copyWithZone, реализацию которого требует метод copy базового обьекта NSObject. Часть протокола NSCopying Reference.

Чтобы класс стал реализовывать какой-нибудь протокол мы должны "пометить"(теггировать) его
//declaration
@interface Person : NSObject 
@end

//realization
#import "Person.h"
@implementation Person
- (Person *) copyWithZone:(NSZone *)zone;
{
    Person *personCopy = [[Person allocWithZone:zone] init];
    return personCopy;
}
@end

//usage
Person  *person = [[Person alloc] init];
Person *copy = [person copy];

Почти как конструктор, только катомный инициалайзер

Этот метод будет вызван на фазе инит, он должен обязательно начинаться с init в своем имени
Person.h:
@interface Person : NSObject
- (Person *) initWithFirstName:(NSString *)firstName 
                      lastName:(NSString *)lastName;
                      
@property NSString *firstName;
@property NSString *lastName;
@end
Person.m:
@implementation Person
- (Person *) initWithFirstName:(NSString *)firstName 
                      lastName:(NSString *)lastName;
{
   _firstName = firstName;
   _lastName = lastName;
   return [super init];
}
@end
И пользуемся:
Person *person = [[Person alloc] initWithFirstName:@"Tim" 
                                          lastName:@"Cook"];

Примеры реализации копиВизЗон c безопасной реализацией для наследников класса:

- (TalkingiPhone *) copyWithZone:(NSZone *)zone;
{
   TalkingiPhone *copy = [[[self class] allocWithZone:zone] initWithBatteryLife:_batteryLife];
   copy.phoneName = [NSString stringWithFormat:@"Copy of %@", self.phoneName];
   return copy;
}

Комментариев нет:

Отправить комментарий