Cocoa Programming Tips 1001
Foundation
NSString
Foundation - NSString
1 行づつ substring を取り出す
Keywords: lineRangeForRange
ある程度の長さの文や、ファイルをあらわしている NSString を考えよう。そこには、改行コードで区切られた、いくつかの行がある。その行を 1 つづつ取り出すにはどうすればいいか?byte 列を取り出して、自分で 1 つづつパースするのもいいよ。だけど、改行コードっていろいろあるじゃない?Mac, UNIX, DOS で違って、めんどうくさい。だから、NSString で用意されている、substringWithRange: を使うのが簡単だ。
Foundation/NSString.h
- (NSRange)lineRangeForRange:(NSRange)range;
これを使うと、range で指定した範囲を含む、最小の行を取り出してくれる。たとえば、引き数の range に最初の一文字(0, 0)を指定すると、一番最初の行を表す NSRange を取り出すことができるんだ。
これを使って、NSString から、1 行づ取り出すコードを書いてみる。
makeParseString (sample)
- (void)makeParsedString:(NSString*)string
{
NSString* parsedString;
NSRange range, subrange;
int length;
length = [string length];
range = NSMakeRange(0, length);
while(range.length > 0) {
subrange = [string lineRangeForRange:
NSMakeRange(range.location, 0)];
parsedString = [string substringWithRange:subrange];
printf("line: %s¥n", [parsedString cString]);
range.location = NSMaxRange(subrange);
range.length -= subrange.length;
}
}
range と subrange という 2 つの NSRange を使って、string の中身を走査していくんだ。まず最初は、range は string 全体を表しているんだ。で、lineRangeForRange: を使って、最初の一行を取り出す。引き数として、長さ 0 の NSRange を指定するところがミソだ。subrange が取りだせたら、substringWithRange: を使って、1 行分の string を取り出す。そして、range を更新する。取り出した文字列の文だけ、後ろに移動させてやるんだ。
これで 1 行づつにパースできるよ。
Foundation - NSString
文字列を比較する
Keywords: compare
文字列の比較は、プログラミングの基本中の基本だ!てなわけで、NSString の比較を考えよう。普通、というか standard C library を用いたプログラミングでは、文字列の比較は memcmp() を使うよな。NSString から cString を取り出して、それで比較してもいいけど、NSString でも比較用のメソッドが用意されている。それらを使うと、NSString 同士を比べてくれるんだ。結果は NSComparisonResult って型で返ってくる。そこで定義されている定数は、こんな感じだ。
Foundation/NSObjCRuntime.h
typedef enum _NSComparisonResult {
NSOrderedAscending = -1,
NSOrderedSame,
NSOrderedDescending
} NSComparisonResult;
比較した結果、同じ文字列ならば NSOrderedSame = 0 が、そうじゃなければ、それ以外が返る。memcmp() と同じだね。
あと、比較する時に、オプションを指定することができる。オプションの値は、ヘッダでは次のように定義されている。
Foundation/NSString.h
enum {
NSCaseInseistiiveSearch = 1,
NSLiteralSearch = 2,
NSbackwardSearch = 4,
NSAnchroedSearch = 8
} NSComparisonResult;
だけど、比較する時に指定できるのは、
- NSCaseInsensitiveSearch
- NSLiteralSearch
の 2 つだ。気をつけてくれ。OR を取ることによって複数指定できるよ。
さて、いよいよ本題だ。文字列を比較するには、compare: メソッドを使う。
Foundation/NSString.h
- (NSComparisonResult)compare:(NSString*)string;
- (NSComparisonResult)compare:(NSString*)string
options:(unsigned)mask;
- (NSComparisonResult)compare:(NSString*)string
options:(unsigned)mask
range:(NSRange*)compareRanage;
比較する文字列と、オプションと、範囲を指定する。オプションを省略した場合はオプション無しで、範囲を省略した場合は全範囲で実行されるんだ。
Foundation - NSString
トークンに分割する
Keywords: tokenize
文字列をデリミタで区切って、トークンに分割する、っていう処理は、きっとあらゆるところで求められるよな。どういうことかっていうと、たとえば、
"ABC, DEF, GHI"
っていう文字列があったとき、コンマと空白で区切られた部分を順々にとりだしたい。つまり、
{"ABC", "DEF", "GHI"}
っていう配列を得るか、enumerator でアクセスしたい、ってことだ。これを実現して見よう。
まず、NSString にカテゴリを使ってメソッドを加える。
HMDTString.h (sample)
@interface NSString (HMDTString)
- (NSEnumerator*)tokenize:(NSString*)delimiters;
@end
tokenize: っていうメソッドを付け加えてみた。引数にデリミタを取る。複数指定可能。
続いて、HMDTStringEnumerator っていう、外部には公開しないクラスを作ってみた。
HMDTString.m (sample)
@interface HMDTStringEnumerator : NSEnumerator
{
NSString* _string;
NSString* _delimiters;
const char* _indicator;
}
- (id)initWithString:(NSString*)string delimiters:(NSString*)delimiters;
- (NSArray*)allObjects;
- (id)nextObject;
@end
このクラスが、実際にトークン化を行うんだ。中心となるメソッド、nextObject: の実装は、こんな感じになる。
HMDTString.m (sample)
- (id)nextObject
{
const char* delis = [_delimiters cString];
const char* tmpInd;
// Skip delimiters
while(_isDelimiter(*_indicator, delis)) {
_indicator++;
}
tmpInd = _indicator;
// Terminator check
if(*_indicator == '¥0') {
// There is no more tokens
return nil;
}
// Parse token
while(*_indicator != NULL) {
if(_isDelimiter(*_indicator, delis)) {
break;
}
_indicator++;
}
return [NSString
stringWithCString:tmpInd length:(_indicator - tmpInd)];
}
まず始めに、デリミタがあったらスキップする。次に、文字の終端かどうかをチェックする(NULL チェック)。そしたら、次のデリミタが来るか、NULL が来るまで読み進めて、最後に NSString を作って return するんだ。めんどくさいけど、複雑な話ではないよね。
これを使ったサンプルコードは、こうなる。
main (sample)
NSString* string = @"abc, def gh,ijk";
NSEnumerator* enumerator = [string tokenize:@", "];
id token;
while((token = [enumerator nextObject])) {
NSLog(token);
}
実行結果はこんな感じ。
main (sample)
abc
def
gh
ijk
これはけっこう重宝すると思うよ。いや、おれは欲しかったな。
■サンプルダウンロード:
StringTokenizer.tar.gz
■関連リンク:
トークン分割する (NSScanner) (Apple)
Foundation - NSString
NSString のエンコーディング
Keywords: CFStringEncoding, NSStringEncoding
NSString には、エンコーディング機能があるんだ。NSString 自体は Unicode で文字列を保持しているんだけど、エンコードを指定して、NSData 型に書き出すことができる。このエンコーディング機能は NSString と CoreFoundation とに存在しているんだ。
ちなみに、エンコーディングで気をつけないといけないのは、環境によって可能なエンコーディングの種類が違うこと。たとえば、英語システムでは日本語エンコーディングは利用不可かもしれない。Cocoa でのエンコーディングは、すべてのシステムで利用可能なエンコーディングと、一部のマシンで可能なものとに分かれるんだ。
んじゃ、使用可能なエンコーディングを調べてみよう。まず、エンコーディングは定数で指定するんだ。これは NSString なら NSStringEncoding 型、CoreFoundation なら CFStringEncoding だ。この 2 種類の定数の間では、コンバータをかましてやる必要があるんで、注意。
NSString 側でのエンコーディングの種類は NSString.h で。CoreFoundation では CFString.h と CFStringEncodingExt.h で定義されている。基本的に、CoreFoundation の方がたくさん定義されている。CFString.h で定義されているものは、すべてのシステムで使用可能なことが保証されているんだ。CFStringEncodingExt.h の方は、そうではない。
実際にエンコーディングを使うには、まず最初に、使いたいエンコーディングが、システムで使えるかどうか調べてやらないといけない。それには CFStringIsEncodingAvailable() を使う。
CoreFoundation/CFString.h
CF_EXPORT
Boolean CFStringIsEncodingAvailable(
CFStringEncoding encoding);
使えるようなら、CFStringEncoding から NSStringEncoding に変換してやる。それには、CoreFoundation の関数 CFStringConvertEncodingToNSStringEncoding() を使う。これで、あのたくさんの CFString のエンコーディングが、NSString で使えるんだ。
CoreFoundation/CFString.h
CF_EXPORT
UInt32 CFStringConvertEncodingToNSStringEncoding(
CFStringEncoding encoding);
また、使用可能なすべてのエンコーディングを調べるには CFStringGetListOfAvailableEncoding() を使う。あと、エンコーディングから名前を得ることもできる。それには CFStringGetNameOfEncoding() だ。
CoreFoundation/CFString.h
CF_EXPORT
const CFStringEncoding *CFStringGetListOfAvailableEncodings();
CF_EXPORT
CFStringRef CFStringGetNameOfEncoding(
CFStringEncoding encoding);
続いては NSString 側での話。エンコーディングが使えるようだったら、実際にエンコードしてやろう。dataUsingEncoding: を使う。
CoreFoundation/CFString.h
- (NSData *)dataUsingEncoding:(NSStringEncoding)encoding;
- (NSData *)dataUsingEncoding:(NSStringEncoding)encoding
allowLossyConversion:(BOOL)flag;
後の方の、dataUsingEncoding:allowLossyConversion: では、コンバートする際に、いくつかの情報が失われてもいいか?ってことを指定できるんだ。たとえば、アクセントとか大文字小文字とか。日本語の場合はなにかあるのか?
NSString でも使用可能なすべてのエンコーディングを調べることができる。avaialbeStringEncodings だ。名前を得るには localizedNameOfStringEncoding: ね。
CoreFoundation/CFString.h
+ (const NSStringEncoding *)availableStringEncodings;
+ (NSString *)localizedNameOfStringEncoding:(NSStringEncoding)encoding;
で、これらを利用してエンコーディングメニューを作ってみた。使用可能なエンコーディングを、すべて表示したメニューだ。
AppDelegate.m (sample)
- (NSArray*)allAvailableEncodings
{
NSMutableArray* array = [NSMutableArray array];
const NSStringEncoding* encoding = [NSString availableStringEncodings];
while (*encoding) {
[array addObject:[NSString localizedNameOfStringEncoding:*encoding]];
[array addObject:[NSNumber numberWithInt:*encoding]];
encoding++;
}
return array;
}
上のコードは、サンプルの一部。使用可能なエンコーディングと、その値を調べるためのメソッドだ。Mac OS X 10.1.5 の日本語版では、80 を超えるエンコーディングが使用可能。いっぱいあるのぉ。
■サンプルダウンロード:
EncodingMenu.tar.gz
Foundation - NSString
NSString での日本語エンコーディング
Keywords: JIS, Shift-JIS, EUC
次に、日本語のエンコーディングにしぼって話をしよう。Cocoa で使用可能な日本語エンコーディングはどれだけあるのか?CFStringEncodingExt.h から、それらしいものを集めてみた。
AppDelegate.m (sample)
static CFStringEncoding _japaneseEncodings[] = {
kCFStringEncodingMacJapanese,
kCFStringEncodingDOSJapanese,
kCFStringEncodingJIS_X0201_76,
kCFStringEncodingJIS_X0208_83,
kCFStringEncodingJIS_X0208_90,
kCFStringEncodingJIS_X0212_90,
kCFStringEncodingJIS_C6226_78,
0x0628, // JIS_X0213
kCFStringEncodingISO_2022_JP,
kCFStringEncodingISO_2022_JP_2,
kCFStringEncodingEUC_JP,
kCFStringEncodingShiftJIS,
kCFStringEncodingInvalidId
};
これだけの定数が定義されている。JIS_X0213 を現すと思われる 0x0628 は、ヘッダファイルには定義がなかったよ。で、これらの定数の内、実際に使用可能なものはどれだけか?Mac OS X 10.1.5 で試したところ、以下の通りだ。

これらが使用可能なわけね。その他のエンコーディングについては、obsolete 扱いということかな?
■サンプルダウンロード:
EncodingMenu.tar.gz
Foundation - NSString
日本語エンコーディングの IANA 表現
Keywords: IANA
CoreFoundation の API CFStringConvertEncodingToIANACharSetName() を使うと、エンコーディングの IANA 表現を得ることができるんだ。
AppDelegate.m (sample)
CF_EXPORT
CFStringRef CFStringConvertEncodingToIANACharSetName(
CFStringEncoding encoding);
Cocoa で使える日本語エンコーディングのうち、これを使って得ることができる IANA 表現を調べてみた。以下の表の通り。
| 定数 | IANA 表現 |
| kCFStringEncodingMacJapanese | X-MAC-JAPANESE |
| kCFStringEncodingDOSJapanese | CP932 |
| kCFStringEncodingJIS_X0208_90 | JIS_C6226-1983 |
| kCFStringEncodingISO_2022_JP | ISO-2022-JP |
| kCFStringEncodingEUC_JP | EUC-JP |
| kCFStringEncodingShiftJIS | SHIFT_JIS |
Foundation - NSString
文字列を検索する
Keywords: rangeOfString:
検索ってのは文字列操作の中でも重要なものの 1 つだ。NSString では、ある文字列から部分文字列を探し出すためのメソッド、rangeOfString: を提供してるんだ。
Foundation/NSString.h
- (NSRange)rangeOfString:(NSString *)aString;
- (NSRange)rangeOfString:(NSString *)aString
options:(unsigned)mask;
- (NSRange)rangeOfString:(NSString *)aString
options:(unsigned)mask
range:(NSRange)searchRange;
基本は aString が最初に出てくる NSRange を返すんだ。mask を指定すると、いろいろな条件がつけられる。可能なマスクは以下の通り。
-
NSCaseInsensitiveSearch
大文字、小文字を無視する -
NSLiteralSearch
byte ごとに比較する。Unicode だと、合成文字(たとえば「か」と「゛」で「が」になる)のコードと、これと同一の文字をあらわす合成済みのコードとが用意されてるんだ。これのマッチングをやめるのが、このオプション。これを指定すると、スピードがとても速くなることがある。 -
NSBackwardsSearch
指定された範囲の先頭から探すか、終わりから探すか -
NSAnchoredSearch
文字列の一番先頭か、一番終わりかしか探さない
searchRange は検索する範囲を指定する。こんなところ。
で、これだけじゃ面白くないんで、実践的な検索メソッドの例を見てみよう。それは、テキストエディタの検索コマンドで使われるような検索の仕方だ。以下で見ていくのは、Developer Tools のサンプルでついてくる TextEdit での検索方法だ。
エディタで求められるような検索の仕様を考えてみよう。まず、全体の文字列と、検索する文字列とが与えられる。文字列の一部が選択されているかもしれない。選択されていなかったら、先頭(または終わり)から普通に探す。選択されていたら、まず選択されているところから後ろの部分を探す。そこになかったら、文字列の先頭から選択されている部分までを探す。つまりラップして探すんだ。
と、いう仕様の検索メソッドだ。普通のテキストエディタに求められるようなタイプだね。ではコードを。
TextEdit/TextFinder.m (Developer kit example)
@implementation NSString (NSStringTextFinding)
- (NSRange)findString:(NSString *)string
selectedRange:(NSRange)selectedRange
options:(unsigned)options
wrap:(BOOL)wrap
{
BOOL forwards = (options & NSBackwardsSearch) == 0;
unsigned length = [self length];
NSRange searchRange, range;
// In case of find forward
if (forwards) {
searchRange.location = NSMaxRange(selectedRange);
searchRange.length = length - searchRange.location;
// Find string
range = [self rangeOfString:string
options:options
range:searchRange];
// If not found look at the first part of the string
if ((range.length == 0) && wrap) {
searchRange.location = 0;
searchRange.length = selectedRange.location;
// Find again
range = [self rangeOfString:string
options:options
range:searchRange];
}
}
// In case of find backward
else {
searchRange.location = 0;
searchRange.length = selectedRange.location;
// Find string
range = [self rangeOfString:string
options:options
range:searchRange];
// If not found look at the first part of the string
if ((range.length == 0) && wrap) {
searchRange.location = NSMaxRange(selectedRange);
searchRange.length = length - searchRange.location;
// Find again
range = [self rangeOfString:string
options:options
range:searchRange];
}
}
return range;
}
@end
このソースコードは、Developer Tools の Examples/AppKit/TextEdit/TextFinder.m から抜き出しました。検束メソッドを NSString のカテゴリっていう形で実装している。引数には、検索する文字列と、いま選択されている箇所、オプション、に加えてラップして検索するかどうかを指定する。文字列の千択か所を指定する、っていうことは、NSTextView とかを使っている、っていう前提が暗黙にあるんだ。
実装自体は普通にやっている。仕様に合うように検索する範囲を決定して、rangeOfString:options:range: を使って検索する。見つからなくて、かつラップが指定してある場合は、範囲を設定し直してもう 1 回検索してやる。テキストを取り扱うアプリケーションを作る時に参考になるかも。
Copyright © 2002-2006 HMDT. All rights reserved.