Members of subclass not initialized

Hi,

I tried to extend FileHandle for the sake of some unittests, but I ran into a weird run-time failure that I cannot really figure out.

class FakeFileHandle : FileHandle {
   public var written_data : [Data] = []

   override func write(_ data: Data) {
      written_data.append(data)  // fails with EXC_BAD_ACCESS (code=1, address=0x8)
   }
}

This code compiles just fine. And when I run the tests where I create a FakeFileHandle creating works fine, like this:

let f = FakeFileHandle()

That call I assume will initialize with an empty array, but when I call f.write execution fails on written_data.append(data) with a EXC_BAD_ACCESS (code=1, address=0x8)-error.

f.write(some_data)

In the debugger I see that any member that I add to the sub-class does not get initialized properly and that seem to be the direct cause of the failure.

Am I wrong to think that the written_data property of the subclass should always be initialized with an empty Array?

What am I missing/doing wrong here?

Thanks in advance!

what happens if you call super.write(data) ?

just a guess, but maybe that func isnt designed to be overriden without calling super...

This is not really the right forum for this (Apple Developer Forums would be more appropriate), but there might be a sorta Swift language issue here.

Class FileHandle does not have any parameter-less initializers AFAICT (https://developer.apple.com/documentation/foundation/filehandle). That means your subclass should at least invoke a valid super.init that has parameters.

There are some Obj-C classes that have initializer declarations that don't actually follow Swift rules, so sometimes you can invoke parameter-less init when you shouldn't be allowed to.

(In your subclass, super.init() is being synthesized, which obscures what's really going on.)

Thank you for your idea @andrekandore ! However, I don't think calling super.write(data) would change the lack of initialization of the subclass properties, which is what causes the runtime failure.

Thank you for your answer @QuinceyMorris. You are right that there are two sides to the question. One being the specifics of how to subclass FileHandle which is not relevant here, but the other is related to what is allowed and what happens when swift initializes subclasses that inherits from Object-C classes like in this case where FileHandle inherits from NSObject .

And I can't get my head around how the code can compile when it clearly leaves the subclass (which is all swift) instance uninitialized?

There are some Obj-C classes that have initializer declarations that don't actually follow Swift rules, so sometimes you can invoke parameter-less init when you shouldn't be allowed to.

This is probably what is going on then... Where should I look to find documentation about how Obj-C base classes and Swift sub classes should behave when subclassing/initialization?

Since it's a type-specific issue, a good place to start is the type's Obj-C header. Even if the init isn't properly marked to prevent its use in Swift, there's usually some comments about whether or not it should be used which sometimes aren't visible to Swift either.

There's probably more going on here than I originally described. It looks like the underlying Obj-C class NSFileHandle is a class cluster, which means there are no concrete instances of NSFileHandle, only private subclasses which you can't get to at initializer time. (Thanks to @eskimo for a heads-up on this point.)

If this is like other class clusters in Obj-C, such as NSArray, when you think you are creating an instance in Obj-C, you are actually starting with a placeholder pseudo-object. This would be replaced with an instance of a private concrete subclass during initialization, but shenanigans like that aren't supported in Swift.

If that's happening here, that explains the crash. Your subclass init() is automatically calling through to super.init() which creates a placeholder and nothing else. That means your attempt to create a working subclass instance is going to fail — the placeholder isn't even a real object.

This is on top of, but separate from, the issue of whether Swift should allow you to omit an explicit initializer. That may be a bug in the "overlay" that transforms NSFileHandle into FileHandle, or in Swift's automatic Obj-C import, or it may be a defect (not exactly a bug) because the Obj-C initializer pattern (defined in header files as @Jon_Shier mentioned) doesn't have an exact equivalent in Swift initializer terms.

What you would do next depends on whether you want FakeFileHandle to have actual FileHandle/NSFileHandle behavior, or whether it's just a placeholder that has no real functionality.

For the first alternative, I'd suggest you make FakeFileHandle wrap a separate instance of FileHandle (which you initialize in the proper way). For the second, you don't need an actual file handle.

Then, for either alternative, you would have to write methods in FakeFileHandle for all of FileHandle's methods that you need. You can't actually inherit any behavior. Luckily, IIRC, NSFileHandle doesn't have a lot of methods.

1 Like

You might want to just write your class in Obj-C.

It's not just that the members of your subclass aren't initialized, the entire object isn't initialized! -[NSFileHandle init] immediately deallocates self and returns nil. You can see this at the REPL

Welcome to Apple Swift version 5.1 (swiftlang-1103.0.1 clang-1103.0.17.90).
Type :help for assistance.
  1> import Foundation
  2> FileHandle()
$R0: FileHandle = <uninitialized>

Unfortunately, this means that there isn't actually a viable designated initializer for your subclass to chain up to. Perhaps it would be better to express this by composition instead. Sealed class clusters like this are exceedingly difficult to subclass from the outside because of these kinds of internal invariants.

Expressing this in Objective-C is one avenue, but it's still dangerous and as it would rely on NSFileHandle remaining an abstract base class with no initializable storage. Neither its header nor its documentation have guaranteed that to be the case.

@Jon_Shier thanks. I realize that confused things for myself and looked at the source code for swift core foundation, which is of course not what is used when run this on my mac. :blush:

@QuinceyMorris @phoneyDev @codafi Thank you all for explaining and looking into this.

Terms of Service

Privacy Policy

Cookie Policy