When using Swift, the Cocoa frameworks are declared to return native Swift types, even though the frameworks are actually returning Objective-C objects. Likewise, the methods takes Swift types as parameters, where that makes sense.
Suppose I want to call a Cocoa method that (in Objective-C) would give me an NSArray and then pass that to a Cocoa method that takes an NSArray
. With code like this:
let a: [AnyObject] = [] // Imagine calling a method that returns a huge NSArray.
let mutable = NSMutableArray()
mutable.addObjectsFromArray(a)
It looks like the huge NSArray
is going to get bridged to a Swift array when assigned to a
and then bridged back to an NSArray
when passed as a parameter. At least that's how it seems from profiling and looking at the disassembly.
Is there a way to avoid these potentially slow conversions when I don't need to actually work with the array in Swift? When I'm just receiving it from Cocoa and then passing it back to Cocoa?
At first, I thought that it would help to add type information for a
:
let a: NSArray = [] // Imagine calling a method that returns a huge NSArray.
let mutable = NSMutableArray()
mutable.addObjectsFromArray(a as [AnyObject])
But then I have to convert the parameter to a Swift array later or the compiler will complain.
Furthermore, the disassembly for code like:
let c: NSArray = mutable.subarrayWithRange(NSMakeRange(0, 50))
shows calls to __TF10Foundation22_convertNSArrayToArrayurFGSqCSo7NSArray_GSaq__
and __TFer10FoundationSa19_bridgeToObjectiveCurfGSaq__FT_CSo7NSArray
, seemingly converting the return value to Swift and then back to Objective-C. (This happens even with Release builds.) I had hoped that by typing c
as NSArray
there would be no bridging necessary.
I'm concerned that this could lead to inefficiencies with very large data structures, with many disparate conversions of regular ones, and with collections that are lazy/proxied because they are not necessarily large but may be expensive to compute. It would be nice to be able to receive such an array from Objective-C code and pass it back without having to realize all of the elements of the array if they are never accessed from Swift.
This is a very different performance model than with Core Foundation/Foundation where the bridging was toll-free. There are so many cases where code passes objects back and forth assuming that it will be O(1), and if these are invisibly changed to O(n) the outer algorithms could become quadratic or worse. It's not clear to me what one is supposed to do in this case. If there is no way to turn off the bridging, it seems like everything that touches those objects would need to be rewritten in Objective-C.
Here is some sample timing code based on the above example:
NSArray *getArray() {
static NSMutableArray *result;
if (!result) {
NSMutableArray *array = [NSMutableArray array];
for (NSUInteger i = 0; i < 1000000; i++) {
[array addObjectsFromArray:@[@1, @2, @3, @"foo", @"bar", @"baz"]];
}
result = array;
}
return result;
}
@interface ObjCTests : XCTestCase
@end
@implementation ObjCTests
- (void)testObjC { // 0.27 seconds
[self measureBlock:^{
NSArray *a = getArray();
NSMutableArray *m = [NSMutableArray array];
[m addObjectsFromArray:a];
}];
}
@end
class SwiftTests: XCTestCase {
func testSwift() { // 0.33 seconds
self.measureBlock() {
let a: NSArray = getArray() as NSArray
let m = NSMutableArray()
m.addObjectsFromArray(a as [AnyObject])
}
}
func testSwiftPure() { // 0.83 seconds
self.measureBlock() {
let a = getArray()
var m = [AnyObject]()
m.appendContentsOf(a)
}
}
}
In this example, testSwift()
is about 22% slower than testObjC()
. Just for fun, I tried doing the array append with the native Swift array, and this was much slower.
A related issue is that when Objective-C code passes Swift code an NSMutableString
, the Swift String
ends up with a copy of the mutable string. This is good in the sense that it won’t be unexpectedly mutated behind Swift’s back. But if all you need to do is pass a string to Swift and look at it briefly, this copy could add unexpected overhead.
have you tried making an extension?
extension NSMutableArray
{
func addObjectsFromNSArray(array:NSArray)
{
for item in array
{
self.addObject(item);
}
}
}
Now that I had time to actually play with this instead of talking in theory, I am going to revise my answer
Create an extension, but instead, do it in an objective c file
@interface NSMutableArray(Extension)
- (void)addObjectsFromNSArray:(NSObject*) array;
@end
@implementation NSMutableArray(Extension)
- (void)addObjectsFromNSArray:(NSObject*) array
{
[self addObjectsFromArray:(NSArray*)array];
}
@end
I found the code to work a lot faster doing it this way. (Almost 2x from my tests)
testSwift 4.06 seconds
testSwiftPure 7.97 seconds
testSwiftExtension 2.30 seconds
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With