Rambler.iOS #2: Введение в RestKit
-
Upload
rambler-ios -
Category
Technology
-
view
350 -
download
1
Transcript of Rambler.iOS #2: Введение в RestKit
Что такое RestKit?
• Фреймворк для взаимодействия с RESTful API
• Разрабатывается с 2011 года
• 7750 🌟 на GitHub
Что такое RestKit?
• Фреймворк для взаимодействия с RESTful API
• Разрабатывается с 2011 года
• 7750 🌟 на GitHub
• 4 коммита от Сергея Крапивенского 👸
Терминология
• mapping – преобразование данных из одного представления в другое
• source – объект, из которого производится мэппинг
Терминология
• mapping – преобразование данных из одного представления в другое
• source – объект, из которого производится мэппинг
• destination – объект, в который производится мэппинг
Терминология
• mapping – преобразование данных из одного представления в другое
• source – объект, из которого производится мэппинг
• destination – объект, в который производится мэппинг
• АПИ – лень выговаривать ЭйПиАй
Зачем он мне?
• API клиент и мэппинг в одном флаконе
• Упрощает жизнь при работе с плохим JSON
• Легкий мэппинг напрямую в Core Data
Зачем он мне?
• API клиент и мэппинг в одном флаконе
• Упрощает жизнь при работе с плохим JSON
• Легкий мэппинг напрямую в Core Data
• Мэппинг можно прикрутить к другому сетевому стеку
Зачем он мне?
• API клиент и мэппинг в одном флаконе
• Упрощает жизнь при работе с плохим JSON
• Легкий мэппинг напрямую в Core Data
• Мэппинг можно прикрутить к другому сетевому стеку
• Упрощает тестирование мэппинга
Минусы
• Много issues на GitHub
• Сетевой слой использует AFNetworking 1.0
• Много задач на одну библиотеку
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
• 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
Мэппинг без KVC:
[{ "title": "Redigo",
"body": "Удобный компактный гид по странам мира от Австралии до Японии в вашем телефоне",
"author": {"name": "Стас Цыганов","email": "[email protected]"
},"publication_date": “30/01/2015"
}]
Мэппинг кривого 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
@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
• заранее неизвестно какой мэппинг потребуется
• нужный RKObjectMapping подбирается при анализе полученных данных
RKDynamicMapping
• заранее неизвестно какой мэппинг потребуется
• нужный RKObjectMapping подбирается при анализе полученных данных
• выбор осуществляется с помощью блока
RKDynamicMapping
• заранее неизвестно какой мэппинг потребуется
• нужный RKObjectMapping подбирается при анализе полученных данных
• выбор осуществляется с помощью блока
• или при анализе аттрибута в JSON с помощью класса RKObjectMappingMatcher
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
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
• централизация настроек
• хэлперы для создания 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 средствами 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