Converting NSArray Contents to a varargs (With ARC) For Use With NSString initWithFormat

前端 未结 6 919
广开言路
广开言路 2021-02-19 13:43

We have some code today that takes an NSArray and passes it as a argument list to -[NSString initWithFormat:arguments] and we\'re trying to get this to work with ARC. Here\'s th

相关标签:
6条回答
  • 2021-02-19 14:07

    I tried mcfedr's code. Somehow, my Xcode 11 treated CVarArgType as undeclared type, so I investigated into this for a while.

    I didn't not understand the closure part of his/her code. And, I just simplified to hard casted each element to CVarArg using as! operator.

    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
    

    It seems it's working fine under swift 5.1, but there may be some pitfalls.

    0 讨论(0)
  • 2021-02-19 14:18

    I write solution use NSInvocation and signatures.

    Answer create in this. Also I write detailed description how it work but only on Russian ((

    Maybe it help for someone.

    0 讨论(0)
  • 2021-02-19 14:23

    This only works for arrays with a single element

    The answer by chrisco was working well, until I went to compile with 64-bit architecture. This caused an error:

    EXC_BAD_ADDRESS type EXC_I386_GPFLT

    The solution was to use a slightly different approach for passing the argument list to the method:

    + (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;
    }
    
    0 讨论(0)
  • 2021-02-19 14:23

    Expanding on @mcfedr's answer, this Swift 3 helper does the job:

    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)
        }
    }
    

    Calling from Objective-C:

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

    For the %@ format specifier we're using Swift's CustomStringConvertible protocol in order to call description on all of the array members.

    Supporting all number format specifiers like %d and %f is not really possible because the NSNumber object doesn't reveal if it's an integer or float. So we could only support one or the other. Here we use intValue, so %d is supported but %f and %g are not.

    0 讨论(0)
  • 2021-02-19 14:27

    The only thing you need to do is remove the autorelease.

    You're malloc'ing and free'ing yourself - ARC doesn't care about that.

    0 讨论(0)
  • 2021-02-19 14:32

    Cannot find a way to do this obj-c but a swift helper class finally got this working (my whole project is obj-c except this class)

    @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)
        }
    }
    

    There is some magic going on, to do with how [CVarArgType] doesn't behave like a normal array - but this works in the flexible cross architecture way you expect it to.

    0 讨论(0)
提交回复
热议问题