Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Pro Core Data for iOS. Глава №1. Утилитарная часть

Anna | 2.07.2014 | нет комментариев

Сегодня хочу начать свободный перевод книги Михаеля Привата и Роберта Варнера «Pro Core Data for iOS», которую можете скачать по этой ссылке. Всякая глава будет содержать теоретическую и фактическуючасть.

image

Оглавление:

  • Глава №1. Приступаем (Утилитарная часть)
  • Глава №2. Усваиваем Core Data
  • Глава №3. Хранение данных: SQLite и другие варианты
  • Глава №4. Создание модели данных
  • Глава №5. Трудимся с объектами данных
  • Глава №6. Обработка результатирующих множеств
  • Глава №7. Настройка продуктивности и применяемой памяти
  • Глава №8. Управление версиями и миграции
  • Глава №9. Управление таблицами с применением NSFetchedResultsController
  • Глава №10. Применение Core Data в продвинутых приложениях

Фактическая часть

Так как это первая глава и её дозволено считать вводной, то в качестве утилитарного задания мы предпочтем создание обыкновенного общественного приложения, которое будет отображать список наших друзей из ВК и применять Core Data для хранения данных о них.
Приблизительно (в процессе решим что добавить/исключить) таким образом будет выглядеть наше приложение позже нескольких часов (а может и минут) упорного программирования:

image image

Как Вы могли теснее додуматься, применять мы будем Vkontakte iOS SDK v2.0.
Кстати, умоляю меня извинить за то, что в фактической части будет применяться не только XCode, но иAppCode (ребятам из JB спасибо за продукт!). Всё, что дозволено сделать в AppCode, будет там сделано.

Поехали…

Создание пустого плана

Сделаем пустой план без Core Data — Single View Application.
image
image

Приложение успешно запустилось:
image

Добавление и настройка UITableView

Открываем ASAViewController.h и добавляем следующее качество:

@property (nonatomic, strong) UITableView *tableView;

Полный вид ASAViewController.h:

#import <UIKit/UIKit.h>

@interface ASAViewController : UIViewController

@property (nonatomic, strong) UITableView *tableView;

@end

Открываем ASAViewController.m и в способ viewDidLoad добавляем строки создания таблицы UITableView:

    CGRect frame = [[UIScreen mainScreen] bounds];
    _tableView = [[UITableView alloc]
                               initWithFrame:frame
                                       style:UITableViewStylePlain];
    [self.view addSubview:_tableView];

Полный вид ASAViewController.m:

#import "ASAViewController.h"

@implementation ASAViewController

- (void)viewDidLoad
{
    CGRect frame = [[UIScreen mainScreen] bounds];
    _tableView = [[UITableView alloc]
                               initWithFrame:frame
                                       style:UITableViewStylePlain];
    [_tableView setDelegate:self];
    [_tableView setDataSource:self];
    [self.view addSubview:_tableView];
}

@end

Запускаем:
image

Осталось реализовать способы делегатов UITableViewDelegate и UITableViewDataSource.
Дописываем протоколы в  ASAViewController.h:

@interface ASAViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>

Открываем ASAViewController.m и реализовываем два способа (один для возврата кол-ва друзей в списке, а 2-й для создания заполненной ячейки с данными пользователя):

#pragma mark - UITableViewDelegate & UITableViewDataSource

- (NSInteger)tableView:(UITableView *)tableView
  numberOfRowsInSection:(NSInteger)section
{
    return [_userFriends count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView
  cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *cellID = @"friendID";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
    if(nil == cell){
        cell = [[UITableViewCell alloc]
                initWithStyle:UITableViewCellStyleSubtitle
              reuseIdentifier:cellID];
    }

    //    setting default image while main photo is loading
    cell.imageView.image = [UIImage imageNamed:@"default.png"];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        NSString* imgPath = _userFriends[(NSUInteger)indexPath.row][@"photo"];
        NSData* img = [NSData dataWithContentsOfURL:[NSURL URLWithString:imgPath]];

        dispatch_async(dispatch_get_main_queue(), ^{
            cell.imageView.image = [UIImage imageWithData:img];
        });
    });

    NSString* firstName = _userFriends[(NSUInteger)indexPath.row][@"first_name"];
    NSString* lastName = _userFriends[(NSUInteger)indexPath.row][@"last_name"];
    NSString* fullName = [NSString stringWithFormat:@"%@ %@", firstName, lastName];
    cell.textLabel.text = fullName;

    NSString* status = _userFriends[(NSUInteger)indexPath.row][@"status"];
    cell.detailTextLabel.text = status;

    return cell;
}

Переменная _userFriends является свойством ASAViewController:

@property (nonatomic, strong) NSMutableArray *userFriends;

Итоговый вид ASAViewController.h и ASAViewController.m:

#import <UIKit/UIKit.h>

@interface ASAViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>

@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSMutableArray *userFriends;

@end
#import "ASAViewController.h"

@implementation ASAViewController

- (void)viewDidLoad
{
    _userFriends = [[NSMutableArray alloc] init];

    CGRect frame = [[UIScreen mainScreen] bounds];
    _tableView = [[UITableView alloc]
                               initWithFrame:frame
                                       style:UITableViewStylePlain];
    [_tableView setDelegate:self];
    [_tableView setDataSource:self];
    [self.view addSubview:_tableView];
}

#pragma mark - UITableViewDelegate & UITableViewDataSource

- (NSInteger)tableView:(UITableView *)tableView
  numberOfRowsInSection:(NSInteger)section
{
    return [_userFriends count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView
  cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *cellID = @"friendID";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
    if(nil == cell){
        cell = [[UITableViewCell alloc]
                initWithStyle:UITableViewCellStyleSubtitle
              reuseIdentifier:cellID];
    }

    //    setting default image while main photo is loading
    cell.imageView.image = [UIImage imageNamed:@"default.png"];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        NSString* imgPath = _userFriends[(NSUInteger)indexPath.row][@"photo"];
        NSData* img = [NSData dataWithContentsOfURL:[NSURL URLWithString:imgPath]];

        dispatch_async(dispatch_get_main_queue(), ^{
            cell.imageView.image = [UIImage imageWithData:img];
        });
    });

    NSString* firstName = _userFriends[(NSUInteger)indexPath.row][@"first_name"];
    NSString* lastName = _userFriends[(NSUInteger)indexPath.row][@"last_name"];
    NSString* fullName = [NSString stringWithFormat:@"%@ %@", firstName, lastName];
    cell.textLabel.text = fullName;

    NSString* status = _userFriends[(NSUInteger)indexPath.row][@"status"];
    cell.detailTextLabel.text = status;

    return cell;
}

@end

Всё должно запускаться на ура. Переходим к дальнейшему шагу.

Интегрирование ВКонтакте iOS SDK v2.0

Забираем исходники по этой ссылке.

Подключаем QuartzCore.framework
image

Добавляем Vkontakte iOS SDK
image

В ASAAppDelegate.h добавляем два протокола:

@interface ASAAppDelegate : UIResponder <UIApplicationDelegate, VKConnectorDelegate, VKRequestDelegate>

Открываем файл реализации ASAAppDelegate.m и вставляем следующие строки в способ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions:

    [[VKConnector sharedInstance]
                  setDelegate:self];
    [[VKConnector sharedInstance] startWithAppID:@"3541027"
                                      permissons:@[@"friends"]];

Данный код при запуске приложения покажет всплывающее окно пользователю для авторизации в общественной сети ВКонтакте.
image

В  ASAAppDelegate.m реализуем еще два способа:

#pragma mark - VKConnectorDelegate

- (void)        VKConnector:(VKConnector *)connector
accessTokenRenewalSucceeded:(VKAccessToken *)accessToken
{
//   now we can make request
    [[VKUser currentUser] setDelegate:self];
    [[VKUser currentUser] friendsGet:@{
            @"uid"    : @([VKUser currentUser].accessToken.userID),
            @"fields" : @"first_name,last_name,photo,status"
    }];
}

#pragma mark - VKRequestDelegate

- (void)VKRequest:(VKRequest *)request
         response:(id)response
{
    ASAViewController *controller = (ASAViewController *)self.window.rootViewController;

    controller.userFriends = response[@"response"];
    [controller.tableView reloadData];
}

Окончательный вид ASAAppDelegate.h и ASAAppDelegate.m на данном этапе:

#import <UIKit/UIKit.h>
#import "VKConnector.h"
#import "VKRequest.h"

@class ASAViewController;

@interface ASAAppDelegate : UIResponder <UIApplicationDelegate, VKConnectorDelegate, VKRequestDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) ASAViewController *viewController;

@end
#import "ASAAppDelegate.h"
#import "ASAViewController.h"
#import "VKUser.h"
#import "VKAccessToken.h"

@implementation ASAAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    // Override point for customization after application launch.
    self.viewController = [[ASAViewController alloc] initWithNibName:@"ASAViewController" bundle:nil];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];

    [[VKConnector sharedInstance]
                  setDelegate:self];
    [[VKConnector sharedInstance] startWithAppID:@"3541027"
                                      permissons:@[@"friends"]];

    return YES;
}

#pragma mark - VKConnectorDelegate

- (void)        VKConnector:(VKConnector *)connector
accessTokenRenewalSucceeded:(VKAccessToken *)accessToken
{
//   now we can make request
    [[VKUser currentUser] setDelegate:self];
    [[VKUser currentUser] friendsGet:@{
            @"uid"    : @([VKUser currentUser].accessToken.userID),
            @"fields" : @"first_name,last_name,photo,status"
    }];
}

#pragma mark - VKRequestDelegate

- (void)VKRequest:(VKRequest *)request
         response:(id)response
{
    ASAViewController *controller = (ASAViewController *)self.window.rootViewController;

    controller.userFriends = response[@"response"];
    [controller.tableView reloadData];
}

@end

Запускаем приложение и видим приблизительно следующее (не забывайте, что в указанном выше примере не применяется кэширование запросов специально):
image
image

Десерт из Core Data

Вот мы и подошли к самому увлекательному и интересному! Верю Вы еще не утратили желание доделать утилитарную часть ;) Отвлекитесь, выпейте чайку с сушками, погрызите конфетку, разомнитесь, подтянитесь.

Для чего нам тут Core Data? Мы поступим дальнейшим образом: при первом запросе к серверу ВКонтакте мы получим список друзей и запрашиваемые поля (ранг, фотография, имя, фамилия), эту информацию сбережем в локальном хранилище применяя Core Data, а потом запустим приложение и во время запроса отключим интернет и выведем список друзей пользователя, которые были сохранены локально во время первого запроса. Идёт? Тогда приступим.

Для обработки факта отсутствия интернет соединения мы воспользуемся дальнейшим способом из протокола VKRequestDelegate:

- (void)VKRequest:(VKRequest *)request
        connectionErrorOccured:(NSError *)error
{
//    TODO
}

Тело способа мы напишем немножко позднее.

Ах да, вовсе позабыл! Подключаем  CoreData.framework.
image
Добавляем три любимые нами свойства в ASAAppDelegate.h:

@property (nonatomic, strong) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong) NSPersistentStoreCoordinator *coordinator;
@property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;

Сейчас переходим в ASAAppDelegate.m для того, Дабы реализовать очевидные геттеры для всех трёх свойств.
Managed Object Model:

- (NSManagedObjectModel *)managedObjectModel
{
    if(nil != _managedObjectModel)
        return _managedObjectModel;

    _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];

    return _managedObjectModel;
}

Persistent Store Coordinator:

- (NSPersistentStoreCoordinator *)coordinator
{
    if(nil != _coordinator)
        return _coordinator;

    NSURL *storeURL = [[[[NSFileManager defaultManager]
                                        URLsForDirectory:NSDocumentDirectory
                                               inDomains:NSUserDomainMask]
                                        lastObject]
                                        URLByAppendingPathComponent:@"BasicApplication.sqlite"];

    _coordinator = [[NSPersistentStoreCoordinator alloc]
                                                  initWithManagedObjectModel:self.managedObjectModel];

    NSError *error = nil;
    if(![_coordinator addPersistentStoreWithType:NSSQLiteStoreType
                                   configuration:nil
                                             URL:storeURL
                                         options:nil
                                           error:&error]){
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return _coordinator;
}

Managed Object Context:

- (NSManagedObjectContext *)managedObjectContext
{
    if(nil != _managedObjectContext)
        return _managedObjectContext;

    NSPersistentStoreCoordinator *storeCoordinator = self.coordinator;

    if(nil != storeCoordinator){
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:storeCoordinator];
    }

    return _managedObjectContext;
}

Build… И… и… всё типично.

Сейчас переходим к созданию модели. Кстати, хочу подметить, что я делаю всё без страховки и, может быть в конце что-то с чем-то и не состыкуется, но мы же отважные программисты!
Для создания модели нам потребоваться тот самый XCode.
Открываем наш план в нём, нажимаем Control N и выбираем Core Data -> Data Model:
image

Сбережем модель под наименованием Friend:
image

Видим теснее достаточно приятель экран:
image

Сотворим новую сущность под наименованием Friend и добавим 4 свойства: last_name (String), first_name (String), status (String), photo (Binary Data).
image

Завершаем и закрываем XCode.

Следующее, что мы обязаны сделать, так это сберечь данные о пользователях позже осуществления запроса.
Открываем ASAAppDelegate.m, спускаемся к способ VKRequest:response: и изменяем его дальнейшим образом:

- (void)VKRequest:(VKRequest *)request
         response:(id)response
{
    ASAViewController *controller = (ASAViewController *)self.window.rootViewController;

    controller.userFriends = response[@"response"];
    [controller.tableView reloadData];

//    сберегаем данные в фоне, Дабы не замораживать интерфейс
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        for(NSDictionary *user in controller.userFriends){
            NSManagedObject *friend = [NSEntityDescription insertNewObjectForEntityForName:@"Friend"
                                                                    inManagedObjectContext:self.managedObjectContext];

            [friend setValue:user[@"first_name"] forKey:@"first_name"];
            [friend setValue:user[@"last_name"] forKey:@"last_name"];
            [friend setValue:[NSData dataWithContentsOfURL:[NSURL URLWithString:user[@"photo"]]] forKey:@"photo"];
            [friend setValue:user[@"status"] forKey:@"status"];

            NSLog(@"friend: %@", friend);
        }

        if([self.managedObjectContext hasChanges] && ![self.managedObjectContext save:nil]){
            NSLog(@"Unresolved error!");
            abort();
        }
    });
}

На всякой итерации мы создаём новейший объект, устанавливаем его поля и сберегаем. В консоли можете следить радующие глаз строки:
image

Такс, осталось доработать отображение таблицы при обрыве интернет соединения. Каждый код пойдёт в способ - (void)VKRequest:(VKRequest *)request connectionErrorOccured:(NSError *)error и будет выглядеть дальнейшим образом:

- (void)VKRequest:(VKRequest *)request
        connectionErrorOccured:(NSError *)error
{
//    потребуется нам для хранения словарей с пользовательской информацией
    NSMutableArray *data = [[NSMutableArray alloc] init];

//    конфигурируем запрос на приобретение друзей
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]
                                                    initWithEntityName:@"Friend"];
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"last_name"
                                                                     ascending:YES];
    [fetchRequest setSortDescriptors:@[sortDescriptor]];

//    осуществляем запрос
    NSArray *tmpData = [self.managedObjectContext executeFetchRequest:fetchRequest
                                                                error:nil];

//    обрабатываем запрос
    for(NSManagedObject *object in tmpData){
//        эта строка тут потому, что у меня в друзьях есть удаленный пользователь - мудак :) 
        if([object valueForKey:@"status"] == nil)
            continue;

        NSDictionary *tmp = @{
                @"last_name": [object valueForKey:@"first_name"],
                @"first_name": [object valueForKey:@"last_name"],
                @"photo": [object valueForKey:@"photo"],
                @"status": [object valueForKey:@"status"]
        };

        [data addObject:tmp];
    }

//    сейчас данные "перебросим" в необходимый контроллер
    ASAViewController *controller = (ASAViewController *)self.window.rootViewController;
    controller.userFriends = data;
    [controller.tableView reloadData];
}

И небольшие коррективы внести нужно в способ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath:

- (UITableViewCell *)tableView:(UITableView *)tableView
  cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *cellID = @"friendID";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
    if(nil == cell){
        cell = [[UITableViewCell alloc]
                initWithStyle:UITableViewCellStyleSubtitle
              reuseIdentifier:cellID];
    }

    //    setting default image while main photo is loading
    cell.imageView.image = [UIImage imageNamed:@"default.png"];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        NSData *img;

        if([_userFriends[(NSUInteger) indexPath.row][@"photo"] isKindOfClass:[NSData class]]){
            img = _userFriends[(NSUInteger) indexPath.row][@"photo"];
        } else {
            NSString* imgPath = _userFriends[(NSUInteger)indexPath.row][@"photo"];
            img = [NSData dataWithContentsOfURL:[NSURL URLWithString:imgPath]];
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            cell.imageView.image = [UIImage imageWithData:img];
        });
    });

    NSString* firstName = _userFriends[(NSUInteger)indexPath.row][@"first_name"];
    NSString* lastName = _userFriends[(NSUInteger)indexPath.row][@"last_name"];
    NSString* fullName = [NSString stringWithFormat:@"%@ %@", firstName, lastName];
    cell.textLabel.text = fullName;

    NSString* status = _userFriends[(NSUInteger)indexPath.row][@"status"];
    cell.detailTextLabel.text = status;

    return cell;
}

Ура! Приложение закончено и выводит оно друзей из локального хранилища:
image

Слёзы радости

Наконец-то мы завершили нашу первую, но не последнюю утилитарную часть. Каждый план Вы можете обнаружить по этой ссылке (он в архиве).

Верю, что спина и пальцы не утомились.
Верю, что Вы довольны проведенным временем в компании c Core Data.
Верю, что Вы хотите видеть продолжения.

Примечание

Ничто не может радовать автора, как оставленный комментарий, даже если это критика ;)

Благодарствую за внимание!

 

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB