Ошибка выполнения при использовании объектов CoreFoundation в быстром подклассе NSObject

Вот очень простой класс (подкласс NSObject), который хранит список объектов CGPath и добавляет один CGPath к массиву init:

import Foundation
class MyClass: NSObject {

    var list = [CGPath]();

    init() {
        list.append(CGPathCreateMutable());
    }
}

При попытке использовать этот класс:

var instance = MyClass();
println(instance.list.count); // runtime error after adding this line

Выдает уродливую аварию:

Playground execution failed: error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.
* thread #1: tid = 0x1251d1, 0x00000001064ce069 libswiftFoundation.dylib`partial apply forwarder for Swift._ContiguousArrayBuffer.(_asCocoaArray <A>(Swift._ContiguousArrayBuffer<A>) -> () -> Swift._CocoaArray).(closure #1) with unmangled suffix "392" + 121, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
  * frame #0: 0x00000001064ce069 libswiftFoundation.dylib`partial apply forwarder for Swift._ContiguousArrayBuffer.(_asCocoaArray <A>(Swift._ContiguousArrayBuffer<A>) -> () -> Swift._CocoaArray).(closure #1) with unmangled suffix "392" + 121
    frame #1: 0x00000001064ce0d8 libswiftFoundation.dylib`partial apply forwarder for reabstraction thunk helper <T_0_0> from @callee_owned (@in T_0_0) -> (@owned Swift.AnyObject) to @callee_owned (@in T_0_0) -> (@out Swift.AnyObject) with unmangled suffix "395" + 56
    frame #2: 0x00000001057bf29a libswift_stdlib_core.dylib`Swift.MapSequenceGenerator.next <A : Swift.Generator, B>(@inout Swift.MapSequenceGenerator<A, B>)() -> Swift.Optional<B> + 554
    frame #3: 0x00000001057bf49a libswift_stdlib_core.dylib`protocol witness for Swift.Generator.next <A : Swift.Generator>(@inout Swift.Generator.Self)() -> Swift.Optional<Swift.Generator.Self.Element> in conformance Swift.MapSequenceGenerator : Swift.Generator + 58
    frame #4: 0x00000001064d8e97 libswiftFoundation.dylib`Swift._copyCollectionToNativeArrayBuffer <A : protocol<Swift._Collection, Swift._Sequence_>>(A) -> Swift._ContiguousArrayBuffer<A.GeneratorType.Element> + 1511
    frame #5: 0x00000001064f1951 libswiftFoundation.dylib`protocol witness for Swift.Sequence.~> @infix <A : Swift.Sequence>(Swift.Sequence.Self.Type)(Swift.Sequence.Self, (Swift._CopyToNativeArrayBuffer, ())) -> Swift._ContiguousArrayBuffer<Swift.Sequence.Self.GeneratorType.Element> in conformance Swift.LazyRandomAccessCollection : Swift.Sequence + 449
    frame #6: 0x00000001064daf7b libswiftFoundation.dylib`Swift.ContiguousArray.map <A>(Swift.ContiguousArray<A>)<B>((A) -> B) -> Swift.ContiguousArray<B> + 1339
    frame #7: 0x00000001064da9cb libswiftFoundation.dylib`Swift._ContiguousArrayBuffer._asCocoaArray <A>(Swift._ContiguousArrayBuffer<A>)() -> Swift._CocoaArray + 475
    frame #8: 0x00000001064ced3e libswiftFoundation.dylib`Swift._ArrayBuffer._asCocoaArray <A>(Swift._ArrayBuffer<A>)() -> Swift._CocoaArray + 78
    frame #9: 0x000000010649f583 libswiftFoundation.dylib`Foundation._convertArrayToNSArray <A>(Swift.Array<A>) -> ObjectiveC.NSArray + 35
    frame #10: 0x000000011163b40e

Мое внимание привлек кадр №9: libswiftFoundation.dylib\`Foundation._convertArrayToNSArray. Зачем Swift пытаться преобразовать мой красивый, счастливый массив Swift в NSArray?

Эта проблема возникает только при использовании CFType объектов в массиве. Я могу прекрасно использовать подклассы NSObject в массиве (пример [UIBezierPath])

Проблему можно легко решить, не создавая подклассы NSObject, однако я хочу знать, что именно Swift делает с моим невинным массивом. Кроме того, как я могу по-прежнему использовать NSObject в качестве базового класса и иметь массивы CFType объектов, таких как CGPath.

Также было указано, (Спасибо, @user102008!), что это не обязательно должен быть подкласс NSObject, но свойство просто должно быть объявлено как @objc.

Существует некоторая документация с целью использования @objc и создания подклассов класса Objective-C в Swift:

Когда вы определяете класс Swift, который наследуется от NSObject или любого другого класса Objective-C, этот класс автоматически совместим с Objective-C.

Тем не менее, я пытаюсь использовать свой класс Swift из Swift. В документации по различному поведению при создании подкласса класса Objective-C и использовании его в Swift нет упоминания о побочных эффектах. Но в документации также упоминается соединение массивов Swift с NSArray:

Когда вы соединяете массив Swift с объектом NSArray, элементы в массиве Swift должны быть совместимы с AnyObject.

И продолжает:

Если элемент массива Swift несовместим с AnyObject, при соединении с объектом NSArray возникает ошибка времени выполнения.

Хмммм, CGPath не совместим с AnyObject, но Swift не должен пытаться преобразовать мой массив Swift в NSArray.


person Andrew    schedule 28.07.2014    source источник
comment
кстати: класс даже не должен быть подклассом NSObject; свойство просто нужно объявить @objc для возникновения этой проблемы   -  person user102008    schedule 30.07.2014
comment
@ user102008 Хороший улов!   -  person Andrew    schedule 30.07.2014
comment
CGPath не совместим с AnyObject Почему вы так говорите? Мне он кажется совместимым.   -  person newacct    schedule 04.08.2014
comment
@newacct Думаю, дело в том, что CGPath не совместим с NSObject, а это означает, что он не может быть в NSArray.   -  person Andrew    schedule 04.08.2014
comment
@SantaClaus: На самом деле, CGPath NSObject совместим во время выполнения. (CGPathCreateMutable() as AnyObject) is NSObject оценивается как истина.   -  person newacct    schedule 05.08.2014
comment
Обновление: это было исправлено в Xcode 6 beta 5.   -  person newacct    schedule 05.08.2014
comment
@newacct Я проверю это снова, как только получу последнюю бета-версию.   -  person Andrew    schedule 05.08.2014
comment
Похоже, они что-то изменили: If you never import a Swift class in Objective-C code, you don’t need to worry about type compatibility in this case as well.   -  person Andrew    schedule 05.08.2014


Ответы (5)


Хмммм, CGPath не совместим с AnyObject, но Swift не должен пытаться преобразовать мой массив Swift в NSArray.

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

Короткий ответ заключается в том, что это работает точно так, как задокументировано. Однако, поскольку это iOS, решение тривиально. Просто переключитесь на UIBezierPath (который совместим с AnyObject), а не на CGPath.

person Rob Napier    schedule 30.07.2014
comment
Таким образом, он (попытается) преобразовать его в NSArray, даже если я не использую его в Objective-C? Я просто звоню println(MyClass().list.count); в Swift, что не похоже на необходимость конвертировать... - person Andrew; 30.07.2014
comment
Вы сказали, что хотите, чтобы он был совместим с ObjC. Он принимает это решение во время компиляции. В противном случае ему пришлось бы выполнять, возможно, очень дорогое преобразование (которое может привести к сбою) во время выполнения каждый раз, когда вы его запрашивали. Если вы действительно хотите что-то подобное, создайте вычисляемое свойство objc, которое конвертирует для вас. - person Rob Napier; 30.07.2014
comment
Ключевым моментом здесь является понимание того, что размещение @ objc впереди (или создание подкласса от NSObject) говорит о том, что я хочу, чтобы вы сохранили это таким образом, который совместим с ObjC, и я обещаю, что не буду использовать какие-либо функции, которые нарушают это. - person Rob Napier; 30.07.2014
comment
Однако я мог бы порекомендовать открыть радар. В идеале вы должны были получить ошибку компиляции в строке append(). - person Rob Napier; 30.07.2014
comment
Я не уверен, что он сохраняет его как NSArray, поскольку кажется, что он преобразует его в NSArray во время выполнения, как это предлагается Frame #9 трассировки стека. - person Andrew; 30.07.2014
comment
Я не говорил, что он хранит его как NSArray. Я сказал, что он хранит его способом, совместимым с ObjC, и это накладывает определенные ограничения. - person Rob Napier; 30.07.2014
comment
Он все еще использует ArrayBuffer под одеялом. Но во фрейме 7 вы видите, где требуется, чтобы он был совместим с массивом какао, поскольку вы просили об этом в определении типа. - person Rob Napier; 30.07.2014
comment
Что более важно, так это то, что сбой в Swift — это благословение. Худшим ответом было бы, чтобы он работал в Swift, а затем ломался в ObjC (поскольку вы, очевидно, хотели, чтобы он работал в ObjC, иначе вы бы не объявили это таким образом). Вы хотите, чтобы вещи, которые потерпят неудачу, надежно потерпели неудачу. Вы бы не хотели, чтобы Swift старался изо всех сил и иногда преуспевал, а иногда терпел неудачу. - person Rob Napier; 30.07.2014
comment
Я провел еще немного тестов, и ошибка возникает не при инициализации экземпляра MyClass, а только при попытке доступа к instance.list. Значит ли это, что массив можно хранить, но как только я пытаюсь получить к нему доступ, он преобразует его в NSArray? - person Andrew; 30.07.2014
comment
Это соответствует тому, что вы видите в журнале сбоев. Было бы лучше, если бы он потерпел неудачу раньше (в идеале во время компиляции), и это то, о чем вы могли бы открыть радар. Но все это соответствует ожидаемому поведению из документов IMO. Не помещайте небезопасные для ObjC вещи в доступную для ObjC переменную. (Другой радар заключается в том, что CGPathRef должен быть конвертируемым теперь, когда он помечен CF_IMPLICIT_BRIDGING_ENABLED, но это другой вопрос.) - person Rob Napier; 30.07.2014
comment
Почему CGPath не совместим с AnyObject? - person newacct; 04.08.2014
comment
Почти наверняка, потому что это не NSObject. Это CFType. Поскольку он был проверен на соответствие управлению памятью, я надеюсь, что они смогут чисто подключить его к Swift. - person Rob Napier; 04.08.2014
comment
@RobNapier: CFTypes - это NSObjects во время выполнения. - person newacct; 05.08.2014
comment
Объекты @RobNapier Core Foundation AnyObject совместимы. Например. вы всегда могли помещать типы CF в NSArray в Objective-C, приводя их к id. [myArray addObject:(id)myCGColor] - person Jack Lawrence; 05.08.2014
comment
Это задокументированное определение AnyObject? Я согласен с тем, что это должно быть определением AnyObject, но я не знаю, есть ли оно в настоящее время есть в Swift. - person Rob Napier; 05.08.2014
comment
Я бы хотел, чтобы кто-то обновил свой ответ или опубликовал новый ответ о том, что происходило в бета-версии 4 и что изменилось в бета-версии 5, чтобы исправить это. - person Andrew; 05.08.2014

Все объекты в Swift, совместимые с Objective-C, используют полную среду выполнения Objective-C. Это означает, что когда вы создаете тип, который наследуется от NSObject (или определяется как совместимый с Objective-C), его методы и свойства используют Обмен сообщениями вместо ссылки во время компиляции на ваш метод.

Чтобы получить сообщение, все чисто Swift-объекты, такие как ваш массив, должны быть преобразованы в их аналог Objective-C. Это происходит независимо от того, используете ли вы в настоящее время объект в Objective-C, потому что, несмотря ни на что, он использует обмен сообщениями.

Поэтому, если вы создаете класс, который наследуется от NSObject, вы должны исходить из того, что все свойства могут быть преобразованы в аналоги Objective-C. Как вы сказали в своем вопросе, вы можете добиться этого, используя UIBezierPath вместо CGPath.

person drewag    schedule 03.08.2014
comment
В компиляторе Beta 4 вы могли устранить сбой, объявив свойство как final, что говорит Swift не использовать обмен сообщениями для доступа к свойству. И наоборот, компилятор Beta 5 больше не использует обмен сообщениями для доступа к свойствам, поэтому он успешно работает, если только вы не объявите свойство как dynamic, что снова вызовет сбой. - person Jay Lieske; 05.08.2014

Из "Использование Swift с Cocoa и Objective-C"

Когда вы используете класс или протокол Swift в коде Objective-C, средство импорта заменяет все массивы Swift любого типа в импортированном API на NSArray.

Таким образом, в соответствии с этим массив не должен преобразовываться в NSArray. Поскольку это так, я также думаю, что вы должны подать отчет об ошибке.

Тем временем вы можете использовать соответствующий класс Cocoa или обернуть свой CGPath дополнительным объектом.

person ShadowLightz    schedule 02.08.2014
comment
Я не понимаю, как вы можете рассуждать о том, что массив Swift не следует преобразовывать на основе этой части документации. Я использую свой класс Swift в другом коде Swift. - person Andrew; 02.08.2014
comment
Вот что я хочу сказать. Это определенно не должно быть преобразовано. В компиляторе Swift осталось несколько ошибок. Думаю, это один из них. - person ShadowLightz; 03.08.2014
comment
Этот текст не ограничивает то, что произойдет, когда код Swift вызывает другой код Swift, только когда код Objective-C вызывает код Swift. - person Tommy; 02.11.2015

Вероятно, это тот факт, что вы не импортировали Foundaion, где находится NSObject:

import Foundation

Здесь содержится NSObject (насколько мне известно). Надеюсь, это помогло. :)

person zaak71    schedule 29.07.2014
comment
Неа. Это не помогло. Я добавил его в свой пример вопроса. В любом случае, я не знаю, где ошибка класса x, скорее всего, будет ошибкой компилятора, а не ошибкой времени выполнения, с которой я сталкиваюсь. - person Andrew; 29.07.2014
comment
Извините за это тогда. Я не такой опытный программист, поэтому я, вероятно, могу ошибаться. :/ - person zaak71; 30.07.2014
comment
Вы ошибаетесь:разработчик .apple.com/library/mac/documentation/Cocoa/reference/ - person Sviatoslav Yakymiv; 30.07.2014
comment
Если доказано, что ответ не решает проблему, его следует удалить. - person Mizipzor; 30.07.2014

person    schedule
comment
Ой, исправили?? - person Andrew; 02.11.2015