Подтвердить что ты не робот

Преобразование содержимого NSArray в varargs (с ARC) для использования с NSString initWithFormat

У нас есть некоторый код сегодня, который принимает NSArray и передает его как список аргументов в - [NSString initWithFormat: arguments], и мы пытаемся заставить это работать с ARC. Здесь код использовал

NSString* format = @"Item %s and Item %s"; // Retrieved elsewhere
NSArray* args = [NSArray arrayWithObjects:@"1", @"2", nil]; // Retrieved elsewhere

// http://cocoawithlove.com/2009/05/variable-argument-lists-in-cocoa.html
char* argsList = (char*) malloc(sizeof(NSString*) * args.count);
[args getObjects:(id*) argsList];
NSString* message = [[[NSString alloc] initWithFormat:format arguments:argsList] autorelease];
free(argsList);

Любые рекомендации о том, как сделать этот ARC-совместимым? Или мы даже открыты для лучшего способа сделать это.

4b9b3361

Ответ 1

Это работает только для массивов с одним элементом

Ответ от chrisco работал хорошо, пока я не пошел на компиляцию с 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;
}

Ответ 2

Невозможно найти способ сделать это obj-c, но быстрый класс-помощник, наконец, получил эту работу (весь мой проект является obj-c, за исключением этого класса)

@objc class StringFormat: NSObject {
    class func format(key: String, args: [AnyObject]) -> String {
        let locArgs: [CVarArgType] = args.map({ (arg: AnyObject) -> CVarArgType in
            if let iArg = (arg is NSNumber ? arg.intValue : nil) {
                return iArg
            }
            return arg as! CVarArgType
        });
        return String(format: key, arguments: locArgs)
    }
}

Происходит некоторая магия, связанная с тем, как [CVarArgType] не ведет себя как обычный массив, но это работает в гибкой кросс-архитектуре, как вы ожидаете.

Ответ 3

Расширяя ответ на @mcfedr, этот помощник Swift 3 выполняет следующее задание:

import Foundation

@objc (FTStringFormat) public class StringFormat: NSObject {
    @objc public class func format(key: String, args: [AnyObject]) -> String {
        let locArgs: [CVarArg] = args.flatMap({ (arg: AnyObject) -> CVarArg? in
            if let arg = arg as? NSNumber {
                return arg.intValue
            }
            if let arg = arg as? CustomStringConvertible {
                return arg.description
            }
            return nil
        });
        return String(format: key, arguments: locArgs)
    }
}

Вызов из Objective-C:

[FTStringFormat formatWithKey:@"name: %@ age: %d" args:@[@"John", @(42)]]

Для спецификатора формата %@ мы используем протокол Swift CustomStringConvertible, чтобы вызвать description для всех членов массива.

Поддержка всех спецификаторов формата чисел, таких как %d и %f, на самом деле не возможна, потому что объект NSNumber не показывает, является ли это целым или плавающим. Поэтому мы могли поддерживать только тот или иной. Здесь мы используем intValue, поэтому %d поддерживается, но %f и %g не являются.

Ответ 4

Единственное, что вам нужно сделать, это удалить авторекламу.

Вы malloc'ing и освобождаете себя - ARC не заботится об этом.

Ответ 5

Я пишу решение, использующее NSInvocation и подписи.

Ответить создайте в этом. Также я пишу подробное описание, как это работает, но только на русском ((

Может быть, это поможет кому-то.

Ответ 6

Я попробовал код mcfedr. Так или иначе, мой Xcode 11 рассматривал CVarArgType как необъявленный тип, поэтому я некоторое время занимался этим.

Я не понимал часть замыкания его/ее кода. И я просто упростил приведение каждого элемента к CVarArg с помощью оператора as!.

func format(key: String, args: [Any]) -> String {
    return String(format: key, arguments: args.map { ($0 as! CVarArg) })
}

let doubleValue: Double = 1.25
let floatValue: Float = 2.75
let intValue: Int = 3
let numberValue: NSNumber = 4.5 as NSNumber
let hello: String = "Hello"
let world: NSString = "World" as NSString
print(format(key: "double: %f, float: %f, int: %d, number: %@, %@, %@", args: [doubleValue, floatValue, intValue, numberValue, hello, world]))

// double: 1.250000, float: 2.750000, int: 3, number: 4.5, Hello, World

В Swift 5.1 все работает нормально, но могут быть некоторые подводные камни.