I've written a macOS Document-type app with a storyboard, using the Xcode template, and somewhere along the line the association between the initial app launch and the document has varied from the expected pattern such that none of the NSDocument initializers I expect are called when the app first launches (but are called every new window thereafter).
I've subclassed all four documented NSDocument initializers, as follows:
public class Simulation: NSDocument {
override init() {
debugPrint("--------------------\(#file)->\(#function) called")
super.init()
}
init(contentsOf: URL, ofType: String) throws {
debugPrint("--------------------\(#file)->\(#function) called")
fatalError()
}
init(for: URL?, withContentsOf: URL, ofType: String) throws {
debugPrint("--------------------\(#file)->\(#function) called")
fatalError()
}
convenience init(type: String) throws {
debugPrint("--------------------\(#file)->\(#function) called, type: \(type)")
self.init()
}
public override class func autosavesInPlace() -> Bool {
debugPrint("--------------------\(#file)->\(#function) called")
return false
}
}
None of the inits exhibit the debugPrint output when the app launches. The app window is created successfully on launch, with no apparent document association.
However, I notice some really odd behavior I can't explain:
Although I've seen no init call, autosavesInPlace is called three times after the app starts on some instance of the document
When I use cmd-N (i.e., File->New and therefore newDocument()) to create a new document, autosavesInPlace is called three more times, and then the document init is executed!
I never see a call to makeWindowControllers()
My NSDocument subclass is named Simulation. The anomaly seems to be that there's some magic in the initial startup that bypasses Simulation.init, but calls it every document+window creation thereafter.
Here are my questions:
Why does the initial launch not call Simulation.init()?
How does autosavesInPlace find an instance of Simulation to call when there's only that initial, seemingly partially constructed window?
In your storyboard, make sure both your Window Controller and its Content View Controller have Is Initial Controller unchecked and Presentation set to Multiple in the Attributes Inspector.
Having Is Initial Controller checked will cause the application to instantiate one window controller before any of the NSDocument/NSDocumentController "magic" happens. Presentation: Multiple should be selected for coherence, although it might not really make a difference.
Also, make sure your document types are properly setup in Info.plist, particularly the NSDocumentClass key (should contain $(PRODUCT_MODULE_NAME).Simulation).
I believe your question about autosavesInPlace is already answered in the comments…
Related
This question deals with tab window restoration in a document-based app.
In an OSX, document-based app, which allows a user to create and convert tab windows, I need to preserve and restore the 'tab' state of each window.
Currently, my document controller restores its documents windows, but not the tab deployment; I get back individual windows; I can merge all back into one, but this is too heavy-handed as their former groupings are lost.
My app document class's - makeWindowControllers() function is where I affect the new controllers, whether they should cascade, which I'd read be false, during restore:
// Determine cascade based on state of application delegate
controller.shouldCascadeWindows = <app did receive applicationWillFinishLaunching>
so it would be false until it's finished launching.
Finally, my window's class features methods:
override func addTabbedWindow(_ window: NSWindow, ordered: NSWindow.OrderingMode) {
super.addTabbedWindow(window, ordered: ordered)
window.invalidateRestorableState()
}
override func moveTabToNewWindow(_ sender: Any?) {
super.moveTabToNewWindow(sender)
self.invalidateRestorableState()
}
override func encodeRestorableState(with coder: NSCoder) {
if let tabGroup = self.tabGroup {
let tabIndex = tabGroup.windows.firstIndex(of: self)
coder.encode(tabIndex, forKey: "tabIndex" )
Swift.print("<- tabIndex: \(String(describing: tabIndex))")
}
}
override func restoreState(with coder: NSCoder) {
let tabIndex = coder.decodeInt64(forKey: "tabIndex")
Swift.print("-> tabIndex: \(tabIndex)")
}
to invalidate the window restore state when the tab state is changed. But I'm not sure with the NSWindowRestoration protocol implementation, who or what needs to implement the protocol when a document controller is involved.
I think this is the reason the last function is never called. I get debug output about the encoding but during the next app execution the restoreStore(coder:) function is never called.
So who implements this window restore protocol in such an environment I guess is my question, or a decent example doing so.
My question reveals you not require anything special for a document based app; I've updated my prototype which features this support and environment here SimpleViewer, which features Swift5, a document based app supporting tabs.
I have a class, MyViewController, with several actions which are triggered from menu items.
class MyViewController: NSViewController { … }
The actions are connected to first responder in IB. Actions look like this:
#IBAction func removeSelectedItems(_ sender: AnyObject) {
arrayController.remove(contentsOf: arrayController.selectedObjects)
}
validateMenuItem(:) code looks like this:
override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
let selection = arrayController.selectedObjects
if (menuItem.action == #selector(removeSelectedItems(_:))) {
return selection!.count > 0
}
return super.validateMenuItem(menuItem)
}
When I include the actions in the if() list, everything is fine. But if I don't, and validateMenuItem(:) falls through to super, I get an exception:
[MyApp.MyViewController validateMenuItem:]: unrecognized selector sent to instance 0x618000165ac0
If I instead return false at the end of the method, there's no exception.
This happens when validateMenuItem(:) is called, e.g. when the menu is opened. In spite of this, though, the action is triggered when the item is selected.
Am I wrong to be calling super at the end of the method? I would expect the responder chain to be queried until a match was found, not an exception claiming I didn't implement a method which I clearly did!
Am I wrong to be calling super at the end of the method
Yes. Neither NSViewController nor any of its superclasses implements validateMenuItem. Despite the override in Swift, it is not actually inherited. It is injected in Objective-C by an informal protocol (NSMenuValidation). [The Swift compiler doesn't understand that kind of trickery; hence the override despite the fact that we are not overriding anything.]
See https://forums.developer.apple.com/thread/46772
I’ve created a Swift macOS app which uses SMJobBless to create a helper with escalated privileges. This works fine—the helper gets installed to /Library/Privileged Helper Tools and an accompanying LaunchDaemon gets created in /Library/LaunchDaemons. However, the helper is unable to start successfully. Instead, it crashes with an “Illegal instruction: 4” message.
I’ve prepared the helper to respond to XML connections by implementing the NSXPCListenerDelegate protocol. Here‘s my Helper main.swift code:
import Foundation
class HelperDelegate: NSObject, NSXPCListenerDelegate {
func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
newConnection.exportedInterface = NSXPCInterface(with: HelperToolProtocol.self)
newConnection.exportedObject = HelperTool()
newConnection.resume()
return true
}
}
let delegate = HelperDelegate()
let listener = NSXPCListener.service()
listener.delegate = delegate
listener.resume()
The crash occurs on the last line, listener.resume().
I tried to launch the helper app manually from the command line (which is identical to what the LaunchDaemon does) and, again, it crashes with the above error message printed to stdout. I don’t have any more ideas on how to test this for the root cause. My implementation is more than rudimentary, following Apple’s guidlines for implementing XM services. Also, the various posts on SO regarding XML services haven’t helped me in resolving this issue. Has anyone of you tried to create a privileged helper in Swift successfully? BTW, the app is not sandboxed.
For the sake of completeness, here’s the code for the HelperTool class referenced in my HelperDelegate class above:
import Foundation
class HelperTool: NSObject, HelperToolProtocol {
func getVersion(withReply reply: (NSData?) -> ()) {
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString" as String) as? String ?? "<unknown version>"
let build = Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String ?? "<unknown build>"
if let d = "v\(version) (\(build))".data(using: .utf8, allowLossyConversion: false) {
reply(d as NSData)
}
}
}
And finally the HelperToolProtocol:
import Foundation
#objc(HelperToolProtocol) protocol HelperToolProtocol {
func getVersion(withReply: (NSData?) -> ())
}
Thanks for any help!
After days of testing I finally found a solution which makes my XPC helper launch correctly and respond to any messages. The problem lies in the last three lines of the main.swift module which currently read
let listener = NSXPCListener.service()
listener.delegate = delegate
listener.resume()
which, as put in the question, make the helper crash immediately upon the very last line.
I took these lines directly from Apple’s Creating XPC Services documentation. Here’s the documentation for the NSXPCListener resume() function:
If called on the service() object, this method never returns. Therefore, you should call it as the last step inside the XPC service's main function after setting up any desired initial state and configuring the listener itself.
The solution is to not call the NSXPCListener.service() singleton object but rather instantiate a new NSXPCListener object using the init(machServiceName:)initializer passing the same Mach service name that is being used on the main app’s XPC connection. As resume() in this case would resume immediately—thus terminating the helper—you have to put it on the current run loop to have it run indeterminately. Here’s the new, working code:
let listener = NSXPCListener(machServiceName: "Privilege-Escalation-Sample.Helper")
listener.delegate = delegate
listener.resume()
RunLoop.current.run()
To update complications on the watch, I use a singleton class ComplicationController (irrelevant code has been omitted below):
final class ComplicationController: NSObject, CLKComplicationDataSource {
static let shared = ComplicationController() // Instantiate the singleton
private override init() {
super.init()
print("====self: \(self): init")
} // Others can't init the singleton
}
The singleton is created on the watch by the extension delegate:
class ExtensionDelegate: NSObject, WKExtensionDelegate {
override init() {
super.init()
_ = ComplicationController.shared
}
}
When I launch the watch extension with a breakpoint at the print statement above, the execution breaks and the stack trace is:
When I then execute the print statement in a single step, the debugger shows:
====self: <Watch_Extension.ComplicationController: 0x7bf35f20>: init
When I then continue, the execution breaks again at the same breakpoint, and the stack trace is:
After another single step, the debugger shows:
====self: <Watch_Extension.ComplicationController: 0x7d3211d0>: init
Obviously, the CLKComplicationServer has created another instance of the singleton.
My question is: Did I something wrong or is this a bug? If it is a bug, is there a workaround?
PS: It does not help not to initialize ComplicationController in the ExtensionDelegate. In this case, the 2nd instance is created as soon as ComplicationController.shared is used anywhere in the code.
I found a workaround:
Do not instantiate the singleton in the app, i.e. do not use ComplicationController.shared anywhere.
If you have to call functions in ComplicationController.shared, send a notification, e.g. to the default notification center.
The ComplicationController singleton had to have registered for such notifications, and when it receives one, it has to execute the required function.
This did work for me.
I am designing a framework that uses protocols and extensions to allow for third-parties to add support for my framework to their existing classes.
I'd also like to include some built-in extensions for known classes like UIView, but I don't want to prevent users from defining their own additional support for the same classes.
My question is is there any way that I can extend the same class twice, and override the same (protocol) method in that class both times, while still having some way to call the other if the first one fails.
Elaboration: I really have three goals here I want to achieve:
I want to allow users of my framework to provide their own extensions for their own (or any) UIView subclasses.
I also need some way to allow general behavior that can apply to all UIViews as a fallback option (i.e. if the specific class extension can't handle it, fall back on the generic UIView extension).
I'd also like to separate out my own implementation, by providing some built-in generic view handling, but in such a way that it doesn't prevent third parties from also defining their own additional generic handling. (If I can't do this, it's not a big deal, the first two parts are the most important.)
I have part 1 working already. The problem is how to get this fallback behavior implemented. If I do it all with extensions, the subclass will override the superclass's implementation of the protocol method. It could call super.method, but I'd like to avoid putting that responsibility on the subclass (in case the author forgets to call super).
I'd like to do this all from the framework code: first, call the object's protocol method. If it returns false, I'd like to somehow call the generic UIView handler.
Now that I'm typing it all out, I'm wondering if I can just use a different method for the generic fallback and be done with it. I just figured it would be elegant if I could do it all with one method.
No! It can't be extended multiple times.
extension Int {
var add: Int {return self + 100} // Line A
}
extension Int {
var add: Int {return self + 105} //Line B
}
Doing so would create a compile time error ( on Line B) indicating: Invalid redeclaration of 'add'
Swift is a static typing language and helps you find these sorts of errors before runtime
In Objective-C you can write this and still not get an error, however the result would be undefined, because you wouldn't know which method gets loaded first during runtime.
Overriding a single protocol method twice in 2 separate extensions wouldn't work, because the protocol method names would collide. Once compiled, they're all just methods on the same class. With that in mind, perhaps put all the protocol methods in their own extension & call them from within the other ones?
The following could be one general option. Could get messy if you decide to keep adding additional extension functionality.
class baseClass {
//stuff
}
extension baseClass: myProtocol {
override func myProtocolMethod(args) -> returnType {
//Repeat this in a separate extension & your method names collide
var status: Bool
//protocol method code sets status as appropriate...
return status = true ? optOne(status) : optTwo(status)
}
func optOne(status:Bool) -> returnType{
//do the 'true' thing
return returnType
}
func optTwo(status:Bool) -> returnType{
//do the 'false' thing
return returnType
}
}
extension baseClass {
var oneExtension = myProtocolMethod(someArg)
}
extension baseClass {
var twoExtension = myProtocolMethod(someArg)
}
I realize this Question is over a year old and the original poster has probably moved on to other things, but I'd like to share an idea anyways and perhaps get some feedback.
You say that you want a method that can be overwritten multiple times. The short answer, like many in this thread have given is no, but the long answer is yes.
We can solve the issue with a bit of generic magic.
class MyView: UIView {
var customizer: MyProtocol<MyView> = Defaults()
func willCallCustomizer() {
customizer.coolMethod(self)
}
}
// Use this class as if it were a protocol
class MyProtocol<T: UIView>: NSObject {
func coolMethod(_ view: T) {}
}
// Class inherits from the "protocol"
class Defaults: MyProtocol<MyView> {
override func coolMethod(_ view: MyView) {
// Some default behavior
}
}
/// on the clients end...
class CustomerCustomizer: MyProtocol<MyView> {
override func coolMethod(_ view: MyView) {
// customized behavior
}
}
So if the client wants to use their own customizer they can just set it, otherwise it will just use the default one.
myViewInstance.customizer = CustomerCustomizer()
The benefit of this approach is that the client can change the customizer object as many times as they want. Because MyProtocol is generic, it may be used for other UIView's as well; thus fulfilling the role of a protocol.