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

Есть ли способ работать с объектами Foundation (NSString, NSArray, NSDictionary) в Swift без моста?

При использовании Swift предполагается, что фреймворки Cocoa возвращают родные типы Swift, даже несмотря на то, что фреймворки фактически возвращают объекты Objective-C. Аналогично, методы принимают типы Swift как параметры, где это имеет смысл.

Предположим, что я хочу вызвать метод Cocoa, который (в Objective-C) предоставит мне NSArray, а затем передаст это методу Cocoa, который примет значение NSArray. С кодом, подобным этому:

let a: [AnyObject] = [] // Imagine calling a method that returns a huge NSArray.
let mutable = NSMutableArray()
mutable.addObjectsFromArray(a)

Похоже, что огромный NSArray собирается подключиться к массиву Swift при назначении a, а затем передается обратно в NSArray при передаче в качестве параметра. По крайней мере, как это выглядит при профилировании и рассмотрении разборки.

Есть ли способ избежать этих потенциально медленных преобразований, когда мне не нужно действительно работать с массивом в Swift? Когда я просто получаю его от Cocoa, а затем передаю его обратно в Cocoa?

Сначала я подумал, что это поможет добавить информацию типа для a:

let a: NSArray = [] // Imagine calling a method that returns a huge NSArray.
let mutable = NSMutableArray()
mutable.addObjectsFromArray(a as [AnyObject])

Но потом мне нужно преобразовать параметр в массив Swift позже или компилятор будет жаловаться.

Кроме того, разборка для кода типа:

let c: NSArray = mutable.subarrayWithRange(NSMakeRange(0, 50))

показывает вызовы __TF10Foundation22_convertNSArrayToArrayurFGSqCSo7NSArray_GSaq__ и __TFer10FoundationSa19_bridgeToObjectiveCurfGSaq__FT_CSo7NSArray, по-видимому, преобразуя возвращаемое значение в Swift, а затем обратно в Objective-C. (Это происходит даже с версиями Release.) Я надеялся, что, набрав c как NSArray, не потребуется никакого моста.

Я обеспокоен тем, что это может привести к неэффективности с очень большими структурами данных со многими разрозненными преобразованиями обычных и с коллекциями, которые ленивы/прокси, потому что они не обязательно большие, но могут быть дорогими для вычисления. Было бы неплохо иметь возможность получать такой массив из кода Objective-C и передавать его обратно, не выполняя все элементы массив, если они никогда не доступны из Swift.

Это очень другое производительность чем с Core Foundation/Foundation, где мосты были бесплатными. Существует так много случаев, когда код передает объекты назад и вперед, предполагая, что это будет O (1), и если они невидимо изменены на O (n), внешние алгоритмы могут стать квадратичными или худшими. Мне непонятно, что делать в этом случае. Если нет способа отключить мост, кажется, что все, что касается этих объектов, должно быть переписано в Objective-C.

Вот пример временного кода на основе приведенного выше примера:

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

В этом примере testSwift() примерно на 22% медленнее, чем testObjC(). Просто для удовольствия я попытался добавить массив с помощью собственного массива Swift, и это было намного медленнее.

Связанная проблема заключается в том, что когда код Objective-C передает код Swift a NSMutableString, Swift String заканчивается копией изменяемой строки. Это хорошо в том смысле, что он не будет неожиданно изменен позади Свифт. Но если все, что вам нужно сделать, это передать строку Swift и взглянуть на нее кратко, эта копия может добавить неожиданные накладные расходы.

4b9b3361

Ответ 1

Вы пытались сделать расширение?

extension NSMutableArray
{
    func addObjectsFromNSArray(array:NSArray)
    {
         for item in array
         {
              self.addObject(item);
         }
    }
}

Теперь, когда я успел поиграть с этим, вместо того чтобы говорить в теории, я собираюсь пересмотреть свой ответ

Создайте расширение, но вместо этого сделайте это в объектном файле c

@interface NSMutableArray(Extension)
    - (void)addObjectsFromNSArray:(NSObject*) array;
@end

@implementation NSMutableArray(Extension)
    - (void)addObjectsFromNSArray:(NSObject*) array
{
   [self addObjectsFromArray:(NSArray*)array];
}
@end

Я нашел код работать намного быстрее, делая это таким образом. (Почти 2x из моих тестов)

testSwift 4.06 секунды

testSwiftPure 7.97 секунд

testSwiftExtension 2.30 секунд