2012-03-07 29 views
3

我想構建一個與NSStringstringWithFormat:(帶有可變參數)相同簽名的函數,但是我希望在將每個參數傳遞給stringWithFormat:之前對URL進行編碼本身。我已經有一個編碼方法+ (NSString *)urlEncode:(NSString *)s。如何使用編碼參數構建va_list?在傳遞它們之前處理可變參數

+0

[Objective-C傳遞... nil終止參數列表]的可能重複(http://stackoverflow.com/questions/2345196/objective-c-passing-around-nil-terminated-argument-lists) – 2012-03-07 20:11:18

回答

0

我覺得這很難做得很好。事情是,你不能創建一個va_list; 「功能」va_start,va_end等被定義爲宏,隱藏醜惡的內部東西,你可能不想讓你的手髒。

如果你真的想這樣做,你很可能解析format字符串的內容(不應該是非常困難的,特別是如果你只承擔%@是可能的格式說明符),讓你知道有多少參數有在va_list,然後encode他們每個人並返回最終重建字符串。不過,你當然要重新實現stringWithFormat:然後。

「互聯網」告訴我有構建va_list沒有可移植的方法,但是如果可以的話,你就可以使用以下的想法:

s = [[[NSString alloc] initWithFormat:format arguments:argp] autorelease]; 

我個人認爲最好的辦法就是到每當遇到%@令牌時,請穿過字符串並建立結果,使用va_arg

這是關於@daxnitro在下面說的,我建議的方法,他稱爲選項3:這是一個快速入侵,但僅用於對象令牌,它的工作原理如下。

typedef enum { 
NORMAL, 
TOKEN 
} STATE; 

+ (NSString *) encodeWithFormat: (id) format, ... { 

STATE s = NORMAL; 
va_list argp; 
va_start(argp, format); 
unichar c; 
NSString * tmp; 
NSMutableString * out = [[NSMutableString alloc] init]; 

for(int i = 0; i < [format length]; i ++) { 

    c = [format characterAtIndex: i]; 

    // simple state-based recognising 
    switch(c) { 
    case '%': 
     if(s == NORMAL) 
      s = TOKEN; // switch to token-mode 
     else { // we were accepting tokens, so this is an escaped '%' 
      [out appendFormat: @"%c", c]; 
      s = NORMAL; 
     } 
     break; 
    default: 
     if(s == NORMAL) // default case 
      [out appendFormat: @"%c", c]; 
     else // accepting tokens, so check type 
      switch(c) { 
      case '@': // this is a string placeholder 
       tmp = va_arg(argp, NSString*); 
       [out appendFormat: @"%@", [Test encode:tmp]]; // your magic here 
       s = NORMAL; 
       break; 
      // you could add cases for %d etc here, if necessary 
      default: // some unrecognised placeholder. ignore. 
       s = NORMAL; 
      break; 
      } 
     break; 
    } 
} 
va_end(argp); 
return [out autorelease]; 
} 
1

在C,C++或Objective-C中沒有可移植的方法來構建va_lists。你可能可以編寫或查找一些使用內聯彙編的函數來直接修改堆棧並調用可變參數,但這不是一個好的方法。你有三個我能想到的實用選項。

這是第一個選項。僅使用可變字符串,並使用NSString的initWithFormat:arguments:方法轉發參數。

- (NSString*)forwardMessage:(NSString*)format, ... { 
    va_list args; 
    va_start(args, format); 

    BOOL escape = NO; 
    char* ptr = (char*)[format UTF8String]; 
    while (*ptr) { 
     if (*ptr == '%') { 
      escape = !escape; 
     } else if (escape) { 
      // argument 
      id obj = va_arg(args, id); 
      if (*ptr == '@') { 
       // object 
       if ([obj isKindOfClass:[NSString class]]) { 
        // string 
        id copy = [obj copy]; 
        if (copy != obj) { 
         // mutable 
         [obj replaceCharactersInRange:NSMakeRange(0, [obj length]) withString:@"replaced!"]; 
        } 
       } 
      } 
      escape = NO; 
     } 
     ++ptr; 
    } 

    va_end(args); 

    va_list args2; 
    va_start(args2, format); 

    NSString* ret = [[NSString alloc] initWithFormat:format arguments:args2]; 

    va_end(args2); 
    return ret; 
} 

該方法將採用可變參數,並用「replacement!」替換任何可變字符串的內容。由於我們只能在轉發它們之前讀取參數,因此我們實際上不能將不同的對象發送到initWithFormat:參數:我們只需更改對象。請注意,製作對象的副本以測試它是否像我在該方法中做的那樣是可變的,這不是很好的做法。

這是您的第二個選項。使用NSInvocation構建您的新參數到stringWithFormat :.

- (NSString*)forwardMessage:(NSString*)format, ... { 
    BOOL escape = NO; 
    NSUInteger count = 0; 

    char* ptr = (char*)[format UTF8String]; 
    while (*ptr) { 
     if (*ptr == '%') { 
      escape = !escape; 
     } else if (escape) { 
      if (*ptr == '@') { 
       // this is an object 
      } 
      ++count; 
      escape = NO; 
     } 
     ++ptr; 
    } 

    char* sig = malloc(3 + count + 2); 
    memset(sig, '@', 3 + count); 
    sig[3 + count] = ':'; 
    sig[3 + count + 1] = '\0'; 

    NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:sig]]; 

    free(sig); 

    [invocation setTarget:[NSString class]]; 
    [invocation setSelector:@selector(stringWithFormat:)]; 

    [invocation setArgument:&format atIndex:2]; 

    va_list args; 
    va_start(args, format); 

    for (NSUInteger i = 0; i < count; ++i) { 
     void* arg = va_arg(args, void*); 
     // arg is an object, you can change it here 
     [invocation setArgument:&arg atIndex:i + 3]; 
    } 

    [invocation invoke]; 

    va_end(args); 

    id ret; 

    [invocation getReturnValue:&ret]; 

    return ret; 
} 

這種方法有一個缺點:當你通過具有尺寸類型是不一樣的無效*或ID它不會工作。例如,整數有效,但浮點數不正確。 [self forwardMessage:@"test %d asd %s %f %@ %d", 2, "asd", 2.897, @"test" 5]返回test 2 asd asd 0.000000 test 5。添加更多邏輯以使特定類型正常工作並不難。

我不確定我真的可以推薦這些解決方案,但也許他們會爲你的情況工作。

第三個選項:如果你的參數只是NSString對象,我將不得不建議放棄stringWithFormat:並自己解析/形成格式化的字符串。

相關問題