Грешка по време на изпълнение при използване на обекти на CoreFoundation в подклас на swift 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 се оценява като true.   -  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 под завивките. Но във Frame 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-Class или да обвиете своя 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