This question is a friend asked me how to write, at first I refused. I think this kind of thing can be found on the Internet. He said he did, but couldn’t make much sense of it. So I looked it up, but it was a little weird, and I didn’t get it at first. So write a random post, force through this week’s blog tasks, and give a friend an explanation.
This article is divided into two parts, the first part is how Swift calls objective-C’s variable parameter function, the second part is how Objective-C calls Swift’s variable parameter function.
Swift calls objective-C’s mutable argument function
Let me write an example
Write any objective-C variable argument function that takes n String arguments, prints them out one by one, and returns how many arguments there are. This method makes no sense, just to force a return value as an example…
- (NSInteger)foo:(NSString *)value,...
{
va_list list;
va_start(list, value);
NSInteger count = 0;
while (YES)
{
NSString *string = va_arg(list, NSString*);
if(! string) {break;
}
NSLog(@"% @",string);
count++;
}
va_end(list);
return count;
}
Copy the code
This method cannot be tuned directly in swift. In order to call it in Swift, you need to tweak it a little bit.
How do you do that?
- Put the method in the signature
.
Change it to a parameterargs:(va_list)list
va_list list;
和va_start(list, value);
Those two sentences need to be removed because ourva_list
It came in.va_end
It should also be removed. If it is not removed, no error will be reported. Maybe it can also be retained as a good practice.
Objective-c methods:
- (NSInteger)foo:(va_list)list
{
NSInteger count = 0;
while (YES)
{
NSString *string = va_arg(list, NSString*);
if(! string) {break;
}
NSLog(@"% @",string);
count++;
}
return count;
}
Copy the code
How do I call it in Swift
Since the va_list is passed in as an argument, the key is to construct a va_list in a special way. Just as you can use malloc to force a va_list in Objective-C, Swift has a function to do this:
public func withVaList<R>(_ args: [CVarArg], _ body: (CVaListPointer) -> R) -> R
Copy the code
It takes an array as the first argument, and the second argument is a closure, and the arguments to the closure are the generated va_list, and the return value is whatever you want to return, and the return value of the closure is the return value of the whole function.
In other words, you pass it an array and ask it to construct a va_list from that array; And then it gives you the constructed va_list as arguments to the closure, so you can use that va_list as you like in the closure; If you have something in the closure that you want to pass out, you can return it as the return value of the closure, and it will pass it out as the return value of this function, take that return value, and you can do whatever you want with it.
let testClass = TestClass()
let count = withVaList(["hello"."hamster"."good"."morning"]) { args -> Int in
return testClass.foo(args)
}
print(count)
Copy the code
Output:
hello
hamster
good
morning
4
Copy the code
It says in the documentation, this generated va_list is only allowed to be used inside a closure, you’re not allowed to pass it out and use it outside, otherwise it’s not valid. Let’s try it…
let testClass = TestClass()
let args = withVaList(["hello"."hamster"."good"."morning"]) { args -> CVaListPointer in
return args
}
print(testClass.foo(args))
Copy the code
The result is crash, EXC_BAD_ACCESS, presumably the space outside the closure has been freed. This is proof that we don’t need to write va_end anymore…
A similar function, getVaList, returns va_list as its return value. It is written more succintly.
let count = testClass.foo(getVaList(["hello"."hamster"."good"."morning"]))
print(count)
Copy the code
But the document makes two points clear:
- Can be used
withVaList
Don’t usegetVaList
. No specific reason was given. - So why give you this method? Because in some cases the language rules don’t allow it
withVaList
For example, in class Initializer. This is the time to use itgetVaList
.
A variable parameter method wrapped as Swift
The above syntax, if you have to use it a lot, will be very annoying to write it all the time. We can wrap it as a Swift variable parameter method…
extension TestClass {
func foo(_ strings: String...) -> Int {
return withVaList(strings) { args -> Int in
return foo(args)
}
}
}
Copy the code
Then call it once and for all:
let testClass = TestClass()
let count = testClass.foo("hello"."hamster"."good"."morning")
print(count)
Copy the code
Swift’s syntax is too simple, isn’t it?
Objective-c calls Swift’s mutable argument function
Since Swift’s syntax is so simple, why don’t we just implement all the mutable parameter methods in Swift and let Objective-C call them?
Swift, however, ruthlessly refused:
What if you really want to adjust? Either write another method that takes an array as an argument, call it in Objective-C, or write another objective-C variable argument method and wrap it around…