Skip to content

Instantly share code, notes, and snippets.

@kitasuke
Last active August 29, 2015 14:00
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kitasuke/b01d03a2fdd6dee21d59 to your computer and use it in GitHub Desktop.
Save kitasuke/b01d03a2fdd6dee21d59 to your computer and use it in GitHub Desktop.
初めてのRestKit

Restkitの導入

RKGistのチュートリアルを適当に訳しました。


このチュートリアルはRestkit 0.20.0を使用しています。

このチュートリアルはRestkitに関する概念や使用方法を理解するためのものです。チュートリアルを読んだ後には、以下の内容に関して理解が得られるでしょう。

  1. Restkitとは何か、またそのメリットについて
  2. Restkitには、どのような思想があって、どう設計されているか
  3. Restkitの導入方法とwebサービスとの連携方法
  4. RestkitにおけるCore Dataの使用について
  5. RestkitとwebサービスAPIとの通信における抽象化について

前提知識

このチュートリアルは初めてRestkitを導入したいと思っている方への入門編です。 Restkitを使用した開発経験は必須ではありませんが、Cocoaフレームワークに関する基本的な理解が必要です。 また、以下の環境を用意する必要があります。  

  • Xcode >= 4.6  
  • CocosPods >= 1.6.2

Restkitはwebサーバーと連携するクライアントアプリ用のフレームワークです。Restkitを触れる際に、CocoaフレームワークやObjec tive-Cについての知識が必要になります。それらの基礎を理解した上ででRestkitに触れることをお勧めします。

Restkitの根幹を理解するために、Cocoaフレームワークに関する下記の知識を深める必要があります。

  • REST Restkitは、RESTfulクライアントアプリケーションを実装するための様々なツールを提供します。
  • Key-Value Coding
  • NSOperation NSOperationはCocoa Foundatioinをカプセル化した抽象クラスです。 RestKitでは、多くのクラスがNSOperationクラスをサブクラス化して実装されています。
  • Core Data Core Dataはオブジェクトグラフの管理および永続化のためのフレームワークです。 RestKitは、RESTFul webサービスのクライアント側におけるバックエンドの永続化をCore Dataとの連携によって実現しています。
  • AFNetworking AFNetworkingは快適な通信を可能にするネットワークライブラリです。 RestKitでは、通信処理をAFNetworkingを活用して実装しています。

RestKitとは?

この章では、RestKitの背景と概念を説明します。 >一旦この章を飛ばして、チュートリアルを一通り終えた後にこの章の説明を読んでも構いません。 >次章はRestKitのハンズオンを通して理解を深めます。

RestKitは、RESRful webサービス/クライアントアプリケーションをiOS/Mac OSXで実装するためのモダンなObjective-C用フレームワークです。 RestKitは、webサービスとやりとりするJSON、もしくはXMLデータと、アプリ内のデータモデルの両者を結合できるような構造になって いて、クライアントアプリ開発のスピードを向上できるように設計されています。 このライブラリを使用することで、実装上で良く目にする問題、特にCore Dataとの連携周りが今までより少ないコードで実装可能となっています。 また、RestKitは綺麗でテスト可能なコードとなっているため、メンテナンス性が非常に高くなっています。

RestKitは、様々なAPIの使用用途が想定されており、いくつかのデザインパターンが適用出来るようになっています。 しかし、全てのAPIにとってRestKitが最適というわけではなく、特にRPCには適していないでしょう。 どれだけ以下の状態に順応しているかが、RestKitの評価に繋がるでしょう。

  • RESTful、もしくはRESTfulのようなAPIが使用されている。

RestKitの構成要素

RestKitは複数の別々の要素で構成されています。それぞれの要素は最低限の依存性を保ちつつ、独立したライブラリとしても機能します。 詳細部分の理解はひとまず置いておき、まずは個々の要素の機能について学びましょう。

オブジェクトマッピング

オブジェクトマッピング要素は非常に多くに機能を提供しています。 キーバリューコーディングとダイナミックなObjective-cの機能間において、オブジェクトのマッピングが可能となっています。

ネットワーキング

ネットワーキング要素は、AFNetworkingによるHTTP通信とオブジェクトマッピング要素を統合しています。 JSON/XMLのパース、HTTPリクエスト/リスポンスでのオブジェクトマッピング、URLの生成機能、ローカルオブジェクトからHTTPへのパラメータ化が可能となっています。オブジェクトマネージャーやオブジェクトリクエストオペレーションなど、非常に良く使用されるクラスが含まれています。

Core Data

Core Data要素は、RestKitによるネットワーク要素のオブジェクトマッピングと、Appleのデータモデルや永続化フレームワークとの統合が可能になっています。 基本的にこの要素は、オブジェクトマッピングとネットワーキング面に特化していますが、Core Dataにおける関係性の連結などの機能も含まれています。

RKGistプロジェクトの追加

まずはRKGistプロジェクトをGithubからクローンしましょう。 そして、必要なライブラリのインストールを行います。

$ git clone git@github.com/RestKit/RKGist.git

Podfileが下記のようになっているか確認してください。

platform :ios, 6.0
pod 'RestKit', '~> 0.20.0rc'

# Include optional Testing and Search components
pod 'RestKit/Testing', '~> 0.20.0rc'
pod 'RestKit/Search', '~> 0.20.0rc'

# Import Expecta for Testing
target :test do
  link_with :RKGistTests
  pod 'Expecta', '0.2.1'
end

下記のコマンドでライブラリをインストールしましょう。

$ pod install

RKGist-Prefix.pchでRestKitをインポートする

//
// Prefix header for all source files of the 'RKGist' target in the 'RKGist' project
//

#import <Availability.h>

#ifndef __IPHONE_5_0
#warning "This project uses features only available in iOS SDK 5.0 and later."
#endif

#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
    #import <CoreData/CoreData.h>
#endif

#if __IPHONE_OS_VERSION_MIN_REQUIRED
    #import <SystemConfiguration/SystemConfiguration.h>
    #import <MobileCoreServices/MobileCoreServices.h>
#else
    #import <SystemConfiguration/SystemConfiguration.h>
    #import <CoreServices/CoreServices.h>
#endif

// Make RestKit globally available
#import <RestKit/RestKit.h>

データモデル

Gist APIを使用してデータをマッピングします。 Gistリソースに関してはGithub API v3 documentationに目を通してください。 認証無しで、パブリックなgistを取得することが出来ます。https://api.github.com/gists/publicを叩くと、以下のようなJSONを取得出来ます。

{
    "url": "https://api.github.com/gists/4774273",
    "forks_url": "https://api.github.com/gists/4774273/forks",
    "commits_url": "https://api.github.com/gists/4774273/commits",
    "id": "4774273",
    "git_pull_url": "https://gist.github.com/4774273.git",
    "git_push_url": "https://gist.github.com/4774273.git",
    "html_url": "https://gist.github.com/4774273",
    "files": {
        "gistfile1.txt": {
            "filename": "gistfile1.txt",
            "type": "text/plain",
            "language": null,
            "raw_url": "https://gist.github.com/raw/4774273/b8737fef0dd6403b2dc5d51b6fb2fd0a9141f484/gistfile1.txt",
            "size": 58
        }
    },
    "public": true,
    "created_at": "2013-02-12T22:58:52Z",
    "updated_at": "2013-02-12T22:58:53Z",
    "description": "My first gist!",
    "comments": 0,
    "user": {
        "login": "RKGistExample",
        "id": 3541778,
        "avatar_url": "https://secure.gravatar.com/avatar/cccf809ee0b340976d241cb93cc0b601?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png",
        "gravatar_id": "cccf809ee0b340976d241cb93cc0b601",
        "url": "https://api.github.com/users/RKGistExample",
        "followers_url": "https://api.github.com/users/RKGistExample/followers",
        "following_url": "https://api.github.com/users/RKGistExample/following",
        "gists_url": "https://api.github.com/users/RKGistExample/gists{/gist_id}",
        "starred_url": "https://api.github.com/users/RKGistExample/starred{/owner}{/repo}",
        "subscriptions_url": "https://api.github.com/users/RKGistExample/subscriptions",
        "organizations_url": "https://api.github.com/users/RKGistExample/orgs",
        "repos_url": "https://api.github.com/users/RKGistExample/repos",
        "events_url": "https://api.github.com/users/RKGistExample/events{/privacy}",
        "received_events_url": "https://api.github.com/users/RKGistExample/received_events",
        "type": "User"
    },
    "comments_url": "https://api.github.com/gists/4774273/comments"
}

上記はAPIで取得出来るパラメータの一部です。

  • id : gistのID
  • url : JSONを取得出来るURL
  • html_url : HTMLを取得出来るURL
  • description : gistの説明
  • public : 公開か非公開かのフラグ
  • created_at : 作成日
  • id : ユーザID
  • login : Githubのユーザ名
  • avatar_url : ユーザーのアバター画像URL
  • url : JSONを取得出来るURL

Core Dataのエンティティの作成

取得したJSONデータをカプセル化するために、Core Dataのデータモデルが必要になります。 RestKitを使用する際に、Core Dataを利用してオブジェクトのモデル化を行いましょう。 HTTPリクエストで取得したオブジェクトは、管理すべきものと管理しないオブジェクトを自由に組み合わせられます。

Core Dataのエンティティを作成するには、RKGist.xcdatamodeldファイルを追加する必要があります。 もし既にテンプレートによる既存ファイルがある場合は、そのファイルを削除してください。 次に、Gistエンティティを追加しましょう。 以下の属性を追加してください。

属性名
gistID Stinrg
jsonURL Transformable
htmlURL Transformable
descriptionText String
public Boolean
createdAt Date
updatedAt Date

次はUserエンティティの作成です。

属性名
userID Integer32
jsonURL Transformable
login String
gravatarID String
avatarURL Transformable

最後にFileエンティティの作成です。

属性名
filename String
rawURL Transformable
size Integer32

全てのエンティティの作成が終わったら、以下の通りにリレーションの設定を行います。

エンティティ 関係名 関連 逆関連 多対多関係
File gist Gist files ×
Gist files File gist
Gist user User gists ×
User gists Gist user

RestKitとCore Dataの初期設定

RKGAppDelegate.mに以下のコードを追加してください。

#import <RestKit/RestKit.h>
#import "RKGAppDelegate.h"
#import "RKGMasterViewController.h"

@implementation RKGAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSError *error = nil;
    NSURL *modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"RKGist" ofType:@"momd"]];
    // NOTE: Due to an iOS 5 bug, the managed object model returned is immutable.
    NSManagedObjectModel *managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
    RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];

    // Initialize the Core Data stack
    [managedObjectStore createPersistentStoreCoordinator];

    NSPersistentStore __unused *persistentStore = [managedObjectStore addInMemoryPersistentStore:&error];
    NSAssert(persistentStore, @"Failed to add persistent store: %@", error);

    [managedObjectStore createManagedObjectContexts];

    // Set the default store shared instance
    [RKManagedObjectStore setDefaultStore:managedObjectStore];

    // Override point for customization after application launch.
    UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
    RKGMasterViewController *controller = (RKGMasterViewController *)navigationController.topViewController;
    controller.managedObjectContext = managedObjectStore.mainQueueManagedObjectContext;
    return YES;
}

@end

まず、AppleがCore Data用に提供しているクラスではなく、RKManagedObjectStoreというRestKitが提供しているクラスを使用します。 'RKManagedObjectStore'は、Core Dataに関する全ての機能をNSManagedObjectContextと共にカプセル化したクラスです。

createManagedObjectContextsを実行すると、以下のような親子階層のmanaged object contextsが生成されます。

  • persistentStoreManagedObjectContext : persistentStoreCoordinatorと直接関係を持っているルートコンテキストです。 NSPrivateQueueConcurrencyTypeと一緒に初期化され、 I/O処理をメインスレッド外で行えます。
  • mainQueueManagedObjectContext : persistentStoreManagedObjectContextの子関係であり、NSMainQueueConcurrencyTypeと一緒に初期化されます。 メインキューコンテキストは汎用的な利用に適しています。

以下のコードを実装することで、コンテキストの調整を視覚的に理解することも可能です [INSERT FRAPHIC HERE SHOWING THE MOC CONFIGURATION

マッピング

Core Dataのデータモデルの設定が一通り終わったので、簡単なフォーマットでpublic gistsの表示させてみましょう。 RKGMasterViewController.mファイルを開いて、fetchedResultsControllerメソッドに移動してください。 NSEntityDescriptionEventエンティティをGistエンティティに変更しましょう。

- NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event" inManagedObjectContext:self.managedObjectContext];
+ NSEntityDescription *entity = [NSEntityDescription entityForName:@"Gist" inManagedObjectContext:self.managedObjectContext];

次に、sort descriptorをtimeStampからcreatedAtへ変更しましょう。

- NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timeStamp" ascending:NO];
+ NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"createdAt" ascending:NO];

これで一旦ビルドして、Gist情報を表示出来るか確認してみましょう。

このアプリケーションは、Core DataからGist情報を表示出来るように設定してあります。 しかし、まだデータ自体がストアされていません。 RestKitを使って、マッピングの設定を行いましょう。

viewDidLoadメソッドへ移動して、下記のコードを末尾に追加してください。

// Load the public Gists from Github
RKManagedObjectStore *managedObjectStore = [RKManagedObjectStore defaultStore];
RKEntityMapping *entityMapping = [RKEntityMapping mappingForEntityForName:@"Gist" inManagedObjectStore:managedObjectStore];
[entityMapping addAttributeMappingsFromDictionary:@{
 @"id":             @"gistID",
 @"url":            @"jsonURL",
 @"description":    @"descriptionText",
 @"public":         @"public",
 @"created_at":     @"createdAt"}];

RKLogConfigureByName("RestKit/Network", RKLogLevelTrace);
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:entityMapping
                                                                                        method:RKRequestMethodGET
                                                                                   pathPattern:@"/gists/public"
                                                                                       keyPath:nil
                                                                                   statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://api.github.com/gists/public"]];
RKManagedObjectRequestOperation *managedObjectRequestOperation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[ responseDescriptor ]];
managedObjectRequestOperation.managedObjectContext = self.managedObjectContext;
[[NSOperationQueue currentQueue] addOperation:managedObjectRequestOperation];

次に、configureCell:atIndexPath:へ移動して、以下の通りにコードを置き換えてください。

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
    NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = [[object valueForKey:@"descriptionText"] description];
}

もう一度ビルドをしてアプリを実行すると、Gist情報が見れるようになっているはずです。 コンソールにはJSONデータが表示されているでしょう。

さて、上記のコードを実装するだけで、データ取得と表示が可能になりました。 ここからさらに利用可能なクラスやメソッドを深堀したいところではありますが、一旦今まで行った内容を概念ベースで振り返ってみましょう。

  • JSONからCore DataのGistエンティティへのマッピング定義
  • HTTPレスポンスとGistエンティティ用のマッピングの結合
  • オブジェクトマッピングを使用した非同期通信の実装
  • リクエスト情報を取得後に表示処理を行う

次に、それぞれの処理を深堀して、RestKit APIに慣れましょう。

マッピング定義

RestKitでの全てのマッピング処理は、オブジェクトマッピングに関するものです。 いくつかの利用可能なマッピングが用意されています。 RKObjectMapping, RKEntityMapping, RKDynamicMappingなどです。 しかし、これらは全てRKMappingを継承しています。 RKEntityMappingRKObjectMappingのサブクラスで、Core Dataに関する基本機能を拡張しています。

RKGistが永続化のためにCore Dataを使用しているので、KKEntityMappingを初期化しました。 Core Dataのエンティティをオブジェクトマッピング出来るように、エンティティマッピングが定義されています。 RKEntityMappingの初期化メソッドはinitWithEntityですが、mappingForEntityForNAme:inManagedObjectStore:を使用する方が一般的です。 事前にアプリ内で設定したmanaged object storeへの参照が、[RKManagedObjectStore defaultStore]シングルトンで取得出来ます。 mappingForEntityForName:inManagedObjectStore:にエンティティ名とmanaged object storeインスタンスを渡すことで、オブジェクトマッピングを使用する準備が整います。

これでマッピングの初期化が完了したので、addAttributeMappingsFromDictionary:に、JSONのキー名と値の対応と、Core DataのGistエンティティのキー名と値を対応させるdictionary構造を渡して登録します。

- (void)addAttributeMappingsFromDictionary:(NSDictionary *)keyPathToAttributeNames
{
    for (NSString *attributeKeyPath in keyPathToAttributeNames) {
        [self addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:attributeKeyPath toKeyPath:[keyPathToAttributeNames objectForKey:attributeKeyPath]]];
    }
}

RKPropertyMappingが属性や関係のマッピングを定義します。 RKPropertyMappingRKAttributeMappingクラスとRKRelationshipMappingクラスを持った抽象クラスです。

HTTPレスポンスとマッピング

RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:entityMapping method:RKRequestMethodAny pathPattern:@"/gists/public" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];

HTTPリクエスト

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://api.github.com/gists/public"]];
RKManagedObjectRequestOperation *managedObjectRequestOperation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[ responseDescriptor ]];
managedObjectRequestOperation.managedObjectContext = self.managedObjectContext;
[[NSOperationQueue currentQueue] addOperation:managedObjectRequestOperation];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment