読者です 読者をやめる 読者になる 読者になる

assaulter's diary

最近はバイクブログと化している...

AngularJSのプロジェクトを共有する

前回の記事で作成したAngularJSのプロジェクトの共有方法について。

デフォルトで.gitignoreがついてくるので、そのまま適当なgitサーバーに追加。 そのプロジェクトをくろーんしてきた人のやることについて。

npm install
...
...
bower install
...
...
grunt
...
...

これで必要な物件は揃うので、grunt serverで起動。楽ですね。

ちなみに

npmはpackage.json、bowerはbower.jsonを利用します。

また、Gruntfile.jsには、起動時に実行するスクリプトや、監視対象のファイルのパス(ファイルに変更があると登録されているイベントが発火、JSHintを実行する等)が書かれてます。

AngularJSのプロジェクトを作成する

チュートリアルを終え、さてToDoアプリでも作りますかと思ったが、プロジェクトどうやって作るんだろうと思ったので、調べた結果をメモしておきます。

Nodeの環境

そもそものNodeの環境も、nodebrewってのがあるので入れました。

入れ方は下記を参照。

hokaccha/nodebrew · GitHub

AngularJS generator

yeoman/generator-angular · GitHub

こいつを使います。読み方はヨーマンですかね。

YeomanでAngularJSのテンプレートを作成する

yo angular [app-name]

を実行すると、SassとTwitterBootstrapを使うかどうか、あとは4つばかしのモジュールを入れるかどうかを聞かれるので適当に。

他にもrakeコマンドっぽく、コントローラーの作成等も出来るみたいなので、それはおいおい。

Rubyでインスタンスに対して固有のメソッドを定義する

テスト時に、特定のメソッドが実行されているかどうかを確認したい時に、こういう方法を使ったのでメモ。

要件

private な send_mail メソッドが、実行されていることをRSpecで確認したい。

ベースとなるクラス

class Some
  def execute
    # 状況に応じてsend_mailを実行したり、しなかったりする
  end
  
  private 
  
  def send_mail
    # メールが送信される的な
  end
end

RSpec内の記述

it 'send_mailメソッドが実行されること' do
  some = Some.new
  class << some
    # someの特異クラス内
    attr_accessor :is_executed
    def send_mail
      @is_executed = true
    end
  end

  some.execute
  expect(some.is_executed).to be_true
end

局所的にやるんだったら上記の記述でおkですが、複数同じことやるんだったらこんな感じにもできます。

module SendMailChecker
  attr_accessor :is_executed
    def send_mail
      @is_executed = true
    end
end

it 'send_mailが実行されること' do
  some = Some.new
  some.extend(SendMailChecker)
  
  some.execute
  expect(some.is_executed).to be_true
end

オブジェクト.extendしてあげると、オブジェクトの特異クラスにモジュールを取り込むことになるので、先ほどの書き方と一緒になるというわけです。

参考:Ruby7不思議 - 特異クラス・特異メソッド・クラスメソッド - basyura's blog

Rubyで大文字小文字を無視した検索

配列に対して、特定の文字列が含まれるかどうかを調べる際には、こんな感じにしますよね?

%w(test sample code).include?('test')

この場合、testに完全に一致しないとtrueを返してくれないので、大文字小文字を無視して検索したい場合にはこんな感じですかね。

%w(test sample code).find{|item| item =~ /^test$/i}.nil?.!

ちなみに、railsで使うんだったら、nil?.!の部分がpresent?でいいと思います。

日本で一番簡単にビットコインが買える取引所 coincheck bitcoin

Rubyで簡易ストラテジー

いい感じに慣れてきたので、Rubyに関するメモを残していこうと思います。

簡易ストラテジーの実装

異なるkeyを渡した場合に、一部のメソッドの動作を変更したい場合を想定。

class SearchClass
  module IdSearchRequest
    def self.request(value)
      # keyがIDの場合のリクエストを返却
    end
  end

  module NameSearchRequest
    def self.request(value)
      # keyがNAMEの場合のリクエストを返却
    end
  end

  MODULE_BY_KEY = {
    id: IdSearchRequest,
    name: NameSearchRequest
  }
  
  def search(key, value)
    request = MODULE_BY_KEY[key.to_sym]::request(value)
    # リクエストオブジェクトを使って検索を実行
  end
end

JavaとかだとEnumを使って実装するんでしょうが、Rubyで実装する場合はこんな感じですかね。

ちなみに、モジュールにインスタンス変数を持たせたい場合は、モジュールを取ってきてclass.eval等でincludeする方法も結構使ってます。

CoreDataを使ってみる

最近はRailsばかりいじってて、久しぶりにXCode開いたら色々忘れていたので、まずはCoreDataについてメモしていきます。

参考にしたのは下記の本です

iOS開発におけるパターンによるオートマティズム

iOS開発におけるパターンによるオートマティズム

今日のゴール

CoreDataを扱うクラスを作成してみる。

1.DataModelを作成する

newfile→Core Data→Data Modelから作成 シンプルにリレーションはなし。

f:id:assaulter:20131027141350p:plain

この状態でnewfile→NSManagedObjectSubClassを選択。すると設定したEntityと等価のクラスが作成される。

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>


@interface GuidePageEntity : NSManagedObject

@property (nonatomic, retain) NSString * identifyer;
@property (nonatomic, retain) NSString * imageUrl;
@property (nonatomic, retain) NSNumber * isLastPage;
@property (nonatomic, retain) NSNumber * pageNumber;

@end

2.CoreDataを扱うクラスを作成する

登場人物の紹介

ここで一旦CoreDataを使う場合に出てくるオブジェクト達を紹介します。

ManagedObjectModel

生成法の一例

// Returns the managed object model for the application.
// If the model doesn't already exist, it is created from the application's model.
- (NSManagedObjectModel *)managedObjectModel
{
    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"GuidePageModel" withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}

newfile→Core Data→Data Modelで作成した名前を指定して作成しています。なので、あくまでデータモデルを定義しているだけ(という風に理解している・・・)

Persistent Store Cordinator

// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }
    
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"GuidePageModel.sqlite"];
    
    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    
    return _persistentStoreCoordinator;
}

データ永続化先を指定、データモデルを指定、で生成される。これら両者のブリッジを行っている・・・

Managed Object Context

_managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];

return _managedObjectContext

基本的にRead/Writeな操作はこの人が提供するので、自分が管理するモデルとストア先を指定してあげる。

よって、これらの初期化諸々を行い、外部に必要なAPIを公開するようなクラスを作成すればいいと思われる。

AmazonS3とiOS間でファイルをやりとりする(その2)

前回 - AmazonS3とiOS間でファイルをやりとりする(その1) - assaulter's diary
の続きになります。僕はこういう感じで実装しました。

ダウンロード

仕様 : ユーザーにはDLの結果のみを見せる(今回はStringを返す)。データはよしなに扱う。

Clientを作成

メンバで保持。初期化時に鍵が必要。また、リージョンを設定しておく(やらない場合はデフォルトが入ったはず)。

- (id)init {
    if (self = [super init]) {
        _client = [[AmazonS3Client alloc] initWithAccessKey:ACCESS_KEY_ID withSecretKey:SECRET_KEY];
        _client.endpoint = [AmazonEndpoints s3Endpoint:AP_NORTHEAST_1];
    }
    return self;
}
ダウンロードapi
/// 公開する関数
- (void)downloadDataWithCompletion:(void(^)(NSString* message))resultBlock {
    [self getDataWithCompletion:^(S3GetObjectResponse *response) {
        /// response.bodyに格納されているので、よしなにやる
        if (response.bodyを使ってよしなにやった結果) {
            resultBlock(@"DL成功");
        } else {
            resultBlock(NSLocalizedString(@"保存失敗");
        }
    } errorBlock:^(NSError *error) {
        resultBlock(error.userInfo[@"NSLocalizedDescription"]);
    }];
}

/// ヘルパ関数
- (void)getDataWithCompletion:(void(^)(S3GetObjectResponse* response))responseBlock errorBlock:(void(^)(NSError* error))errorBlock {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        S3GetObjectRequest* getRequest = [[S3GetObjectRequest alloc] initWithKey:@"オブジェクトの名前" withBucket:@"バケット名"];
        getRequest.contentType = CONTENT_TYPE;
        
        S3GetObjectResponse* getResponse = [self.client getObject:getRequest];
        /// ここからmain threadで処理される(非同期)
        dispatch_async(dispatch_get_main_queue(), ^{
            if (getResponse.error) {
                errorBlock(getResponse.error);
            } else {
                responseBlock(getResponse);
            }
        });
    });
}

通信してレスポンスを取ってくるところの非同期処理はGCDを使いました。
参考 - 8.2 Grand Central Dispatch · mixi-inc/iOSTraining Wiki · GitHub
ネストはちょい深くなるけど、mainスレッドに非同期で返す部分をわざわざperformSelectorOnMainThreadでやると関数書かないといけないので、block返すだけとかそういう場合はこっちの方がスッキリするかな。

アップロード

あんま変わりませんね。clientは既に用意されている前提で、ユーザーには結果しか返しません。

/// 公開する関数
- (void)uploadDataWithCompletion:(void(^)(NSString* message))resultBlock {
    [self putDataWithResponseBlock:^(S3PutObjectResponse *response) {
        resultBlock(@"uploadに成功した");
    } errorBlock:^(NSError *error) {
        resultBlock(error.userInfo[@"NSLocalizedDescription"]);
    }];
}

/// ヘルパー
- (void)putDataWithResponseBlock:(void(^)(S3PutObjectResponse* response))responseBlock errorBlock:(void(^)(NSError* error))errorBlock {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        S3PutObjectRequest* putRequest = [[S3PutObjectRequest alloc] initWithKey:@"オブジェクト名" inBucket:@"バケット名"];
        putRequest.contentType = CONTENT_TYPE;
        putRequest.data = 適当なメソッドでデータを取ってくる;
        
        S3PutObjectResponse* putResponse = [self.client putObject:putRequest];
        dispatch_async(dispatch_get_main_queue(), ^{
            // ここからはmainThreadで実行される
            if (putResponse.error) {
                errorBlock(putResponse.error);
            } else {
                responseBlock(putResponse);
            }
        });
    });
}