Runtime error when using CoreFoundation objects in

2020-02-07 02:29发布

问题:

Here's a very simple class (subclass of NSObject) that keeps a list of CGPath objects and appends one CGPath to the array on init:

import Foundation
class MyClass: NSObject {

    var list = [CGPath]();

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

When trying to use this class:

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

Yields an ugly crash:

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

Frame #9 caught my eye: libswiftFoundation.dylib\`Foundation._convertArrayToNSArray. Why would swift be trying to convert my nice, happy, Swift array into an NSArray?

This issue only occurs when using CFType objects in an array. I can use NSObject subclasses in the array just fine (Ex. [UIBezierPath])

The issue can easily be fixed by not subclassing NSObject, however I want to know what exactly swift is doing to my innocent array. Also, how I can still use NSObject as the base class and have arrays of CFType objects such as CGPath.

It was also pointed out (Thanks, @user102008!) that it doesn't have to be a subclass of NSObject, but the property just has to be declared @objc.

There is some documentation on purpose of using @objc and subclassing an Objective-C class in Swift:

When you define a Swift class that inherits from NSObject or any other Objective-C class, the class is automatically compatible with Objective-C.

However, I am trying the use my Swift class from within Swift. There is no mention of side effects in the documentation of different behavior when subclassing an Objective-C class and using it within Swift. But the documentation also mentions bridging Swift arrays to NSArray:

When you bridge from a Swift array to an NSArray object, the elements in the Swift array must be AnyObject compatible.

And goes on to say:

If an element in a Swift array is not AnyObject compatible, a runtime error occurs when you bridge to an NSArray object.

Hmmmm, CGPath isn't AnyObject compatible, but Swift shouldn't be trying to convert my Swift array into an NSArray.

回答1:

Hmmmm, CGPath isn't AnyObject compatible, but Swift shouldn't be trying to convert my Swift array into an NSArray.

It has to. You said you want it to be ObjC compatible, and ObjC can't handle Swift arrays directly. So it has to convert it to an NSArray.

The short answer is that this is working exactly as documented. Since this is iOS, the solution, however, is trivial. Just switch to UIBezierPath (which is AnyObject compatible) rather than CGPath.



回答2:

All objects in Swift that are compatible with Objective-C, use the full Objective-C runtime. This means that when you make a type that inherits from NSObject (or is defined as Objective-C compatible), its methods and properties use Messaging instead of linking at compile time to your method.

To receive a message, all purely Swift objects, like your array, must be converted to their Objective-C counter-part. This happens regardless of if you are currently using the object in Objective-C because no matter what, it uses messaging.

Therefore, if you make a class that inherits from NSObject, you must assume that all properties can be converted to an Objective-C counterparts. As you said in your question, you can achieve this by using UIBezierPath instead of CGPath.



回答3:

From "Using Swift with Cocoa and Objective-C"

When you use a Swift class or protocol in Objective-C code, the importer replaces all Swift arrays of any type in imported API with NSArray.

So according to this, the array should not be converted to an NSArray. As it does, I also think you should file a bug report.

In the meantime, you could use the corresponding Cocoa-Class or wrap your CGPath in an extra object.



回答4:

import UIKit

class MyClass: NSObject {

    var list = [CGPath]();

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

var instance = MyClass()
print(instance.list.count) // 1


回答5:

It is probably the fact that you didn't import Foundaion, which is where NSObject is:

import Foundation

This is where NSObject is contained (as far as I know). Hope this helped. :)