subversionから移行したい、githubも良いけどプライベートなソース管理は自分でしたい。など考えている方も多いと思い、あえて今、MacMiniとMacOSXでgitのリモートサーバの構築方法をまとめておきたいと思います。
※最近ではgitosisなど便利なツールもありますが、ここでは「あえて」MacOSXのみでのサーバ構築の手法を説明します。
1.準備
MacPortsでいれても、GoogleCodeにアップされているMacOSX用のインストーラを入れても大丈夫です。。
サーバのOSX、ローカルどちらにもインストールをしてください。
ちなみにGoogleCodeのアドレスはこちらです。
http://code.google.com/p/git-osx-installer/
2.サーバでの作業
基本的にはMacOSXの管理機能で完結します。
では手順を見て行きます。
2-1グループ登録
Gitのグループをシステム環境設定のアカウントで登録します。
左下の「+」ボタンをクリックし、アカウントを追加する要領で名前などをいれます。
選択ボックスから種類をグループに変更します。
2-2ユーザー登録
Gitを使うユーザーをシステム環境設定のアカウントから登録します。
ユーザーを登録したら、先ほど作ったグループにユーザーをいれてあげましょう。
これもグループを選んで右側のユーザーのチェックボックスにチェックを入れるだけです。
必要に応じて(うちの環境ではこの問題が起こりました)ログインシェルのrcファイルにgitのパスを設定してください。rc以外に書いてもエラーが出ます。必ずrcファイルへの記載をしてください。
一つ作れば今後ユーザーの追加をした時もコピペで構いません。
2-3SSH設定
SSHを設定してgitのユーザーをsshの管理下に置きましょう。
システム環境設定を開いて、共有を選択します。その中の項目であるリモートログインを選択して、ログインを許可するユーザーを追加します。
ルータ設定(ポートフォワーディング)やファイヤーウォールの設定なども必要でしたら行ってください。
2-4リポジトリを作る
git init --bare --shared
コマンドを入力します。
このリポジトリを共有して使い続けることとなります。
(--sharedを付けた場合、このコマンドを実行したユーザーのグループでの書き込みが可能となります。2-1で作成したグループに入っているユーザーで実行をしましょう。)
3.ローカルでの準備
Gitで管理したいフォルダにTerminalで移動して下記のコマンドを打ってください。
git init
git remote add origin <サーバのGitリポジトリのアドレス>
これで設定終了です。
4.運用
外部からインターネットで接続して使用したい場合はグローバルIPを用意するか、ダイナミックDNSをの設定をして、IP変更スクリプトを定期的に実行するなどの手段で常にサーバを参照できる様にしてください。
うちではダイナミックDNSを用意して、Macのcronでスクリプトを実行しています。
ローカルでコミットをある程度ためたり、ソース管理をしておき、あるタイミングでサーバにコミットするという使い方ができます。
また、複数人で開発をする場合などもサーバとして使用しているMacOSXのユーザーを追加してSSHの接続許可をするだけで、すぐに環境を作ることができます。
※SSH接続許可を行うので、ターミナルからもログインできてしまいますので、権限には注意が必要です。
サーバにコミットするには
git push origin master
サーバから最新のソースを取得するなら
git pull origin master
をすればOKです。
ちょっとはまったのでメモ
CoreData使ってUITableViewのデータを表示していて、一括削除をしようとしたときに、
で落ちてしまっていたのでいろいろ調べて見たところ、
OS3の場合は
2010-08-13 03:08:44.863 CompatibilityTests[34809:207] OK
2010-08-13 03:08:44.874 CompatibilityTests[34809:207] 0
2010-08-13 03:08:44.882 CompatibilityTests[34809:207] 1
2010-08-13 03:08:44.913 CompatibilityTests[34809:207] CoreData: sql: BEGIN EXCLUSIVE
2010-08-13 03:08:44.932 CompatibilityTests[34809:207] CoreData: sql: DELETE FROM ZPARTNER WHERE Z_PK = ? AND Z_OPT = ?
2010-08-13 03:08:44.944 CompatibilityTests[34809:207] CoreData: sql: DELETE FROM ZPARTNER WHERE Z_PK = ? AND Z_OPT = ?
2010-08-13 03:08:44.956 CompatibilityTests[34809:207] CoreData: sql: COMMIT
2010-08-13 03:08:44.984 CompatibilityTests[34809:207] CoreData: sql: pragma page_count <−ここ
2010-08-13 03:08:44.993 CompatibilityTests[34809:207] CoreData: annotation: sql execution time: 0.0095s
2010-08-13 03:08:45.004 CompatibilityTests[34809:207] CoreData: sql: pragma freelist_count
2010-08-13 03:08:45.019 CompatibilityTests[34809:207] CoreData: annotation: sql execution time: 0.0145s
OS4だと
2010-08-13 03:09:36.351 CompatibilityTests[378:307] OK
2010-08-13 03:09:36.356 CompatibilityTests[378:307] 0
2010-08-13 03:09:36.359 CompatibilityTests[378:307] 1
2010-08-13 03:09:36.363 CompatibilityTests[378:307] CoreData: sql: BEGIN EXCLUSIVE
2010-08-13 03:09:36.372 CompatibilityTests[378:307] CoreData: sql: DELETE FROM ZPARTNER WHERE Z_PK = ? AND Z_OPT = ?
2010-08-13 03:09:36.376 CompatibilityTests[378:307] CoreData: sql: DELETE FROM ZPARTNER WHERE Z_PK = ? AND Z_OPT = ?
2010-08-13 03:09:36.380 CompatibilityTests[378:307] CoreData: sql: COMMIT
2010-08-13 03:09:36.432 CompatibilityTests[378:307] TableView:MakeCell <−ここ
2010-08-13 03:09:36.441 CompatibilityTests[378:307] CoreData: sql: SELECT 0, t0.Z_PK FROM ZPARTNER t0 ORDER BY t0.ZSEND_DATE DESC
2010-08-13 03:09:36.447 CompatibilityTests[378:307] CoreData: annotation: sql connection fetch time: 0.0064s
2010-08-13 03:09:36.451 CompatibilityTests[378:307] CoreData: annotation: total fetch execution time: 0.0108s for 0 rows.
2010-08-13 03:09:36.460 CompatibilityTests[378:307] 0
となって、COMMIT後の挙動が違っていた。
落ちてた理由はOS3で動作してたので、これでいいと思ってやってたのだけど、
まず、FetchedResultControllerをinitでロードして、それを
で
return fetchedResultsController fetchedObjects] count];
してたんだけど、
OS4の場合、Cellを作る前にTableViewはnumberOfRowInSection:で行数をとっている。
このときに古いままメモリ上にロードされてた(initでロード)数を返してしまっていたので
整合性がとれず落ちていた。
解決策はDelete時のsave:の後に再度fetchedResultControllerを新しいのに変えてあげればOK。
つまり、initでやってたロードをもう一回やってあげれば良かった。
それにしても、コンソールログをよく見ると、CoreDataのSELECTよりも早くTableViewのCell作りに行く方が早いってどういうことだろう。
と思った。
#2010/09/21追記訂正
NSFetchedResultControllerはUITableViewに特化したクラスでした。
そして、普段はNSFetchRequestでNSManagedObjectContextを参照して、そのresultを参照すると、Dataはとれます。
後ほど、改めてCoreDataについて記したいと思います。
そろそろ本腰入れて某Meets.app手をつけたいなと思っていて、そこで使用予定のCoreDataについていろいろ調べていたら
何となくわかった気がしたので、書いてみる。
基本的にはCoreDataのリファレンスとサンプルコードを見ながらなんだこれは。といった感じで調べてみた。
調べていて思ったのは、したの2点。
・どこからどこまでやってくれて、どこからどこまで自分でやるのかがわからない。(どの時点で更新されたデータが保存されるのかとか)
・fechedResultsControllerとmanagedObjectのつながり。
CoreDataBooksでは、インサートとデリートしかやってなくて、どこで更新とかしてるんだろと思ったり、
CoreRecipesでは、どこでアップデートかけてるんだろ。とおもったり。
動きがわからないため、何度かTwitterでつぶやいたところ、下記の方々に助けてもらったので、感謝の意を込めて、IDを記しておく。
Thanks to
@hitoriblog,@basuke
では、本題。
・AppDelegateでManageObjectContextを定義して、インスタンス化。
これをCoreDataを使うクラスでも共有する。
ManagedObjectContextの中でcoordinatorに入れるpersistentStoreCoordinatorメソッドでdatamodelファイル(*.xcdatamodel)を指定する。
NSManageObjectContextが同じならそれを使って更新・参照・削除・挿入ができる。
その後、NSManageObjectContextのsaveメソッドを使うと、データがDBに保存される。
・データ参照
CoreData使用クラス内で
fetchedResultsControllerを定義してインスタンス化。
entityにentity名
sortするならsotrDiscriptorにキー名を指定する。
このfetchedResultsControllerのRowをobjectAtIndexPathで指定してNSManageObjectをインスタンス化して代入後、
そのNSManageObjectのValueForKeyでキーを指定すると値参照できる
・データ追加
CoreData使用クラス内で
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
NSEntityDescription *entity = fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[newManagedObject setValue:[NSDate date] forKey:@"(キー)"];
この状態で
[context save:&error]
すると新しいデータが保存される。
・データ削除
追加とほぼ同じ。
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
[context deleteObject:[fetchedResultsController objectAtIndexPath:indexPath;
この状態で
[context save:&error]
すると選択したデータが削除される。
・データ更新
NSManagedObjectクラスを継承したentityのプロパティと同じ内容の@propertyがあるクラスを作成する。
更新したいRowのデータをこのクラスにキャストしてインスタンス化する。
(NSManagedObject継承クラス) *impMO = (NSManagedObject継承クラス *)[fetchedResultsController objectAtIndexPath:indexPath];
こんな感じ。
でこのクラスの内容を変更した後、継承したクラスが保持しているmanagedObjectContextでNSManagedContextをインスタンス化する。
それをsaveすれば完了する。
NSManagedObjectContext *context = impMO.managedObjectContext;
[context save:&error]
更新されたかどうかは、NSManagedObjectContextのhasChangesメソッドで確認できる。
されたかどうかにかかわらず更新してしまいたいならそのままsaveメソッドを使う。
・DB更新時デリゲート呼び出し
データが変更されるとcontrollerDidChangeContentメソッドが呼ばれる。
この中でテーブルビューの更新とかすればいい。
managedObjectContextが永続化されている間は挿入・更新・削除情報はすべてメモリ上に保存され、
saveメソッドを使った時点でそれらの内容がDBへ保存される。
・まとめ
SQLで言うところのTransaction管理とすべてのデータのSELECTをNSFetchedResultsControllerが行ってくれていて、更新・削除・挿入をしたときには
保持しているTransactionデータを変更しているだけ。で、要所要所でsaveメソッド使うことでcommitしましょう。と。
戻したければroolbackメソッドもあるので、直前のsaveまで戻してもらえるんだろうと。
ちなみにresetなんてメソッドもあります。All the receiver's managed objects are “forgotten.”ですって。
ただのCという噂も。
- (float)roundWithValue:(float)value Precision:(int)precision {
float roundedValue;
int i = 0;
int calcDigit = 1;
float plus = 1.00;
while (i <= precision) {
plus = plus / 10.00f;
if (i == precision) { //最後だったら
plus = plus * 5.00f;
}else if (i < precision) {
calcDigit = calcDigit * 10;
}
i += 1;
NSLog(@"NDCalcRound:Plus:%f",plus);
NSLog(@"NDCalcRound:calcDigit:%d",calcDigit);
}
roundedValue = (float)floor((value + plus) * calcDigit) / (float)calcDigit;
//NSLog(@"NDCalcRound:roundedValue:%f",roundedValue);
return roundedValue;
}
用はテキストファイルをもらって配列にして返すだけ。
- (NSArray *)parseToArrayFromTSV:(NSString *)tsv {
int i = 0;
long from = 0;
long to = 0;
NSMutableArray *rowArray = NSMutableArray alloc] init];
NSMutableArray *columnArray = NSMutableArray alloc] init];
long rowCount = 0;
long columnCount = 0;
NSRange stringRange;
for (i = 0;i < [tsv length];i++) {
NSRange range = NSMakeRange(i,1);
if (tsv substringWithRange:range] isEqualToString:@"\t"] == YES) {
stringRange = NSMakeRange(from, to - 1);
//columnArrayのcolumnCount番に項目を入れる
[columnArray insertObject:[tsv substringWithRange:stringRange] atIndex:columnCount];
//columnCountをカウントアップ
columnCount += 1;
from = i + 1;
to = 0;
}else if (tsv substringWithRange:range] isEqualToString:@"\n"] == YES) {
stringRange = NSMakeRange(from, to - 2 );
//columnArrayのcolumnCount番に項目を入れる
[columnArray insertObject:[tsv substringWithRange:stringRange] atIndex:columnCount];
NSArray *array = NSArray alloc] initWithArray:columnArray];
[rowArray insertObject:array atIndex:rowCount];
//[array release];
//rowCountをカウントアップ
rowCount += 1;
//columnCountを初期化
columnCount = 0;
[columnArray removeAllObjects];
from = i + 1;
to = 0;
}
to += 1;
}
NSLog(@"NDTsvParser:rowArray:count:%d",[rowArray count]);
return rowArray;
}
ソートする必要があったので、かいてみた。
コムソートの速度は 平均計算時間:O(n log n) 最悪計算時間:O(n log n) メモリ使用量:O(1)
利点はコードサイズが小さくて済む。
とWikipediaにかいてあった。
安定ソートではないので、Rowの番号をちゃんと保持しとかないと後々大変。
ちなみに僕はtsvからとった二次元配列をソートして、近似値Top10をとりたかった。
//ソート準備
NSMutableArray *sortArray = NSMutableArray alloc] init];
NSMutableArray *inArray = NSMutableArray alloc] init];
for (i=0; i<[bmiArray count]; i++) {
//値
[inArray insertObject:(値(例ではfloatを詰め込んでる))] atIndex:0];
//行番号
[inArray insertObject:[NSNumber numberWithInt:i] atIndex:1];
NSArray *concArray = NSArray alloc] initWithArray:inArray];
[sortArray insertObject:concArray atIndex:i];
[inArray removeAllObjects];
}
//ソートスタート
NSInteger h = [sortArray count] / 1.3;
BOOL flg = YES;
while (flg == YES) {
int swaps = 0;
for (int i=0; i+h < [sortArray count]; i++) {
if ([sortArray objectAtIndex:i+h] objectAtIndex:0] floatValue] < [sortArray objectAtIndex:i] objectAtIndex:0] floatValue]) {
NSArray *iArray = [NSArray alloc] initWithArray:[sortArray objectAtIndex:i? autorelease];
NSArray *ihArray = [NSArray alloc] initWithArray:[sortArray objectAtIndex:i+h? autorelease];
[sortArray insertObject:iArray atIndex:i+h];
[sortArray insertObject:ihArray atIndex:i];
//NSArrayのinsertObjectは代入ではなく追加なので、挿入した後は挿入前の配列要素は削除しとく。
[sortArray removeObjectAtIndex:i+1];
[sortArray removeObjectAtIndex:i+h+1];
swaps ++;
}
}
if (h == 1) {
if (swaps == 0) {
flg = NO;
break;
}
}else {
h /= 1.3;
}
}
//後はソートした順に使う。
for (i=0;i<[sortArray count];i++) {
...
}