Geometry 関数
NSBundle
NSDictionary
NSFileManager
NSMutableAttributedString
NSNumber
NSScanner
NSString
-1 行づつ substring を取り出す
-文字列を比較する
-トークンに分割する
-NSString のエンコーディング
-NSString での日本語エンコーディング
-日本語エンコーディングの IANA 表現
-文字列を検索する
NSTimer
Graphic 関数
NSApplication
NSBezierPath
NSCell
NSColor
NSCursor
NSCustomImageRep
NSDocument
NSDocumentController
NSDragging
NSEvent
NSFontManager
NSGraphicsContext
NSImage
NSMenu
NSOutlineView
NSPanel
NSPopUpButton
NSResponder
NSScrollView
NSString 追加
NSTableColumn
NSTableView
NSTextStorage
NSTextView
NSToolbar
NSView
NSWindow
NSWorkspace
その他
.nib ウィンドウ
Views パレット
クラス
インスタンス変数
メソッド
CFXML
Carbon Event
Carbon Graphics
Cocoa で日本語
メソッド
その他

- 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", [string cString]);
        
        range.location = NSMaxRange(subrange);
        range.length -= subrange.length;
    }
}

rangesubrange という 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 で試したところ、以下の通りだ。

Japanese encoding

これらが使用可能なわけね。その他のエンコーディングについては、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 回検索してやる。テキストを取り扱うアプリケーションを作る時に参考になるかも。



[Home] [Download] [Archives] [BBS] [Cocoa Programming Tips 1001] [Core Foundation の秘密] [Safari Developer Center] [はじめてのブラウザのつくり方] [Sketch BP] [スクリーンセイバーを作ろう] [Objective-C 最適化] [Authorization API 完全理解] [Mac OS X Programming Books Review] [オブジェクト指向の言語比較論] [panther-dev]

mailto: mkino@xd5.so-net.ne.jp