Có cách nào để chỉ định vị trí / chỉ mục đối số trong NSString stringWithFormat không?


83

C # có cú pháp cho phép bạn chỉ định chỉ mục đối số trong một trình xác định định dạng chuỗi, ví dụ:

string message = string.Format("Hello, {0}. You are {1} years old. How does it feel to be {1}?", name, age);

Bạn có thể sử dụng các đối số nhiều lần và cũng có thể bỏ qua các đối số được cung cấp khỏi việc sử dụng. Một câu hỏi khác đề cập đến cùng một định dạng cho C / C ++ ở dạng %[index]$[format], ví dụ %1$i. Tôi đã không thể yêu cầu NSString hoàn toàn tôn trọng cú pháp này, bởi vì nó hoạt động tốt khi bỏ qua các đối số khỏi định dạng. Những điều sau đây không hoạt động như mong đợi (EXC_BAD_ACCESS vì nó cố gắng bỏ qua agetham số như một NSObject *):

int age = 23;
NSString * name = @"Joe";
NSString * message = [NSString stringWithFormat:@"Age: %2$i", name, age];

Các đối số vị trí chỉ được tôn trọng nếu không có đối số bị thiếu từ định dạng (có vẻ là một yêu cầu kỳ lạ):

NSString * message = [NSString stringWithFormat:@"Age: %2$i; Name: %1$@", name, age];

Tất cả các cuộc gọi này hoạt động bình thường trong OS X:

printf("Age: %2$i", [name UTF8String], age);
printf("Age: %2$i %1$s", [name UTF8String], age);

Có cách nào để đạt được điều này bằng cách sử dụng NSString trong Objective-C / Cocoa không? Nó sẽ hữu ích cho các mục đích địa phương hóa.


Gửi báo cáo lỗi (và cho chúng tôi biết lỗi #).
Dirk Theisen

Câu trả lời:


126

NSString và CFString hỗ trợ các đối số vị trí / có thể sắp xếp lại.

NSString *string = [NSString stringWithFormat: @"Second arg: %2$@, First arg %1$@", @"<1111>", @"<22222>"];
NSLog(@"String = %@", string);

Ngoài ra, hãy xem tài liệu tại Apple: Tài nguyên chuỗi


5
Tôi đã cập nhật câu hỏi với một số giải thích. Có vẻ như Cocoa không tôn trọng các đối số bị bỏ qua từ định dạng, đó là một tác dụng phụ của vi phạm quyền truy cập mà tôi đã nhận được.
Jason

2
Không thể tôn trọng các đối số bị bỏ qua do cách hoạt động của varargs trong C. Không có cách chuẩn nào để biết số lượng đối số hoặc kích thước của chúng. Phân tích cú pháp chuỗi xử lý điều này bằng cách suy ra thông tin từ các mã định dạng, điều này yêu cầu các mã xác định thực sự ở đó.
Jens Ayton

1
Tôi hiểu cách hoạt động của va_args; tuy nhiên, điều này dường như hoạt động như mong đợi: printf ("Tuổi:% 2 $ i", [tên UTF8String], tuổi); Tôi đã thử các printf khác với các args được sắp xếp lại / thiếu và tất cả chúng đều cho kết quả mong đợi, trong khi NSString thì không.
Jason

7
Chỉ để nhắc lại những phát hiện của tôi, sau đó: stringWithFormat:hỗ trợ các đối số vị trí miễn là tất cả các đối số được chỉ định trong chuỗi định dạng.
Jason


1

Đoạn mã sau sẽ sửa lỗi được chỉ định trong vấn đề này. Đó là một cách giải quyết và đánh số lại các trình giữ chỗ để lấp đầy khoảng trống.

+ (id)stringWithFormat:(NSString *)format arguments:(NSArray*) arguments 
{
    NSMutableArray *filteredArguments = [[NSMutableArray alloc] initWithCapacity:arguments.count];
    NSMutableString *correctedFormat = [[NSMutableString alloc ] initWithString:format];
    NSString *placeHolderFormat = @"%%%d$";

    int actualPlaceholderIndex = 1;

    for (int i = 1; i <= arguments.count; ++i) {
        NSString *placeHolder = [[NSString alloc] initWithFormat:placeHolderFormat, i];
        if ([format rangeOfString:placeHolder].location != NSNotFound) {
            [filteredArguments addObject:[arguments objectAtIndex:i - 1]];

            if (actualPlaceholderIndex != i) {
                NSString *replacementPlaceHolder = [[NSString alloc] initWithFormat:placeHolderFormat, actualPlaceholderIndex];
                [correctedFormat replaceAllOccurrencesOfString:placeHolder withString:replacementPlaceHolder];    
                [replacementPlaceHolder release];
            }
            actualPlaceholderIndex++;
        }
        [placeHolder release];
    }

    if (filteredArguments.count == 0) {
        //No numbered arguments found: just copy the original arguments. Mixing of unnumbered and numbered arguments is not supported.
        [filteredArguments setArray:arguments];
    }

    NSString* result;
    if (filteredArguments.count == 0) {
        //Still no arguments: don't use initWithFormat in this case because it will crash: just return the format string
        result = [NSString stringWithString:format];
    } else {
        char *argList = (char *)malloc(sizeof(NSString *) * [filteredArguments count]);
        [filteredArguments getObjects:(id *)argList];
        result = [[[NSString alloc] initWithFormat:correctedFormat arguments:argList] autorelease];
        free(argList);    
    }

    [filteredArguments release];
    [correctedFormat release];

    return result;
}

0

Sau khi nghiên cứu thêm, có vẻ như Cocoa tôn trọng cú pháp vị trí trong printf. Do đó, một mẫu thay thế sẽ là:

char msg[512] = {0};
NSString * format = @"Age %2$i, Name: %1$s"; // loaded from resource in practice
sprintf(msg, [format UTF8String], [name UTF8String], age);
NSString * message = [NSString stringWithCString:msg encoding:NSUTF8StringEncoding];

Tuy nhiên, sẽ rất tốt nếu có một triển khai điều này trên NSString.


1
sprintfkhông phải là một phần của Cocoa, nó là một phần của thư viện tiêu chuẩn C và việc triển khai nó là stringWithFormat:/ initWithFormat:.
Peter Hosey

Làm rõ nhận xét trước đây của tôi: Phiên bản Cacao là stringWithFormat:/ initWithFormat:. Đó là một triển khai riêng biệt (hiện tại, CFStringCreateWithFormat) với của sprintfvà bạn bè.
Peter Hosey

4
Tôi cho rằng có rất ít công dụng để nhận xét thực tế rằng việc khởi tạo msg với chính xác 512 byte cũng an toàn như việc thực hiện một bộ chọn ngẫu nhiên trên một đối tượng ngẫu nhiên, nhưng dù sao. Đối với bất kỳ ai không biết: bộ đệm kích thước cố định là một số cách dễ dàng nhất để kích hoạt. google: tràn bộ đệm
George Penn.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.