2017-03-10 9 views
0

형식 문자열을 사용하여 URL의 경로 부분을 쉽게 설정할 수있는 방법을 작성했습니다. 원래 형식 문자열과 args를 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

여기에 몇 가지 가정이 있습니다. 하나는 NIL을 vararg로 전달하지 않으므로 NIL이 있다고 가정 할 수 없습니다. 대신 형식 자리 표시 자의 수를 세고 수많은 varargs 만 잡아야합니다.

지금하고있는 일은 인수 목록의 끝에서 실행 중입니다. 때로는 운이 좋고이 목록 뒤에있는 임의의 메모리는 8 바이트로 밝혀 지므로 NIL처럼 보이고 루프가 종료됩니다. 충돌이 발생하면 유효한 객체 포인터가 아닌 다른 임의의 바이트를 얻거나 다른 객체 클래스에 대한 포인터처럼 보이게되므로 충돌이 발생합니다.

또한 왜 배열의 첫 번째 항목을 NSString의 -stringWithFormat에 전달할 수 있다고 가정합니까? 코드가이 테스트 케이스에서 작동하지만, 이는 하나의 형식 자리 표시 자만 있기 때문입니다.

-initWithFormat : 형식 문자열에 일치하는 수의 매개 변수를 사용합니다. 다른 숫자가 아닙니다. (현재 지나가고있는 것과 같지 않습니다.) 배열이 아닙니다. (아마도 여러분이 지나가고 있다고 생각 합니다만, C의 배열은 첫 번째 항목에 대한 포인터이므로 C는 하나의 객체에 대한 포인터를 알 수 없습니다. 배열에 대한 포인터이며, 배열의 길이를 알지 못합니다. 실제로 전달하는 것은 하나의 항목입니다.)

이 작업을하려면 형식 문자열 구문 분석의 고유 한 버전을 작성해야합니다. quick-and-dirty 버전은 rangeOfString을 사용하여 "%@"을 찾은 다음 그 부분을 문자열에 추가 한 다음 이스케이프 된 해당 인수를 추가하고 문자열에 남아있는 내용이 있으면 루프가 끝나게됩니다.

+0

감사합니다. 제가 게시 한 직후, 형식 지정자의 수를 계산해야한다는 것을 알았습니다. 그것을 알면 지금까지 발견 된 모든 충돌을 첫 번째'for' 루프에서 해결합니다. 나는 여전히 두 번째'for' 루프 크래시에 붙어 있습니다. 이 코드는 'NSArray'를 varargs 목록에 패키지화하려고합니다. 나는 그 코드를 여기에서 찾아서 내 목적에 맞게 조정했다. 이상적으로는'NSString'은 메소드의 타입 인'initWithFormat : argsInArray'를 가질 것입니다. 나는 그것이 일어날 때까지 형식 지정자를 독자적으로 바꾸어야 할 것이라고 당신이 맞다고 생각합니다. – bmauter