2017-03-10 4 views
0

私は書式文字列を使ってURLのパス部分を簡単に設定できる方法を書いています。もともと、私はちょうどinitWithFormat:にフォーマット文字列とargsを渡しましたが、誰かが私の中を通るようになっています。 initWithFormat:に行く前に、メソッドを%でエンコードしてメソッドを変更しました。このvarargs関数を安全に処理する方法はありますか?

[request setUrlWithFormat:@"users/%@/timesheets", username]のように、usernamebmauterまたはb mauterとなります。

- (void) setUrlWithFormat:(NSString *)format, ... { 

    // loop through varargs and cleanse them for the URL path 
    va_list args; 
    va_start(args, format); 
    NSMutableArray *cleaned = [[NSMutableArray alloc] init]; 
    for(NSString *s = format; s != nil; s = va_arg(args, NSString *)) { 
     if (s == format) continue; 
     [cleaned addObject:[s stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLPathAllowedCharacterSet]]]; 
    } 
    va_end(args); 

    // put the cleansed values back into a varargs list 
    __unsafe_unretained id *argList = (__unsafe_unretained id *) calloc(1UL, sizeof(id) * cleaned.count); 
    for (NSInteger i = 0; i < cleaned.count; i++) argList[i] = cleaned[i]; 
    NSString* result = [[NSString alloc] initWithFormat:format, *argList]; 
    free(argList); 

    [self setUrl:result]; 
} 

時々私は最初forループライン上のEXC_BAD_ACCESSでクラッシュ。時々、私はinitWithString:行でクラッシュします。ほとんどの場合、それは完璧に動作します。

更新:もう一度@uliwitnessに感謝します。他の誰かが私が結んだことを見たい場合は、ここに行きます:

- (void) setUrlWithFormat:(NSString *)format, ... { 

    DLog(@"format=%@", format); 

    va_list args; 
    va_start(args, format); 

    NSMutableString *result = [format mutableCopy]; 

    NSRange range = [result rangeOfString:@"%@"]; 
    while(range.location != NSNotFound) { 

     NSObject *obj = va_arg(args, NSObject *); 
     NSString *dirty = nil; 
     if ([obj isKindOfClass:[NSString class]]) dirty = (NSString *)obj; 
     else dirty = [NSString stringWithFormat:@"%@", obj]; 

     NSString *clean = [dirty stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLPathAllowedCharacterSet]]; 
     DLog(@"dirty=%@, clean=%@", dirty, clean); 

     [result replaceCharactersInRange:range withString:clean]; 

     range = [result rangeOfString:@"%@"]; 
    } 
    va_end(args); 

    DLog(@"result=%@", result); 

    [self setUrl:result]; 
} 

答えて

2

ここではいくつか前提を単純にしています。 1つは、NILがvarargとして渡されていないため、NILがあると想定できません。代わりに、フォーマットプレースホルダの数を数え、多くのvarargsだけを取得する必要があります。

あなたが今やり遂げていることは、引数リストの最後にあります。時にはあなたは幸運にも、このリストの背後にあるランダムなメモリは8バイトで、NILのように見えてループが終了します。クラッシュすると、有効なオブジェクトポインタではない他のランダムなバイトが得られます。オブジェクトの別のクラスへのポインタのように見えるのでクラッシュします。

また、配列の最初の項目をNSStringの-stringWithFormatに渡すことができると仮定しているのはなぜですか?あなたのコードはこのテストケースで動作しますが、これは1つのフォーマットプレースホルダしかないためです。

-initWithFormat:フォーマット文字列に一致する数のパラメータをとります。現在は渡しているようなものではなく、配列ではありません(Cの配列は最初のアイテムへのポインタなので、Cは1つのオブジェクトへのポインタを配列へのポインタであり、配列の長さはわかりません。実際に渡すのは1つのアイテムです)。

この作業を行うには、フォーマット文字列解析の独自のバージョンを作成する必要があります。すばやく汚れたバージョンは、rangeOfStringを使用して"%@"を検索し、その前にその部分を文字列に追加し、エスケープされた対応する引数を追加し、文字列に残っているものがあればループを終了します。

+0

ありがとうございます。私が投稿した直後に、フォーマット指定子の数を数えなければならないと分かりました。それを知ることは、これまでに見つかったすべてのクラッシュを最初の 'for'ループで解決します。私はまだ2回目の 'for'ループクラッシュに悩まされています。そのコードは 'NSArray'をvarargsリストにパッケージ化しようとしています。私はそのコードをここに見つけ、私の目的のためにそれを微調整しました。理想的には 'NSString'は' initWithFormat:argsInArray':メソッドの型を持ちます。それが起こるまでは、フォーマット指定子を自分で置き換えなければならないことは間違いありません。 – bmauter

関連する問題