ラベル Code Snipet の投稿を表示しています。 すべての投稿を表示
ラベル Code Snipet の投稿を表示しています。 すべての投稿を表示

[iOS][Mac] CoreData - マイグレーションが必要かどうかを知る

2011年2月24日木曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

マイグレーションの要不要は?


CoreData では属性を追加したり変更するとマイグレーションが必要になる。過去にリリースしたアプリを新しいアプリでバージョンアップする時にマイグレーションが必要かどうか判断するにはどうしたらよいか?


実装


NSPersistentStoreCoordinator を使えば良い。
こんな感じ。
- (BOOL)isRequiredMigration
{
 CoreDataManager* manager = [CoreDataManager sharedManager];
 [[[NSPersistentStoreCoordinator alloc]
  initWithManagedObjectModel:manager.managedObjectModel] autorelease];
 
 NSURL* fileURL = [CoreDataManager fileURL_];
 NSError* error = nil;
 
 NSDictionary* sourceMetaData =
  [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType
                   URL:fileURL
                 error:&error];
 
 if (sourceMetaData == nil) {
  return NO;
 } else if (error) {
  NSLog(@"Checking migration was failed (%@, %@)", error, [error userInfo]);
  abort();
 }
 

 BOOL isCompatible = [manager.managedObjectModel isConfiguration:nil
          compatibleWithStoreMetadata:sourceMetaData]; 

 return !isCompatible;
 
}
古いバージョンのアプリで使用しているメタデータと、(これから実行しようとしている)新しいアプリで使用するメタデータの比較を行えば良い。



- - - -
NSPersistentStoreCoordinator のインスタンスは1つしか作れないという制限はなく必要ならいくつでも作ることができる。これは同じDBファイルに対してもそう(ただし非スレッドセーフなので排他制御は自分で行う必要がある。その為のメソッドも用意されている)。
上記の様にマイグレーションの要不要をチェックする為に NSPersistentStoreCoordinator のインスタンスを作ることは、複数インスタンスを作る場合の利点の一つ。普通に1つの NSPersistentStoreCoordinator インスタンスだけで通常の利用とマイグレーションを一緒にやろうとした場合、起動時にDBへアクセスしようとした時に時間のかかるマイグレーションが発生して起動時間問題にひっかかるなどの弊害が出る場合がある。
他には大量のインポートを行いたい時に専用の NSPersistentStoreCoordinator インスタンスを用意する、複数のスレッドでインスタンスを持つ、などが考えられる。SQLite に対するオプションは NSPersistentStoreCoordinator の単位で設定できるので、インポートなど特殊な用途向けのオプションを設定する場合などにインスタンスを分けることが役立つ。

ファイル書き出し

2010年8月26日木曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

覚え書き。CSVファイルを書き出した時のメモ。

ファイル書き出し


NSFileHandle が使える。

1. NSFileHandleを取得
2. NSFileHandleを使ってデータ書き出し
3. NSFileHandleをクローズ


実装例


エラー処理はお好みで。
NSFileManager* fileManager = [NSFileManager defaultManager];

// 既存チェック
if (![fileManager fileExistsAtPath:filePath]) {

 // 新規の場合は空のファイルを作成
 [fileManager createFileAtPath:filePath
        contents:[NSData data]
      attributes:nil];
}

NSFileHandle* fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];

for (NSString* row in CSVArray) {
 NSData* data = [row dataUsingEncoding:NSShiftJISStringEncoding];
 [fileHandle writeData:data];
}
[fileHandle closeFile];

MD5変換

2010年8月23日月曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

文字列を MD5変換したい。下記が参考になった。
MD5 algorithm in Objective C - Stack Overflow


サンプルコード


上記のサイトを参考に(というかほとんどそのまんまだが)NSString のカテゴリを書いてみた。

#import "NSString_Extension.h"
#import 

@implementation NSString (Extension)

/*
 * from: http://stackoverflow.com/questions/1524604/md5-algorithm-in-objective-c
 */
- (NSString *)md5String
{
    const char *cStr = [self UTF8String];
    unsigned char result[16];
    CC_MD5( cStr, strlen(cStr), result ); // This is the md5 call
    return [NSString stringWithFormat:
   @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
   result[0], result[1], result[2], result[3], 
   result[4], result[5], result[6], result[7],
   result[8], result[9], result[10], result[11],
   result[12], result[13], result[14], result[15]
   ];  
}

@end
なかなか便利。

拡張子から MIME Type を取得する

2010年8月11日水曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

MobileCoreServices.framework に 拡張子とMIME Type の相互変換用の関数が用意されている。

UTType Reference

変換方法


変換はどちらの場合も一旦 UTIへの変換を経由する。
MIME Type (例:image/png)
 ↓
UTI(例:public.png)
 ↓
拡張子(例:png)
UTI への変換には UTTypeCreatePreferredIdentifierForTag() を使う。
逆に UTIからの変換には UTTypeCopyPreferredTagWithClass() を使う。

MIME Type から 拡張子を取得する場合はこんなコードになる。
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(
 kUTTagClassMIMEType, (CFStringRef)mimeTYpe, NULL);
 
CFStringRef filenamExtension =
 UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension);
 :
CFRelease(uti);
CFRelease(filenameExtension);

どちらの関数も引数に UTTagClass を取る。これは次の2つが定義されている。
const CFStringRef kUTTagClassFilenameExtension;
const CFStringRef kUTTagClassMIMEType;
また取得した文字列は CFReleaseで開放してやる必要がある。


サンプル


こんな感じ。

UTType系関数をラップするユーティリティクラスを用意してみた。
@interface UTTypeUtility : NSObject {
}

+ (NSString*)filenameExtensionFromMimeType:(NSString*)mimeType;
+ (NSString*)mimeTypeFromFilename:(NSString*)filename;
+ (NSString*)mimeTypeFromFilenameExtension:(NSString*)filenameExtension;

+ (NSString*)UTIfromMimeType:(NSString*)mimeType;
+ (NSString*)UTIfromFilename:(NSString*)filename;
+ (NSString*)UTIfromFilenameExtension:(NSString*)filenameExtension;

@end

中身はこんな感じ。
+ (NSString*)filenameExtensionFromMimeType:(NSString*)mimeTYpe
{
 CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(
  kUTTagClassMIMEType, (CFStringRef)mimeTYpe, NULL);
 
 NSString* filenamExtension =
  (NSString*)UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension);
 
 CFRelease(uti);
 
 return [filenamExtension autorelease];
}

なお UTType系関数の利用には下記のフレームワークを追加する必要がある。

その上で MobileCoreServicesをインポートする。
#import <MobileCoreServices/MobileCoreServices.h>


ソースコード


GitHubからどうぞ。
UUTypeSample at 2010-08-10 from xcatsan's iOS-Sample-Code - GitHub
ソースコードはGitHubからどうぞ。


参考情報

Able Pear Software: How to get the file extension for a mime type on iOS
UTType関連の関数の使い方はここで知った。

CoreData - 最大値をもつ NSManagedObject を取得するコード見本

2010年8月2日月曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

以前、最大値の求め方について書いた。

Cocoaの日々: Core Data - 最大値を取得する

では、その最大値を持つ NSManagedObject を取得するにはどうしたら良いのか?


コード例


こんな感じ。
- (NSManagedObject*)lastTimeStampObject
{
    NSManagedObjectContext* moc = self.managedObjectContext;
    NSFetchRequest* request = [[[NSFetchRequest alloc] init] autorelease];

    NSEntityDescription* entity = [NSEntityDescription entityForName:@"Event"
                                              inManagedObjectContext:moc];
    [request setEntity:entity]; 

    NSSortDescriptor* sort = [NSSortDescriptor sortDescriptorWithKey:@"timeStamp"
                                                           ascending:NO];
    [request setSortDescriptors:[NSArray arrayWithObject:sort]];
    [request setFetchLimit:1];

    NSError* error = nil;
    NSArray* results = [moc executeFetchRequest:request error:&error];
    
    if ([results count] > 0) {
        return [results objectAtIndex:0];
    } else {
        return nil;
    }
}

timeStamp の降順で検索をかけ、1番目の要素を返している。
出力されているSQLは次の通り。
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZTIMESTAMP
 FROM ZEVENT t0 ORDER BY t0.ZTIMESTAMP DESC LIMIT 1

-[NSFetchRequest setFetchLimit:1] を指定により SQLでも 'LIMIT 1'が付与されている。この為結果は1もしくは0(まったくレコードが無い)のどちらかになる。



サンプル


Xcodeで生成される CoreDataサンプルコードに手を加えてみた。


左下のボタンを押すと最大値を持つ NSManagedObject の descriptionがデバッグコンソールへ出力される。

ソースコード


GitHub からどうぞ
LargestManagedObject at 2010-08-02 from xcatsan's iOS-Sample-Code - GitHub

NSCalendar - 2つの日付間の日数を取得する

2010年7月31日土曜日 | Published in | 2 コメント

このエントリーをはてなブックマークに追加

例えば 2010/07/25 と 7/27 の間の日数は2日と返すようなメソッドを作る。

実装


DateUtility というクラスを作り、そこへクラスメソッドを実装する。
@interface DateUtility : NSObject {
}
+ (NSInteger)daysBetween:(NSDate*)startDate and:(NSDate*)endDate;

+ (NSDate*)adjustZeroClock:(NSDate*)date withCalendar:(NSCalendar*)calendar
{
 NSDateComponents *components =
  [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit
     fromDate:date];
 return [calendar dateFromComponents:components];
}

+ (NSInteger)daysBetween:(NSDate*)startDate and:(NSDate*)endDate
{
 NSCalendar *calendar = [[NSCalendar alloc]
        initWithCalendarIdentifier:NSGregorianCalendar];
 startDate = [DateUtility adjustZeroClock:startDate withCalendar:calendar];
 endDate = [DateUtility adjustZeroClock:endDate withCalendar:calendar];

 NSDateComponents *components = [calendar components:NSDayCalendarUnit
              fromDate:startDate
             toDate:endDate
            options:0];
 NSInteger days = [components day];

 [calendar release];
 
 return days;
}

NSCalendar を使うと2つの日付間の日数を簡単に取得できる。ポイントとしては +adjustZeroClock:withCalendar: を使い、時刻を 0:00 に合わせていること。これをやらないと日時まで含めた比較となってしまう。
(例)7/25 11:00〜7/27 9:00 のケース
  [A] 時刻補正なし:1日となる。
  [B] 時刻補正あり:2日となる。
補正が不要であれば +ajdustZeroClock:withCalendar: の呼出をやめる。

なお -[NSCalendar components:fromDate:toDate:options:] の最初に引数に NSMonthCalendarUnit を含めると、結果は日数ではなく月数+日数の組み合わせになるので注意が必要。
(例)7/10〜8/19の日のケース
  [A] NSDayCalendarUnit|NSMonthCalendarUnit
   [components month] == 1
   [components day]   == 10

  [B] NSDayCalendarUnit
   [components day]   == 40

参考情報

NSDate Class Reference
NSDateのリファレンス
NSCalendar Class Reference
NSCalendarのリファレンス
NSDateComponents Class Reference
NSDateComponentsのリファレンス
Date and Time Programming Guide
日付関連クラスの解説

UIActionSheet のサブクラス化

2010年7月27日火曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

初期化メソッドの最後が可変引数なので、これをオーバーライドする場合は工夫が必要。

初期化メソッドのオーバーライド


可変引数はあとから -[addButtonWithTitle:]で追加してやる。

- (id)initWithTitle:(NSString *)title
 delegate:(id < UIActionSheetDelegate >)delegate
 cancelButtonTitle:(NSString *)cancelButtonTitle
 destructiveButtonTitle:(NSString *)destructiveButtonTitle
 otherButtonTitles:(NSString *)firstOtherTitle,...
{
 self = [super initWithTitle:title
        delegate:delegate
     cancelButtonTitle:nil
   destructiveButtonTitle:nil
     otherButtonTitles:nil];


 if (self) {
  int index = 0;
  
  if (destructiveButtonTitle) {
   [self addButtonWithTitle:destructiveButtonTitle];
   self.destructiveButtonIndex = index;
   index++;
  }
  
  if (firstOtherTitle) {
   [self addButtonWithTitle:firstOtherTitle];
   index++;
 
   va_list args;
   va_start(args, firstOtherTitle);
   NSString* title;
   while (title = va_arg(args, NSString*)) {
    [self addButtonWithTitle:title];
    index++;
   }
   va_end(args);
  }
  
  [self addButtonWithTitle:cancelButtonTitle];
  self.cancelButtonIndex = index;
 }
 return self;
}

試しに呼び出すとこんな感じ。ちゃんと動いているようだ。

なお self=[super ...] で cancel と destructive ボタンを指定すると順番がおかしくなる。


これを防ぐために cancel と destructive ボタンも標準の順番に合うように追加している。


参考情報

UIActionSheet addButtonWithTitle: doesn't add buttons in the right order - Stack Overflow
今回の方法が載っていた
CodeResource - Uncategorized Messages - Uiactionsheet And Popviewcontrolleranimated
同様の方法が示されていた
Cocoa with Love: Variable argument lists in Cocoa
Cocoaにおける可変引数の記事
Cocoaの日々 - 2005年1月
当ブログでも大昔に取り上げたことがあった。
UIActionSheet Class Reference
リファレンス

- - - -
次回は UIActionSheet の Blocks化をやります(今回はその伏線)。

Core Data - 最大値を取得する

2010年7月19日月曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

Core Data に格納されたデータの中から特定の属性値が最大値を取得したい。
SQL だと
SELECT MAX(timeStamp) FROM Book;
と、たった一行で簡単に取得できるが Core Data ではどうか?


前提


こんなエンティティがあったとする。

この属性値 timeStamp の最大値(すなわち最も最近の日時)を取得するメソッドを用意する。またこのメソッドは絞り込みの条件として Author(NSManagedObjectのサブクラス)を渡すことができる。


コード見本


こんな感じ。

- (Book*)lastTimeStampOfAuthor:(Author*)author
{
 NSManagedObjectContext* moc = self.managedObjectContext;
 
 NSFetchRequest* request = [[NSFetchRequest alloc] init];
 
 // entity
 NSEntityDescription* entity = [NSEntityDescription entityForName:@"Book"
    inManagedObjectContext:moc];
 [request setEntity:entity]; 

 // expression
 NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:@"timeStamp"];
 NSExpression *expression =
 [NSExpression expressionForFunction:@"max:"
   arguments:[NSArray arrayWithObject:keyPathExpression]];

 // expresssion description
 NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
 [expressionDescription setName:@"maxTimeStamp"];
 [expressionDescription setExpression:expression];
 [expressionDescription setExpressionResultType:NSDateAttributeType];

 // result properties
 [request setResultType:NSDictionaryResultType];
 [request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];

 // predicate
 if (author) {
  NSPredicate* predicate = [NSPredicate predicateWithFormat:@"Author == %@", author];
  [request setPredicate:predicate];
 }

 // execution
 NSError* error = nil;
 NSArray* array = [moc executeFetchRequest:request error:&error];
 NSDate* timeStamp = nil;
 
 if (error) {
  NSLog(@"[ERROR] %@", error);
 } else {
  timeStamp = [[array objectAtIndex:0] valueForKey:@"maxTimeStamp"];
 }

 [expressionDescription release];
 [request release];
 return timeStamp;
 
}


長っ...


参考情報


Core Data Programming Guide: Fetching Managed Objects - Fetching Specific Values


補足

(7/27補足)SQLを確認したところ次のようになっていた。
CoreData: sql: SELECT max( t0.ZTREATEDDATE) FROM ZKARTE t0 WHERE  t0.ZCUSTOMER = ? 

やっぱり1行か。

UIImage から縮小画像を生成する (2) iPhone4 高解像度対応

2010年7月1日木曜日 | Published in | 2 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: UIImage から縮小画像を生成する

前回作成したメソッドに手を入れて iPhone4向け高解像度対応を行う。

デバイス毎の解像度に対応した縮小


例えば 960x300 の縮小画像を作成する場合、iPhone 3G/3GS で横幅ぴったりに表示できる画像のサイズは 320 x 100 となる。一方、iPhone4の場合は 640 x 200 が望ましい。どちらか一方のデバイスをターゲットにするアプリの場合は特に問題ないが、両方のデバイスをターゲットとする場合はデバイスの解像度に合わせた縮小率を適用する必要がある。


UIGraphicsBeginImageContextWithOptions


iOS4 からは UIGraphicsBeginImageContextWithOptions() が導入されて、これを使うとデバイス毎の解像度の違いを吸収することができる。
UIKit Function Reference - UIGraphicsBeginImageContextWithOptions

この関数ではビットマップに適用する倍率を指定することができる。
(例)UIGraphicsBeginImageContextWithOptions(CGRectMake(100, 100), NO, 2.0);

この場合、実際に作成されるビットマップのサイズは 200x200(ピクセル)となる。この倍率(scale)は 0 を指定することができて、その場合はデバイスに適した倍率が自動的い採用される。現在のところ iPhone3G/3GS は 1.0、iPhone4 は 2.0 となる。


高解像度対応版 縮小処理


前回のコードに手を入れて UIGraphicsBeginImageContext() の代わりに UIGraphicsBeginImageContextWithOptions()を使うようにする。
#import <CoreGraphics/CoreGraphics.h>

@implementation UIImage (extension)

- (UIImage*)imageByShrinkingWithSize:(CGSize)size
{
 CGFloat widthRatio  = size.width  / self.size.width;
 CGFloat heightRatio = size.height / self.size.height;
 
 CGFloat ratio = (widthRatio < heightRatio) ? widthRatio : heightRatio;

 if (ratio >= 1.0) {
  return self;
 }

 CGRect rect = CGRectMake(0, 0,
        self.size.width  * ratio,
        self.size.height * ratio);
 
 UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0); // 変更

 [self drawInRect:rect];

 UIImage* shrinkedImage = UIGraphicsGetImageFromCurrentImageContext();
 
 UIGraphicsEndImageContext();
 
 return shrinkedImage;
}


実行例


次の画像を縮小してみた。サイズ 320x480 ピクセル。
縮小後のサイズは iPhone3G/3GS で 80x120 ピクセルとする。

生成されるビットマップ画像のサイズがそれぞれのデバイスで異なることをiPhoneシミュレータを使って確認してみる。

検証に使ったコードの一部
UIImage* image = [UIImage imageNamed:@"sample1.png"];
 CGSize size = CGSizeMake(80, 120);
 
 UIImage* shrinkedImage = [image imageByShrinkingWithSize:size];
 NSLog(@"image.scale: %f, size:[%f, %f]",
    shrinkedImage.scale,
    shrinkedImage.size.width,
    shrinkedImage.size.height);
 
 NSLog(@"cgimage.size:[%d, %d]",
    CGImageGetWidth(shrinkedImage.CGImage),
    CGImageGetHeight(shrinkedImage.CGImage));
UIImage.size と CGImageGetWidth/Height をコンソールへ出力して見比べてみた。

iPhone3の結果(iPhoneシミュレータ)

image.scale: 1.000000, size:[80.000000, 120.000000]
cgimage.size:[80, 120]

iPhone4の結果(iPhoneシミュレータ)

image.scale: 2.000000, size:[80.000000, 120.000000]
cgimage.size:[160, 240]

シミュレータでは見た目の違いはわからないが、コンソールの出力からは予想通りiPhone4では scaleが 2.0 となっており、ビットマップのサイズも iPhone3の縦横2倍となっているのがわかる。一方で、UIImage.size は両者とも同じになっている。実は iOS4から UIImage.sizeの定義が変わっている。リファレンスによると iOS4 からはピクセルではなく、ポイントを返すようになっている。
In iPhone OS 4.0 and later, this value reflects the logical size of the image and is measured in points. In iPhone OS 3.x and earlier, this value always reflects the dimensions of the image measured in pixels.

UIImage Class Reference

単位が変わるなんてちょっとドラスティックな変更な気もするが、従来のコードの修正を最小限に抑えるという意味ではうまい方法かもしれない。

参考情報


iPhone Application Programming Guide: Supporting High-Resolution Screens
アプリを高解像度対応させる方法についての解説

UIImage から縮小画像を生成する

2010年6月30日水曜日 | Published in | 2 コメント

このエントリーをはてなブックマークに追加

UIImage からサムネイル用途で使用する縮小画像を作る。

縮小処理


こんな感じ。
@implementation UIImage (extension)

- (UIImage*)imageByShrinkingWithSize:(CGSize)size
{
 CGFloat widthRatio  = size.width  / self.size.width;
 CGFloat heightRatio = size.height / self.size.height;
 
 CGFloat ratio = (widthRatio < heightRatio) ? widthRatio : heightRatio;

 if (ratio >= 1.0) {
  return self;
 }

 CGRect rect = CGRectMake(0, 0,
     self.size.width  * ratio,
     self.size.height * ratio);
 
 UIGraphicsBeginImageContext(rect.size);

 CGContextRef context = UIGraphicsGetCurrentContext();
 [self drawInRect:rect];

 UIImage* shrinkedImage = UIGraphicsGetImageFromCurrentImageContext();
 
 UIGraphicsEndImageContext();
 
 return shrinkedImage;
}
UIImage のカテゴリとして実装した。縦横比は保持したまま、与えられたサイズ内に収まる最大の画像を返す。


参考情報

補間方法を指定してUIImageを縮小する(1) - tosh-tの日記
画像を縮小する時の補完方法について

人気の投稿(過去 30日間)