Apple documentation suggests to override an NSDocument convenience init (initWithType:error:) as described here.
However, as this is a convenience init, I cannot override it. But I still need to execute some code when a new document is created. I do not want to execute that code when I load a document.
In my particular case I try to initialise an NSPersistentDocument, but I doubt that is relevant.
What shall I do?
Above answer works for Swift 1.
It has to be changed to answer below in Swift 2:
convenience init(type typeName: String) throws {
self.init()
// Rest of initialization code here
}
This was answered here: http://meandmark.com/blog/2015/07/nsdocument-initwithtype-in-swift-2/
Reposted for convenience since this is a common problem.
To execute init code for a new document:
// Create new document (only called for new documents)
convenience init?(type typeName: String, error outError: NSErrorPointer) {
self.init()
fileType = typeName
// add your own initialisation for new document here
}
The problem in Swift is that you can not call a convenience initializer in super. Instead you must delegate to a designated initializer in self. This means that you can't take advantage of any of supers convenience initializers and you must implement the initialization your self---hence fileType = typeName above. As much as I like Swift, I find this stupid: what's the point of re-implementing code that could be reused!?
Related
I'm trying to capture initialisation of InputStream with file URL in Swift. I've successfully implemented such capture in Objective-C for NSInputStream class.
The issue is that after exchanging initialisers, swizzled method is not triggered. Furthermore, after method exchange, calling swizzled method directly does not lead to its execution, meaning, its implementation was successfully exchanged. I'd like to know, what are the reasons of such odd behaviour, as method is swizzled successfully, but it's not clear, which method was replaced by swizzled one. Provided sample with current implementation of InputStream swizzling in Swift.
private let swizzling: (AnyClass, Selector, Selector) -> () = { forClass, originalSelector, swizzledSelector in
if let originalMethod = class_getInstanceMethod(forClass, originalSelector),
let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
extension InputStream {
#objc dynamic func swizzledInit(url: URL) -> InputStream? {
print("Swizzled constructor")
return self.swizzledInit(url: url)
}
static func Swizzle() {
swizzling(InputStream.self, #selector(InputStream.init(url:)), #selector(swizzledInit(url:)))
}
}
From the InputStream documentation:
NSInputStream is an abstract superclass of a class cluster consisting of concrete subclasses of NSStream that provide standard read-only access to stream data.
The key here: when you create an InputStream, the object you get back will not be of type InputStream, but a (private) subclass of InputStream. Much like the Foundation collection types (NSArray/NSMutableArray, NSDictionary/NSMutableDictionary, etc.), the parent type you interface with is not the effective type you're working on: when you +alloc one of these types, the returned object is usually of a private subclass.
In most cases, this is irrelevant implementation detail, but in your case, because you're trying to swizzle an initializer, you do actually care about the value returned from +alloc, since you're swizzling an initializer which is never getting called.
In the specific case of InputStream, the value returned from +[NSInputStream alloc] is of the private NSCFInputStream class, which is the effective toll-free bridged type shared with CoreFoundation. This is private implementation detail that may change at any time, but you can swizzle the initializer on that class instead:
guard let class = NSClassFromString("NSCFInputStream") else {
// Handle the fact that the class is absent / has changed.
return
}
swizzling(class, #selector(InputStream.init(url:)), #selector(swizzledInit(url:)))
Note that if you're submitting an app for App Store Review, it is possible that the inclusion of a private class name may affect the review process.
I want to create init for my class that might look something like this:
initWithSomeMode { // Not compilable
self.init()
self.customSetup()
}
Of course code above will not work, I just want to show what I want to achieve.
I can only see convenience init in Swift class, but in that case I need to add parameters, but I don't need them.
So, I might achieve this with convenience init something like this:
convenience init(foo: String?) {
self.init()
self.customSetup()
}
Is there any way to create custom init without parameters?
You need to create a static method:
static func initWithSomeMode() -> YourClass {
let obj = YourClass()
obj.someCustomSetup()
return obj
}
And then you can do this:
let yourClass = YourClass.initWithSomeMode()
Also a workaround, but I tend to use this:
convenience init(withSomeMode: Void) {
self.init()
self.customSetup()
}
and then:
YourClass.init(withSomeMode: ())
it has some advantages over static method, like easier subclassing. But yes, still a workaround.
In current Swift you can only write a single no-parameter method that follows the Swift initialization process and rules: init()
To customize initialization, you must provide named input parameters: init(with: String)
There is no way to write a no-parameter "named initializer" to the left of the ( and still have it be an initialization function that follows the init rules. Something like initSpecial() won't work.
Initializers
Initializers are called to create a new instance of a particular type. In its simplest form, an initializer is like an instance method with no parameters, written using the init keyword.
Customizing Initialization
You can customize the initialization process with input parameters and optional property types, or by assigning constant properties during initialization, as described in the following sections.
https://docs.swift.org/swift-book/LanguageGuide/Initialization.html
This question already has answers here:
Custom class clusters in Swift
(7 answers)
Closed 6 years ago.
Objective-C has a peculiar, but very helpful ability to make an initializer return a different instance than the one the initializer is being called on. You can read about that here.
It's great for things like class clusters, but even for things as simple as ensuring specific instances are returned based on specific conditions (i.e. newing up an Account class with an account number will always return the same instance of the Account class for that number.)
However, in our app, we're using Swift but I don't see any such provisions in this language. Is there such a thing?
Normally we'd simply create a class method to return the instance we're interested in, but while we own the class in question, we do not own the calling code which uses the standard initializer syntax. e.g.
let foo = OurClass()
We want to control which instance is handed back to them based on some external conditions (omitted for clarity here.)
So does Swift have any such mechanisms?
While not as easy or clean as objective-c, hopefully you should be able to achieve your goal using convenience initializers or failable initializers (or both).
A convenience initializer allows you to delegate initialization to another initializer, essentially giving you control of how the object is initialized:
class RecipeIngredient {
var quantity: Int
init(quantity: Int) {
self.quantity = quantity
}
convenience init() {
// You control how the properties are initialized here
if (/* Some external or internal condition */) {
self.init(quantity: 1)
}
else {
self.init(quantity: 2)
}
}
}
// The caller
let mysteryFood = Food()
The failable initializer allows you to return nil in a designated initializer if a condition isn't met or if an error occurs during initialization:
class Account {
var id: Int
// Failable initializers have '?' after init
init?(id: Int) {
// You control if the initialization succeeds or not
if (/* check your db if id is duplicate, for example */) {
return nil
}
// Succeeds
self.quantity = quantity
}
}
// The caller
let account = Account(id: 5)
if (account == nil) {
// It failed
}
else {
// It succeeded
}
In your case, based on your comments, it sounds like you would want to use a failable initializer that returns nil if the caller is attempting to create a duplicate instance.
Hopefully this is along the lines of what you are looking for!
As far as I can tell it's not possible with classes, but you can do this with struct initializers (self = otherValue). If it's possible the class can be converted to a struct that would probably be the way to go.
The obvious alternative (and arguably better design) is a factory method, and when working with an already existing API, you could deprecate that initializer or make it fallible to indicate the factory method should instead be used. Otherwise there are a few options that come to mind.
Copy the properties of the instance you would like to return to the new instance you're creating.
As suggested, drop down to Objective-C, for that initializer.
Use a subclass only for Swift and enforce usage of the constructor.
I feel like the answer is obvious, but I haven't been able to figure this out and it seems to be a recurring problem for me. Basically I want to do something like this:
extension NSData {
convenience init(JSONObject: AnyObject) {
do {
self = try NSJSONSerialization.dataWithJSONObject(JSONObject, options: [])
}
catch {
self = nil
}
}
}
However it won't let me simply assign a value to self. I do this all the time with enums, but it won't let me do it with classes. Is there any way implement at convenience initializer using an instance of the class created in the initializer implementation?
Saying that factory initializers are "not supported yet" in Swift is fallacious. Their exclusion is a design decision, and their use intended to be covered by failable initializers; quoting the following Apple Swift blog post
Failable initializers eliminate the most common reason for factory
methods in Swift, which were previously the only way to report failure
when constructing this object.
...
Using the failable initializer allows greater use of Swift’s uniform
construction syntax, which simplifies the language by eliminating
the confusion and duplication between initializers and factory
methods.
So in your case, you're probably looking for a convenience failable initializer. E.g., something along the lines
extension NSData {
convenience init?(JSONObject: AnyObject) {
do {
let foo = try NSJSONSerialization.dataWithJSONObject(JSONObject, options: [])
self.init(data: foo)
}
catch {
return nil
}
}
}
/* Example usage */
let foo : AnyObject = ["Foo":"bar"]
let bar = NSData.init(JSONObject: foo)
In the title of your question you include "... instead of calling an existing init". When making use of convenience initializer, a designated initializer (of same class) must be called at some point (even via other convenience initializers). From the Swift Language Guide - Initialization - Class Inheritance and Initialization:
...
Rule 2
A convenience initializer must call another initializer from the same
class.
Rule 3
A convenience initializer must ultimately call a designated
initializer.
The example code above, however, allows an early escape (failure) of the convenience initializer if NSJSONSerialization.dataWithJSONObject(...) fails, but if it succeeds, sooner or later a designated initializer needs to be called (in this case init(data:) designated initializer).
For details on failable initializers, see the Swift Language Guide - Initialization - Failable Initializers. For an additional remark regarding the initializer chain (convenience -> ... -> designated initializer), see rickster:s comment below.
If I understand you right you want a factory initializer. In Swift they are not supported yet. The best you could do is to use static factory method.
Please don't ask me to go read a link on the apple website, because I've read all of it!
Im confused on where the actual initialization of the property 'text' is taking place? Is it in the init method or is it happening in the instance declaration?
Because it seems to me that the initialization of the text property is happening in the instance declaration where it's being given the value "How about beets?"
And if so, then why does the apple book state that all properties must be initialized before an instance is created; within the class definition (correct me if I'm wrong)
Lastly if 'text' is actually being initialized in the class definition....where is the initialization??
class SurveyQuestion {
let text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
print(text)
}
}
let beetsQuestion = SurveyQuestion(text: "How about beets?")
The initialization takes place in the init method. Note that init takes one parameter, text, and assigns that parameter to self.text.
I believe the process followed in you code is:
You call SurveyQuestion(text: "How about beets?") to get an instance of the SurveyQuestion class.
When the class is running it's initialization method init(text: String) it initializes all the properties. That means you're initializing the text property, giving it a value.
Finally the class finishes initialization and you get an instance of that class.
That corresponds to Apple's documentation as when you initialize the class the properties are initialized but you don't get the class instance until the init method has finished, and that means until all properties are initialized.
Sorry for the initialization redundance, I didn't find another way to explain it.
I hope that solves your doubts.