كيفية إنشاء NSString من سلسلة تنسيق مثل @"xxx=%@, yyy=%@" وNSArray من الكائنات؟

StackOverflow https://stackoverflow.com/questions/1058736

سؤال

هل هناك أي طريقة لإنشاء NSString جديد من سلسلة تنسيق مثل@"xxx = ٪@، yyy = ٪@" و nsarray من الكائنات؟

يوجد في فئة NSSTring العديد من الطرق مثل:

- (id)initWithFormat:(NSString *)format arguments:(va_list)argList
- (id)initWithFormat:(NSString *)format locale:(id)locale arguments:(va_list)argList
+ (id)stringWithFormat:(NSString *)format, ...

لكن لم يأخذ أي منهم NSArray كوسيطة، ولا يمكنني العثور على طريقة لإنشاء قائمة va_list من NSArray...

هل كانت مفيدة؟

المحلول

وهو في الواقع ليس من الصعب إنشاء va_list من NSArray. انظر مات غالاغر ممتاز المقالة حول هذا الموضوع.

وهنا فئة NSString أن تفعل ما تريد:

@interface NSString (NSArrayFormatExtension)

+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments;

@end

@implementation NSString (NSArrayFormatExtension)

+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments
{
    char *argList = (char *)malloc(sizeof(NSString *) * arguments.count);
    [arguments getObjects:(id *)argList];
    NSString* result = [[[NSString alloc] initWithFormat:format arguments:argList] autorelease];
    free(argList);
    return result;
}

@end

وبعد ذلك:

NSString* s = [NSString stringWithFormat:@"xxx=%@, yyy=%@" array:@[@"XXX", @"YYY"]];
NSLog( @"%@", s );

ولسوء الحظ، 64 بت، تغير شكل va_list، وبالتالي فإن رمز أعلاه لم يعد يعمل. وربما لا ينبغي أن تستخدم معين على أي حال فإنه يعتمد على الشكل الذي هو عرضة للتغيير بشكل واضح. ونظرا لا توجد وسيلة قوية حقا لخلق va_list، أفضل حل هو ببساطة تحديد عدد من الحجج لمدة أقصاها معقول (مثلا 10) ومن ثم استدعاء stringWithFormat مع الحجج ال 10 الاولى، شيء من هذا القبيل:

+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments
{
    if ( arguments.count > 10 ) {
        @throw [NSException exceptionWithName:NSRangeException reason:@"Maximum of 10 arguments allowed" userInfo:@{@"collection": arguments}];
    }
    NSArray* a = [arguments arrayByAddingObjectsFromArray:@[@"X",@"X",@"X",@"X",@"X",@"X",@"X",@"X",@"X",@"X"]];
    return [NSString stringWithFormat:format, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9] ];
}

نصائح أخرى

بناءً على هذه الإجابة باستخدام العد المرجعي التلقائي (ARC): https://stackoverflow.com/a/8217755/881197

أضف فئة إلى NSString مع الطريقة التالية:

+ (id)stringWithFormat:(NSString *)format array:(NSArray *)arguments
{
    NSRange range = NSMakeRange(0, [arguments count]);
    NSMutableData *data = [NSMutableData dataWithLength:sizeof(id) * [arguments count]];
    [arguments getObjects:(__unsafe_unretained id *)data.mutableBytes range:range];
    NSString *result = [[NSString alloc] initWithFormat:format arguments:data.mutableBytes];
    return result;
}

وأحد الحلول التي جاءت إلى ذهني هو أن أتمكن من إنشاء الأسلوب الذي يعمل مع عدد كبير ثابت من الحجج مثل:

+ (NSString *) stringWithFormat: (NSString *) format arguments: (NSArray *) arguments {
    return [NSString stringWithFormat: format ,
          (arguments.count>0) ? [arguments objectAtIndex: 0]: nil,
          (arguments.count>1) ? [arguments objectAtIndex: 1]: nil,
          (arguments.count>2) ? [arguments objectAtIndex: 2]: nil,
          ...
          (arguments.count>20) ? [arguments objectAtIndex: 20]: nil];
}

ويمكنني أن أضيف أيضا فحص لمعرفة ما اذا سلسلة التنسيق لديها أكثر من 21 '٪' حرفا ورمي استثناء في هذه الحالة.

Chuck هو الصحيح حول حقيقة أنك لا يمكن تحويل NSArray إلى varargs . ومع ذلك، وأنا لا أنصح بالبحث عن %@ نمط في السلسلة واستبدالها في كل مرة. (استبدال الحروف في وسط سلسلة عموما فعالة جدا، وليس فكرة جيدة اذا كنت يمكن أن تنجز الشيء نفسه بطريقة مختلفة.) هنا هو وسيلة أكثر كفاءة لإنشاء سلسلة مع شكل كنت واصفا:

NSArray *array = ...
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:[array count]];
for (id object in array) {
    [newArray addObject:[NSString stringWithFormat:@"x=%@", [object description]]];
}
NSString *composedString = [[newArray componentsJoinedByString:@", "] retain];
[pool drain];

وأنا تضمنت تجمع autorelease للتدبير المنزلي الجيد، حيث سيتم إنشاء سلسلة autoreleased لكل إدخال مجموعة، وautoreleased مجموعة قابلة للتغيير أيضا. هل يمكن بسهولة جعل هذا إلى أسلوب / وظيفة والعودة composedString دون الاحتفاظ بها، والتعامل مع autorelease في أي مكان آخر في رمز إذا رغبت في ذلك.

وهذا الجواب هو عربات التي تجرها الدواب. كما لوحظ، لا يوجد حل لهذه المشكلة هو أن يضمن أن تعمل عندما يتم إدخال مناهج جديدة بخلاف باستخدام "10 عنصرا مجموعة" الأسلوب.


والجواب من قبل solidsun كان يعمل بشكل جيد، حتى ذهبت إلى ترجمة مع الهندسة المعمارية 64 بت. يحدث هذا خطأ:

وEXC_BAD_ADDRESS نوع EXC_I386_GPFLT

وكان الحل لاستخدام نهج مختلف قليلا لاجتياز قائمة الوسيطة إلى الأسلوب:

+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments;
{
     __unsafe_unretained id  * argList = (__unsafe_unretained id  *) calloc(1UL, sizeof(id) * arguments.count);
    for (NSInteger i = 0; i < arguments.count; i++) {
        argList[i] = arguments[i];
    }

    NSString* result = [[NSString alloc] initWithFormat:format, *argList] ;//  arguments:(void *) argList];
    free (argList);
    return result;
}

هذا يعمل فقط للصفائف مع عنصر واحد

ليس الطريق العام لتمرير صفيف إلى وظيفة أو الأسلوب الذي يستخدمه varargs. في هذه الحالة بالذات، ومع ذلك، يمكن أنها وهمية باستخدام شيء من هذا القبيل:

for (NSString *currentReplacement in array)
    [string stringByReplacingCharactersInRange:[string rangeOfString:@"%@"] 
            withString:currentReplacement];

وتحرير: الجواب المقبول تدعي هناك طريقة للقيام بذلك، ولكن بغض النظر عن مدى هشاشة قد يبدو هذا الجواب، وهذا النهج هو أكثر هشاشة بكثير. لأنه يعتمد على سلوك المعرفة من قبل التنفيذ (على وجه التحديد، بنية va_list) التي لم يتم ضمان أن تبقى نفسها. أرى أن جوابي هو الصحيح والحل المقترح بلدي هو أقل هشاشة نظرا لأنه يعتمد فقط على ملامح محددة من اللغة والأطر.

وبالنسبة لأولئك الذين في حاجة الى حل سريع، وهنا هو امتداد للقيام بذلك في سويفت

extension String {

    static func stringWithFormat(format: String, argumentsArray: Array<AnyObject>) -> String {
        let arguments = argumentsArray.map { $0 as! CVarArgType }
        let result = String(format:format, arguments:arguments)
        return result
    }

}

نعم، فمن الممكن. في دول مجلس التعاون الخليجي التي تستهدف نظام التشغيل Mac OS X، على الأقل، va_list هو مجرد مجموعة C، لذلك عليك أن تجعل واحدة من ids، ثم نقول للNSArray لملئه:

NSArray *argsArray = [[NSProcessInfo processInfo] arguments];
va_list args = malloc(sizeof(id) * [argsArray count]);
NSAssert1(args != nil, @"Couldn't allocate array for %u arguments", [argsArray count]);

[argsArray getObjects:(id *)args];

//Example: NSLogv is the version of NSLog that takes a va_list instead of separate arguments.
NSString *formatSpecifier = @"\n%@";
NSString *format = [@"Arguments:" stringByAppendingString:[formatSpecifier stringByPaddingToLength:[argsArray count] * 3U withString:formatSpecifier startingAtIndex:0U]];
NSLogv(format, args);

free(args);

ويجب أن لا تعتمد على هذا النوع في التعليمات البرمجية التي يجب أن تكون محمولة. مطوري فون، وهذا هو الشيء الوحيد الذي يجب اختبار بالتأكيد على الجهاز.

- (NSString *)stringWithFormat:(NSString *)format andArguments:(NSArray *)arguments {
    NSMutableString *result = [NSMutableString new];
    NSArray *components = format ? [format componentsSeparatedByString:@"%@"] : @[@""];
    NSUInteger argumentsCount = [arguments count];
    NSUInteger componentsCount = [components count] - 1;
    NSUInteger iterationCount = argumentsCount < componentsCount ? argumentsCount : componentsCount;
    for (NSUInteger i = 0; i < iterationCount; i++) {
        [result appendFormat:@"%@%@", components[i], arguments[i]];
    }
    [result appendString:[components lastObject]];
    return iterationCount == 0 ? [result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] : result;
}

واختبارها بشكل والحجج:

NSString *format = @"xxx=%@, yyy=%@ last component";
NSArray *arguments = @[@"XXX", @"YYY", @"ZZZ"];

والنتيجة: XXX = XXX، ص ص ص = YYY المكون الأخير

NSString *format = @"xxx=%@, yyy=%@ last component";
NSArray *arguments = @[@"XXX", @"YYY"];

والنتيجة: XXX = XXX، ص ص ص = YYY المكون الأخير

NSString *format = @"xxx=%@, yyy=%@ last component";
NSArray *arguments = @[@"XXX"];

والنتيجة: XXX = XXX المكون الأخير

NSString *format = @"xxx=%@, yyy=%@ last component";
NSArray *arguments = @[];

والنتيجة: العنصر الأخير

NSString *format = @"some text";
NSArray *arguments = @[@"XXX", @"YYY", @"ZZZ"];

والنتيجة: بعض النصوص

ولقد وجدت بعض الرموز على شبكة الإنترنت أن يدعي أن هذا ممكن ولكن أنا لم تمكن من القيام بذلك نفسي، ولكن إذا كنت لا تعرف عدد من الحجج مقدما تحتاج أيضا إلى بناء سلسلة التنسيق حيوي جدا أنا لا أرى هذه النقطة.

وأنت أفضل حالا مجرد بناء سلسلة من بالتكرار مجموعة.

وقد تجد stringByAppendingString: أو stringByAppendingFormat: أسلوب مثيل مفيد

يمكن للمرء إنشاء فئة لـ NSString وإنشاء دالة تتلقى تنسيقًا ومصفوفة وترجع السلسلة بالكائنات المستبدلة.

@interface NSString (NSArrayFormat)

+ (NSString *)stringWithFormat:(NSString *)format arrayArguments:(NSArray *)arrayArguments;

@end

@implementation NSString (NSArrayFormat)

+ (NSString *)stringWithFormat:(NSString *)format arrayArguments:(NSArray *)arrayArguments {
    static NSString *objectSpecifier = @"%@"; // static is redundant because compiler will optimize this string to have same address
    NSMutableString *string = [[NSMutableString alloc] init]; // here we'll create the string
    NSRange searchRange = NSMakeRange(0, [format length]);
    NSRange rangeOfPlaceholder = NSMakeRange(NSNotFound, 0); // variables are declared here because they're needed for NSAsserts
    NSUInteger index;
    for (index = 0; index < [arrayArguments count]; ++index) {
        rangeOfPlaceholder = [format rangeOfString:objectSpecifier options:0 range:searchRange]; // find next object specifier
        if (rangeOfPlaceholder.location != NSNotFound) { // if we found one
            NSRange substringRange = NSMakeRange(searchRange.location, rangeOfPlaceholder.location - searchRange.location);
            NSString *formatSubstring = [format substringWithRange:substringRange];
            [string appendString:formatSubstring]; // copy the format from previous specifier up to this one
            NSObject *object = [arrayArguments objectAtIndex:index];
            NSString *objectDescription = [object description]; // convert object into string
            [string appendString:objectDescription];
            searchRange.location = rangeOfPlaceholder.location + [objectSpecifier length]; // update the search range in order to minimize search
            searchRange.length = [format length] - searchRange.location;
        } else {
            break;
        }
    }
    if (rangeOfPlaceholder.location != NSNotFound) { // we need to check if format still specifiers
        rangeOfPlaceholder = [format rangeOfString:@"%@" options:0 range:searchRange];
    }
    NSAssert(rangeOfPlaceholder.location == NSNotFound, @"arrayArguments doesn't have enough objects to fill specified format");
    NSAssert(index == [arrayArguments count], @"Objects starting with index %lu from arrayArguments have been ignored because there aren't enough object specifiers!", index);
    return string;
}

@end

نظرًا لأن NSArray يتم إنشاؤه في وقت التشغيل، لا يمكننا تقديم تحذيرات وقت الترجمة، ولكن يمكننا استخدام NSAssert لإخبارنا إذا كان عدد المحددات يساوي عدد الكائنات داخل المصفوفة.

إبتكرت مشروع على جيثب حيث يمكن العثور على هذه الفئة.تمت إضافة إصدار Chuck أيضًا باستخدام "stringByReplacingCharactersInRange:" بالإضافة إلى بعض الاختبارات.

باستخدام مليون كائن في المصفوفة، لا يتم قياس الإصدار الذي يحتوي على "stringByReplacingCharactersInRange:" بشكل جيد (انتظر حوالي دقيقتين ثم أغلق التطبيق).باستخدام الإصدار مع NSMutableString، قامت الوظيفة بإنشاء السلسلة في حوالي 4 ثوانٍ.تم إجراء الاختبارات باستخدام جهاز المحاكاة. قبل الاستخدام، يجب إجراء الاختبارات على جهاز حقيقي (استخدم جهازًا بأقل المواصفات).

يحرر:على iPhone 5s، يستغرق الإصدار المزود بـ NSMutableString 10.471655 ثانية (مليون كائن)؛على iPhone 5 يستغرق 21.304876 ثانية.

إليك الجواب دون خلق صراحة صفيف:

   NSString *formattedString = [NSString stringWithFormat:@"%@ World, Nice %@", @"Hello", @"Day"];

والسلسلة الأولى هي السلسلة الهدف المراد تنسيقها، السلسلة التالية هي سلسلة لإدراجها في الهدف.

لا، فلن تكون قادرة على. يتم حل المكالمات حجة متغير في وقت الترجمة، وNSArray بك ديه محتويات فقط في وقت التشغيل.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top