I'm trying to port an app from Objective-C to Swift, but I'm having problems with a subclass of GPUImageFilter.
In Obj-C, subclassing GPUImageFilter and using a different fragment shader was as easy as
- (id)init;
{
NSString *fragmentShaderPathname = [[NSBundle mainBundle] pathForResource:@"TestShader" ofType:@"fsh"];
NSString *fragmentShaderString = [NSString stringWithContentsOfFile:fragmentShaderPathname encoding:NSUTF8StringEncoding error:nil];
if (!(self = [super initWithFragmentShaderFromString:fragmentShaderString]))
{
return nil;
}
return self;
}
I can't figure out how to do this in Swift. This is my new initializer:
override init() {
let fragmentShaderPathname = NSBundle.mainBundle().pathForResource("TestShader", ofType: "fsh")!
let fragmentShaderString = NSString(contentsOfFile: fragmentShaderPathname, encoding: NSUTF8StringEncoding, error: nil)
super.init(fragmentShaderFromString: fragmentShaderString)
}
As soon as this gets called, the app crashes with this error message:
/Users/peterwunder/Documents/XCode/welp/welp/GPUImageTestFilter.swift: 12: 7: fatal error: use of unimplemented initializer 'init(vertexShaderFromString:fragmentShaderFromString:)' for class 'welp.GPUImageTestFilter'
What am I doing wrong?
Interesting problem! You're getting tripped up due to the differences in initialization between Swift and Objective-C. If the GPUImageFilter
class were written in Swift, the compiler wouldn't let you have this setup, but since you're mixing & matching it doesn't complain until runtime. Here's what's happening when you try to instantiate your subclass:
let myFilter = CustomGPUImageFilter()
init()
loads the shader string and calls super.init(fragmentShaderString: "foo")
GPUImageFilter.init(fragmentShaderString:)
calls [self initWithVertexShaderFromString:@"bar" fragmentShaderFromString:@"foo"]
.What type is self
in that last line? If you're calling from your subclass's initializer, the type of self
is actually that of your subclass: CustomGPUImageFilter
. So the initializer that gets called is actually on your subclass. Why is this a problem?
Unfortunately, Swift classes don't automatically inherit the initializers from their superclass if they define their own designated initializers, which is what your init()
is. So the runtime tries to call CustomGPUImageFilter.init(vertexShaderFromString:fragmentShaderFromString:)
, doesn't find it, and crashes with that error message.
Happily, the fix is quite simple. If you mark your initializer as convenience init
, you'll no longer be defining a designated initializer, which means your subclass will inherit the initializers from GPUImageFilter
. Then in step 3 above, the runtime will find the inherited initializer on your subclass and execute without a problem. Note that you'll need to call self.init...
instead of super.init...
:
convenience override init() {
let fragmentShaderPathname = NSBundle.mainBundle().pathForResource("TestShader", ofType: "fsh")!
let fragmentShaderString = NSString(contentsOfFile: fragmentShaderPathname, encoding: NSUTF8StringEncoding, error: nil)
self.init(fragmentShaderFromString: fragmentShaderString)
}
For more about this process, read the section on class inheritance and initialization in the Swift documentation. The process is significantly different from Objective-C.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With