In Swift, is there a pattern to produce the semantic equivalent of overriding a convenience initializer?
Behaviorally, this what I'd like to do:
class MyScene: SCNScene {
convenience init(URL url: NSURL, options: [String : AnyObject]?) {
super.init(URL: url, options: options)
.... other initialization
}
}
Yes, I understand initializer chaining and why you can't actually do this directly; however, it seems like a reasonable and frequently needed behavior.
Sure, I could make my own convenience initializer which does the same thing as the superclass, and calls a designated superclass initializer. But since I'm subclassing an opaque system class, I don't necessarily know how to reconstruct the behavior.
Any way to do this or a design pattern which obviates the need for this?
Related
For example, I have a simple subclass of UIImage that have some additional properties:
class Image: UIImage {
var poeticDescription: String?
}
What I would like is like this:
init(with: UIImage) { /* something to do here */ }
You cannot initialize an existing object a second time, no matter, if the source object is a superclass object or not. Of course you can call the initializer with a superclass object as argument, but this will create a completely new object.
init(with superclassObject: SuperClass) {
// Do some stuff with superclass object
}
The usual way to do inheriting is calling the superclass initializer in the inherited initializer:
init() {
super.init()
}
But again, in this case you cannot use an already existing superclass object to initialize it again.
Maybe you can explain, what you want to achieve. We can then help you to find the correct way.
I think what you want to achieve is to have a UIImage subclass with custom properties. You have to call a super initializer with the source image's data. For UIImage subclassing is tricky, have a look this answer for an example.
I am trying to Mock an amazon services object to perform UnitTesting on a related code. I have done it as follows but each time the init is hit it crashes with error failed:
caught "NSInternalInconsistencyException", "- init is not a valid
initializer
Normally the same object will be created using there factory method, so it seems the initializer is made private or something. How can such object be mocked ?
class MyAWSiOTDataManager : AWSIoTDataManager {
override func publishString(_ string: String, onTopic topic: String, qoS qos: AWSIoTMQTTQoS) -> Bool {
print("publish string called")
return true
}
override init() {
}
}
let manager = MyAWSiOTDataManager()
You must call the designated initializer of AWSIoTDataManager inside your init call and remove the override decorator. That is why you got the error.
You can not mock something by subclassing it. With Swift, mocks are usually provided through a shared protocol. Define a protocol for the interface that you use from the third party library. Create an empty extension on the library with your protocol (there should be no code needed). Then implement a mock object against the protocol for use in your tests.
Can we communicate between classes like below code instead of using delegate/protocol pattern and notification. I haven't seen any code example like this. But just curious to know why this should work or not work.
class Class1 {
var class2Obj: Class2 = Class2()
init() {
class2Obj.class1Obj = self
}
func class1Method() {
print("Parent")
}
}
class Class2 {
weak var class1Obj: Class1?
func class2Method() {
class1Obj.class1Method()
}
}
What you have here is a delegate pattern. Your delegate property is merely called class1Obj rather than the more customary delegate name. The key issue here is that you're not using a protocol, and as a result these two classes are "tightly coupled," i.e. Class2 is highly dependent upon details of the Class1 implementation. Furthermore, with these two tightly coupled classes, it's not immediately clear which methods of Class1 that Class2 might need. It makes maintenance of Class1 harder because so it's easy to accidentally make a change that breaks behavior in Class2. It also makes it hard to use Class2 in conjunction with some other class other than Class1.
Instead, you'd generally declare a protocol to precisely articulate the nature of the contract between Class2 and other object that might need to use it. The result is that the classes are less tightly coupled, i.e. Class2 needs to know nothing about the other class other than its conformance to the protocol in question. Furthermore, when editing Class1, if you declare it to conform the the protocol, the compiler will warn you when you fail to implement some required method or property.
So, with a negligible amount of work up-front, the protocol makes the code much easier to maintain. It also provides some additional flexibility whereby you may use Class2 in conjunction with something other than Class1 in the future.
Bottom line, protocols can result in code that is easier to maintain and is more flexible, with no hidden assumptions.
If you don't want to use the delegate-protocol pattern, another alternative is to use a closure, where Class1 supplies a block of code that Class2 can call. So you can do something like:
class Class1 {
var class2Obj = Class2()
init() {
class2Obj.handler = { [weak self] in // note `weak` reference which avoids strong reference cycle
self?.class1Method()
}
}
func class1Method() {
print("Parent")
}
}
class Class2 {
var handler: (() -> Void)?
func class2Method() {
handler?()
}
}
While the delegate-protocol pattern is useful when you have a rich interface between the two classes, this closure pattern when you have a very simple interface between the two classes.
Frankly, a more common permutation of the above is where the closure is more directly associated with some particular request that Class1 initiates in Class2. So, you might just make the parameter a closure to the appropriate method in Class2. Furthermore, you're often passing data back, so imagine that we're passing back an optional String:
class Class1 {
var class2Obj = Class2()
func performClass2Method() {
class2Obj.class2Method { string in
guard let string = string else { return }
self.class1Method()
}
}
func class1Method() {
print("Parent")
}
}
class Class2 {
func class2Method(completionHandler: #escaping (String?) -> Void) {
// do something which creates `string`
// when done, call the closure, passing that `string` value back
completionHandler(string)
}
}
These closure patterns a great way to do simple interfaces between objects, but also keeps the two classes very loosely coupled (i.e. Class2 has no dependencies upon Class1), much like using a protocol in the delegate pattern did. But hopefully the above closure examples illustrate a simple alternatives to the rich delegate-protocol pattern.
I want to subclass UNNotificationCategory(UserNotifications), because I want to use enums instead of hard coded strings as category identifiers.
There is one convenience init inside UNNotificationCategory definition
public convenience init(identifier: String, actions: [UNNotificationAction], intentIdentifiers: [String], options: UNNotificationCategoryOptions = [])
I am not able to write an initializer for my subclass.
I understand I cant have designated initializer inside the subclass because I want to call the convenience init of superclass. But my convenience init is also throwing complier error.
Here's the code:
enum PushNotificationCategoryIdentifier:String {
}
convenience init(categoryIdentifier:PushNotificationCategoryIdentifier, actions:[UNNotificationAction], intentIdentifiers:[String], options: UNNotificationCategoryOptions) {
self.init(identifier: categoryIdentifier.rawValue, actions: actions, intentIdentifiers: intentIdentifiers, options: options)
}
This is resulting in error: self.init isn't called on all paths before returning from initializer
I guess this is because this class is implemented in Objective-C and may be they have not called the designated initailizer from convenience initailizer(as Objective-C classes dont have to call designated initializer from convenience initailizer).
But does that mean I can't subclass UNNotificationCategory if I want to write an initializer in it?
No you can do this. You will have to define init() method for this. Right now you have only defined convenience init(). But you will have to define init()in your subclass.
When you write a convenience init() it is only there to help initialization in an easy way but still you will have to call designated init with syntax init() from the convenience init().
You can read it on Apple Official Documentation
As a learning exercise I am trying to implement a subclass of SKShapeNode that provides a new convenience initializer that takes a number and constructs a ShapeNode that is a square of number width and height.
According to the Swift Book:
Rule 1
If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.
Rule 2
If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.”
However, the following class doesn't work:
class MyShapeNode : SKShapeNode {
convenience init(squareOfSize value: CGFloat) {
self.init(rectOfSize: CGSizeMake(value, value))
}
}
Instead I get:
Playground execution failed: error: <REPL>:34:9: error: use of 'self' in delegating initializer before self.init is called
self.init(rectOfSize: CGSizeMake(value, value))
^
<REPL>:34:14: error: use of 'self' in delegating initializer before self.init is called
self.init(rectOfSize: CGSizeMake(value, value))
^
<REPL>:35:5: error: self.init isn't called on all paths in delegating initializer
}
My understanding is that MyShapeNode should inherit all of SKShapeNode's convenience initializers because I am not implementing any of my own designated initializers, and because my convenience initializer is calling init(rectOfSize), another convenience initializer, this should work. What am I doing wrong?
There are two problems here:
SKShapeNode has only one designated initializer: init(). This means that we cannot get out of our initializer without calling init().
SKShapeNode has a property path declared as CGPath!. This means that we don't want to get out of our initializer without somehow initializing the path.
The combination of those two things is the source of the issue. In a nutshell, SKShapeNode is incorrectly written. It has a property path that must be initialized; therefore it should have a designated initializer that sets the path. But it doesn't (all of its path-setting initializers are convenience initializers). That's the bug. Putting it another way, the source of the problem is that, convenience or not, the shapeNodeWith... methods are not really initializers at all.
You can, nevertheless, do what you want to do — write a convenience initializer without being forced to write any other initializers — by satisfying both requirements in that order, i.e. by writing it like this:
class MyShapeNode : SKShapeNode {
convenience init(squareOfSize value: CGFloat) {
self.init()
self.init(rectOfSize: CGSizeMake(value, value))
}
}
It looks illegal, but it isn't. Once we've called self.init(), we've satisfied the first requirement, and we are now free to refer to self (we no longer get the "use of 'self' in delegating initializer before self.init is called" error) and satisfy the second requirement.
My understanding of Initializer Inheritance is the same as yours, and I think we are both well aligned with what the book states. I don't think it's an interpretation issue or a misunderstanding of the stated rules. That said, I don't think you're doing anything wrong.
I tested the following in a Playground and it works as expected:
class RectShape: NSObject {
var size = CGSize(width: 0, height: 0)
convenience init(rectOfSize size: CGSize) {
self.init()
self.size = size
}
}
class SquareShape: RectShape {
convenience init(squareOfSize size: CGFloat) {
self.init(rectOfSize: CGSize(width: size, height: size))
}
}
RectShape inherits from NSObject and doesn't define any designated initializers. Thus, as per Rule 1, it inherits all of NSObject's designated initializers. The convenience initializer I provided in the implementation correctly delegates to a designated initializer, prior to doing the setup for the intance.
SquareShape inherits from RectShape, doesn't provide a designated initializer and, again, as per Rule 1, inherits all of SquareShape's designated initializers. As per Rule 2, it also inherits the convenience initializer defined in RectShape. Finally, the convenience initializer defined in SquareShape properly delegates across to the inherited convenience initializer, which in turn delegates to the inherited designated initializer.
So, given the fact you're doing nothing wrong and that my example works as expected, I am extrapolating the following hypothesis:
Since SKShapeNode is written in Objective-C, the rule which states that "every convenience initializer must call another initializer from the same class" is not enforced by the language. So, maybe the convenience initializer for SKShapeNode doesn't actually call a designated initializer. Hence, even though the subclass MyShapeNode inherits the convenience initializers as expected, they don't properly delegate to the inherited designated initializer.
But, again, it's only a hypothesis. All I can confirm is that the mechanics works as expected on the two classes I created myself.
Building on Matt's answer, we had to include an additional function, or else the compiler complained about invoking an initializer with no arguments.
Here's what worked to subclass SKShapeNode:
class CircleNode : SKShapeNode {
override init() {
super.init()
}
convenience init(width: CGFloat, point: CGPoint) {
self.init()
self.init(circleOfRadius: width/2)
// Do stuff
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Good news from 2019! I can report that I now have a SKShape subclass that has the following three initializers:
override init() {
super.init()
}
convenience init(width: CGFloat, point: CGPoint) {
self.init(circleOfRadius: width/2)
self.fillColor = .green
self.position = point
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
and that behaves exactly as expected: when you call the convenience initializer, you get green dots in the desired position. (The double calling of init() as described by #matt and #Crashalot, on the other hand, now results in an error).
I'd prefer to have the ability to modify SKShapeNodes in the .sks scene editor, but you can't have everything. YET.