rambler.ios #2: Введение в restkit

108
RestKit

Upload: rambler-ios

Post on 16-Jul-2015

348 views

Category:

Technology


1 download

TRANSCRIPT

RestKit

Что такое RestKit?

• Фреймворк для взаимодействия с RESTful API

Что такое RestKit?

• Фреймворк для взаимодействия с RESTful API

• Разрабатывается с 2011 года

Что такое RestKit?

• Фреймворк для взаимодействия с RESTful API

• Разрабатывается с 2011 года

• 7750 🌟 на GitHub

Что такое RestKit?

• Фреймворк для взаимодействия с RESTful API

• Разрабатывается с 2011 года

• 7750 🌟 на GitHub

• 4 коммита от Сергея Крапивенского 👸

Терминология

• mapping – преобразование данных из одного представления в другое

Терминология

• mapping – преобразование данных из одного представления в другое

• source – объект, из которого производится мэппинг

Терминология

• mapping – преобразование данных из одного представления в другое

• source – объект, из которого производится мэппинг

• destination – объект, в который производится мэппинг

Терминология

• mapping – преобразование данных из одного представления в другое

• source – объект, из которого производится мэппинг

• destination – объект, в который производится мэппинг

• АПИ – лень выговаривать ЭйПиАй

Зачем он мне?

Зачем он мне?

• API клиент и мэппинг в одном флаконе

Зачем он мне?

• API клиент и мэппинг в одном флаконе

• Упрощает жизнь при работе с плохим JSON

Зачем он мне?

• API клиент и мэппинг в одном флаконе

• Упрощает жизнь при работе с плохим JSON

• Легкий мэппинг напрямую в Core Data

Зачем он мне?

• API клиент и мэппинг в одном флаконе

• Упрощает жизнь при работе с плохим JSON

• Легкий мэппинг напрямую в Core Data

• Мэппинг можно прикрутить к другому сетевому стеку

Зачем он мне?

• API клиент и мэппинг в одном флаконе

• Упрощает жизнь при работе с плохим JSON

• Легкий мэппинг напрямую в Core Data

• Мэппинг можно прикрутить к другому сетевому стеку

• Упрощает тестирование мэппинга

ОДНАКО

Минусы

• Много issues на GitHub

Минусы

• Много issues на GitHub

• Сетевой слой использует AFNetworking 1.0

Минусы

• Много issues на GitHub

• Сетевой слой использует AFNetworking 1.0

• Много задач на одну библиотеку

Составные части

Составные части

• Object Mapping

Составные части

• Object Mapping

• Network

Составные части

• Object Mapping

• Network

• Core Data

Составные части

• Object Mapping

• Network

• Core Data

• Testing

Object Mapping

RKMapping

RKObjectMapping

RKEntityMapping

RKDynamicMapping

RKObjectMapping

• класс объекта-назначения

• набор объектов типа RKPropertyMapping

RKPropertyMapping

• RKObjectMapping – контейнер для объектов класса RKPropertyMapping, описывающих мэппинг одного поля объекта-источника

RKPropertyMapping

• RKObjectMapping – контейнер для объектов класса RKPropertyMapping, описывающих мэппинг одного поля объекта-источника

• RKAttributeMapping – мэппинг простого значения (строка, число и т.д.)

RKPropertyMapping

• RKObjectMapping – контейнер для объектов класса RKPropertyMapping, описывающих мэппинг одного поля объекта-источника

• RKAttributeMapping – мэппинг простого значения (строка, число и т.д.)

• RKRelationshipMapping – мэппинг составных объектов

Пример

{ "applications": [ { "title": "Афиша-Рестораны","body": "Самый быстрый и удобный способ выбрать в какой ресторан

или бар пойти","author": "Илья Пучка","publication_date": "13/11/2014"

},{ "title": "Redigo","body": "Удобный компактный гид по странам мира от Австралии до

Японии в вашем телефоне","author": "Стас Цыганов","publication_date": "30/01/2015"

}]}

Пример

@interface Application: NSObject

@property (nonatomic, copy) NSString* title;

@property (nonatomic, copy) NSString* body;

@property (nonatomic, copy) NSString* author;

@property (nonatomic) NSDate* publicationDate;

@end

RKObjectMapping

RKObjectMapping* appMapping =

[RKObjectMapping mappingForClass:[Application class]];

[appMapping addAttributeMappingsFromDictionary:@{

@"title": @"title",

@"body": @"body",

@"author": @"author",

@"publication_date": @"publicationDate"

}];

RKObjectMapping

То же самое что и:

[appMapping addPropertyMapping:

[RKAttributeMapping attributeMappingFromKeyPath:@"title"

toKeyPath:@"title"]];

[appMapping addPropertyMapping:

[RKAttributeMapping attributeMappingFromKeyPath:@"body"

toKeyPath:@"body"]];

и т.д.

Приведение типов

• NSString -> NSDate с помощью NSDateFormatter

• NSString -> NSURL

• NSString -> NSNumber

• NSString с булевыми значениями (YES, NO, false и тд) -> NSNumber

• NSNumber -> NSDate

• предустановленные NSDateFormatter’ы

• и многое другое (свое собственное преобразование можно задать блоком)

Связь запроса и мэппинга

• Для связи запроса с мэппингом есть специальный класс - RKResponseDescriptor

• Для сериализации объектов перед отправкой на сервер используется похожий класс RKRequestDescriptor

Параметры RKResponseDescriptor

• mapping

Параметры RKResponseDescriptor

• mapping

• method - тип REST-метода

Параметры RKResponseDescriptor

• mapping

• method - тип REST-метода

• pathPattern – относительный URL запроса

Параметры RKResponseDescriptor

• mapping

• method - тип REST-метода

• pathPattern – относительный URL запроса

• keyPath

Параметры RKResponseDescriptor

• mapping

• method - тип REST-метода

• pathPattern – относительный URL запроса

• keyPath

• statusCodes – NSIndexSet статус-кодов

Параметры RKResponseDescriptor

• mapping = appMapping

• method = RKRequestMethodAny

• pathPattern = nil

• keyPath = @”applications”

• statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)

RKResponseDescriptor

• [[RKObjectManager sharedManager] addResponseDescriptor:responseDescriptor];

• список дескрипторов анализируется при запросе

• после добавления дескриптора можно посылать запросы и обрабатывать ответ

Мэппинг графа объектов

{”applications": [

{"title": "Redigo","body": "Удобный компактный гид по странам мира от Австралии до

Японии в вашем телефоне","author": {

"name": "Стас Цыганов","email": "[email protected]"

},"publication_date": "30/01/2015"

}]

}

Мэппинг графа объектов

@interface Author : NSObject

@property (nonatomic, copy) NSString* name;

@property (nonatomic, copy) NSString* email;

@end

@interface Application: NSObject

@property (nonatomic, copy) NSString* title;

@property (nonatomic, copy) NSString* body;

@property (nonatomic) Author* author;

@property (nonatomic) NSDate* publicationDate;

@end

Мэппинг графа объектов

RKObjectMapping* authorMapping =

[RKObjectMapping mappingForClass:[Author class] ];

[authorMapping addAttributeMappingsFromArray:@[

@"name", @"email" ]];

Мэппинг графа объектов

RKObjectMapping* appMapping =

[RKObjectMapping mappingForClass:[Application class] ];

[appMapping addAttributeMappingsFromDictionary:@{

@"title": @"title",

@"body": @"body”,

@"publication_date": @"publicationDate”

}];

RKRelationshipMapping

[appMapping addPropertyMapping:

[RKRelationshipMapping relationshipMappingFromKeyPath:@"author”

toKeyPath:@"author”

withMapping:authorMapping]];

Мэппинг кривого JSON

Мэппинг кривого JSON

Мэппинг без KVC:

[{ "title": "Redigo",

"body": "Удобный компактный гид по странам мира от Австралии до Японии в вашем телефоне",

"author": {"name": "Стас Цыганов","email": "[email protected]"

},"publication_date": “30/01/2015"

}]

Мэппинг кривого JSON

Что делать?

• у дескриптора сделать keyPath = nil

• PROFIT

Мэппинг кривого JSON

NSIndexSet *success = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);

RKResponseDescriptor *responseDescriptor =

[RKResponseDescriptor responseDescriptorWithMapping:appMapping

method:RKRequestMethodAny

pathPattern:@"/applications"

keyPath:nil

statusCodes:success];

Мэппинг кривого JSON

Массив простых значений:

{ "developers":

["Егор Толстой",

"Герман Сапрыкин",

"Андрей Резанов"] }

Мэппинг кривого JSON

@interface RamblerDeveloper: NSObject

@property (nonatomic, copy) NSString *name;

@end

Мэппинг кривого JSON

RKObjectMapping *userMapping = [RKObjectMapping

mappingForClass:[RamblerDeveloper class]];

[userMapping addPropertyMapping:

[RKAttributeMapping attributeMappingFromKeyPath:nil

toKeyPath:@”name"]];

Мэппинг кривого JSON

{

"first_name": "Егор",

"last_name": "Толстой",

"city": "Москва",

"country": "Россия",

"zip": 128405

}

Мэппинг кривого JSON

@interface ExampleUser : NSObject

@property (nonatomic, copy) NSString *firstName;

@property (nonatomic, copy) NSString *lastName;

@property (nonatomic) ExampleAddress *address;

@end

@interface ExampleAddress : NSObject

@property (nonatomic, copy) NSString *city;

@property (nonatomic, copy) NSString *country;

@property (nonatomic, copy) NSNumber *zipCode;

@end

Мэппинг кривого JSON

RKObjectMapping *addressMapping =

[RKObjectMapping mappingForClass:[ExampleAddress class]];

[addressMapping addAttributeMappingsFromDictionary:@{

@"city": @"city",

@”country": @"country",

@"zip": @"zipCode”

}];

Мэппинг кривого JSON

RKObjectMapping *userMapping =

[RKObjectMapping mappingForClass:[ExampleUser class]];

[userMapping addAttributeMappingsFromDictionary:@{

@"first_name": @"firstName",

@"last_name": @"lastName“

}];

Мэппинг кривого JSON

[userMapping addPropertyMapping:

[RKRelationshipMapping

relationshipMappingFromKeyPath:nil

toKeyPath:@"address"

withMapping:addressMapping]];

Мэппинг кривого JSON

{

"Сергей": {

"email": "[email protected]",

"age": 28

}

}

Мэппинг кривого JSON

@interface User : NSObject

@property (nonatomic, copy) NSString* email;

@property (nonatomic, copy) NSString* username;

@property (nonatomic, copy) NSNumber* age;

@end

Мэппинг кривого JSON

RKObjectMapping* mapping =

[RKObjectMapping mappingForClass:[User class] ];

[mapping addAttributeMappingFromKeyOfRepresentationToAttribute:@"username"];

[mapping addAttributeMappingsFromDictionary:@{

@"(username).email": @"email",

@"(username).age": @"age”

}];

RKDynamicMapping

• заранее неизвестно какой мэппинг потребуется

RKDynamicMapping

• заранее неизвестно какой мэппинг потребуется

• нужный RKObjectMapping подбирается при анализе полученных данных

RKDynamicMapping

• заранее неизвестно какой мэппинг потребуется

• нужный RKObjectMapping подбирается при анализе полученных данных

• выбор осуществляется с помощью блока

RKDynamicMapping

• заранее неизвестно какой мэппинг потребуется

• нужный RKObjectMapping подбирается при анализе полученных данных

• выбор осуществляется с помощью блока

• или при анализе аттрибута в JSON с помощью класса RKObjectMappingMatcher

Network

RKObjectRequestOperation

• наследуется от NSOperation

RKObjectRequestOperation

• наследуется от NSOperation

• управляет жизненным циклом HTTP-запроса

RKObjectRequestOperation

• наследуется от NSOperation

• управляет жизненным циклом HTTP-запроса

• обрабатывает ответ с помощью движка мэппинга

RKObjectRequestOperation

1) создаем NSURLRequest с нужным URL

NSURL *URL = [NSURL URLWithString:@"http://rambler.ru/applications"];

NSURLRequest *request = [NSURLRequest requestWithURL:URL];

RKObjectRequestOperation

2) создаем операцию с созданным реквестом и дескриптором

RKObjectRequestOperation *objectRequestOperation =

[[RKObjectRequestOperation alloc] initWithRequest:request

responseDescriptors:@[ responseDescriptor ]];

RKObjectRequestOperation

3) создаем блоки, вызываемые в случае успеха и в случае ошибки

RKObjectRequestOperation

4) запускаем операцию

[objectRequestOperation start];

RKObjectRequestOperation

NSURL *URL = [NSURL URLWithString:@"http://rambler.ru/applications"];

NSURLRequest *request = [NSURLRequest requestWithURL:URL];

RKObjectRequestOperation *objectRequestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[ responseDescriptor ]];

[objectRequestOperation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)

{

NSLog(@"Loaded collection of applications: %@", mappingResult.array);

} failure:^(RKObjectRequestOperation *operation, NSError *error)

{

NSLog(@"Operation failed with error: %@", error);

}];

[objectRequestOperation start];

Цикл RKObjectRequestOperation

1) создается инстанс RKHTTPRequestOperation (сабкласс AFHTTPRequestOperation), эта операция запускается

Цикл RKObjectRequestOperation

2) в случае успеха инициализируется RKMapperResponseOperation и обрабатывает response. на вход ей подается:

• NSURLRequest

• NSHTTPURLResponse

• response data (NSData)

• массив дескрипторов

Цикл RKObjectRequestOperation

3) RKMapperResponseOperation составляет список дескрипторов, URL и HTTP method у которых подходят для обработки данного response

Цикл RKObjectRequestOperation

4) если объект можно десериализовать, то создается RKMapperOperation с полученным объектом и словарем, где ключом является keyPath, а значением – мэппинг

@{ "applications" : appMapping }

Цикл RKObjectRequestOperation

5) RKMapperOperation проходит по всем ключам в полученном словаре и вызывает у серверного объекта valueForKeyPath: с этим ключом

Пример

Исходный словарь:

{ "applications": [ { "title": "Афиша-Рестораны",

"body": "Самый быстрый и удобный способ выбрать в какой ресторан или бар пойти",

"author": "Илья Пучка","publication_date": "13/11/2014"

},{ "title": "Redigo",

"body": "Удобный компактный гид по странам мира от Австралии до Японии в вашем телефоне",

"author": "Стас Цыганов","publication_date": "30/01/2015"

}]}

Пример

После вызова у него valueForKeyPath:@"applications":

NSArray{

title = "Афиша-Рестораны”;body = "Самый быстрый и удобный способ выбрать в какой ресторан или бар

пойти”;author = "Илья Пучка";publication_date = "13/11/2014";

},{

title = "Redigo";body = "Удобный компактный гид по странам мира от Австралии до Японии в

вашем телефоне”;author = "Стас Цыганов";publication_date = "30/01/2015";

}

Цикл RKObjectRequestOperation

6) с полученным объектом, целевым объектом (Application) и мэппингом инициализируется RKMappingOperation

Цикл RKObjectRequestOperation

7) внутри RKMappingOperation по очереди обрабатываются все аттрибуты, указанные в мэппинге

RKObjectManager

• централизация настроек

RKObjectManager

• централизация настроек

• хэлперы для создания RKObjectRequestOperation

RKObjectManager

• централизация настроек

• хэлперы для создания RKObjectRequestOperation

• хранение списка дескрипторов

RKObjectManager

• централизация настроек

• хэлперы для создания RKObjectRequestOperation

• хранение списка дескрипторов

• роутеры

• и т.д.

RKObjectManager

[[RKObjectManager sharedManager] getObjectsAtPath:@"applications"

parameters:nil

success:successBlock

failure:failureBlock];

[[RKObjectManager sharedManager] postObject:someObject

path:nil

parameters:nil

success:successBlock

failure:failureBlock];

Core Data

Как мэппить в Core Data?

• создать стек Core Data средствами RestKit

Как мэппить в Core Data?

• создать стек Core Data средствами RestKit

• вместо RKObjectMapping использовать RKEntityMapping

Создание стека Core Data

NSURL *modelURL = /* model URL */;

NSManagedObjectModel *managedObjectModel =

[[NSManagedObjectModel alloc]

initWithContentsOfURL:modelURL];

Создание стека Core Data

RKManagedObjectStore *managedObjectStore =

[[RKManagedObjectStore alloc]

initWithManagedObjectModel:managedObjectModel];

Создание стека Core Data

[managedObjectStore createPersistentStoreCoordinator];

NSPersistentStore __unused *persistentStore =

[managedObjectStore addInMemoryPersistentStore:&error];

NSAssert(persistentStore, @"Failed to add persistent store: %@", error);

Создание стека Core Data

[managedObjectStore createManagedObjectContexts];

[RKManagedObjectStore setDefaultStore:managedObjectStore];

Создание стека Core Data

[NSPersistentStoreCoordinator

MR_setDefaultStoreCoordinator:managedObjectStore.persistentStoreCoordinat

or];

[NSManagedObjectContext

MR_setRootSavingContext:managedObjectStore.persistentStoreManagedObje

ctContext];

[NSManagedObjectContext

MR_setDefaultContext:managedObjectStore.mainQueueManagedObjectContext];

Мэппинг в Core Data

{ "applications": [

{ "title": "Афиша-Рестораны","body": "Самый быстрый и удобный способ выбрать

в какой ресторан или бар пойти","author": "Илья Пучка","publication_date": "13/11/2014","id": 12

}]

}

Мэппинг в Core Data

@interface Application: NSManagedObject

@property (nonatomic, retain) NSNumber* applicationID;

@property (nonatomic, retain) NSString* title;

@property (nonatomic, retain) NSString* body;

@property (nonatomic, retain) NSDate* publicationDate;

@end

RKEntityMapping

RKEntityMapping* appMapping =

[RKEntityMapping mappingForEntityForName:@"Application"

inManagedObjectStore:managedObjectStore];

[appMapping addAttributeMappingsFromDictionary:@{

@"id": @"applicationID",

@"title": @"title",

@"body": @"body",

@"publication_date": @"publicationDate"

}];

appMapping.identificationAttributes = @[ @"applicationID" ];

Connection

{ "applications": [

{ "appID": 12, "title": "Афиша-Рестораны" },

{ "appID": 11, "title": "Redigo" }

],

"users": [

{ "userID":101, "userName": "Стас Цыганов", "appID": 11 },

{ "userID":102, "userName": "Илья Пучка", "appID": 12 },

{ "userID":103, "userName": "Ash Furrow", "appID": 11 }

]

}

Connection

@interface Application: NSManagedObject

@property (nonatomic, retain) NSNumber* appID;

@property (nonatomic, retain) NSString* title;

@end

@interface User: NSManagedObject

@property (nonatomic, retain) NSNumber* userID;

@property (nonatomic, retain) NSString* userName;

@property (nonatomic, retain) NSNumber* appID;

@property (nonatomic, retain) Application* app;

@end

Connection

[userMapping addConnectionForRelationship:@"app"connectedBy:@{@"appID": @"appID"}];

RKManagedObjectRequestOperation

• создается с NSManagedObjectContext

RKManagedObjectRequestOperation

• создается с NSManagedObjectContext

• удаляет orphaned objects

Спасибо!