Inconsistent behavior in invoking a Swift method from a different class?

I have posted several times asking for help to resolve a conundrum involving the ability to invoke "needsDisplay = true" from a different class. I have done some tests and seem to have revealed some inconsistent behavior in Swift which I don't understand. As I said in previous posts, for some reason I haven't acquired a deep understanding of Apple's object oriented programming - my brain somehow can't wrap itself around a number of its concepts.

First, here is an outline of the class structure of my Mac app, leaving out unimportant details:

class GameViewController: NSViewController {

    @IBOutlet weak var graph: GraphView!
 
     @IBAction func h1_Slider(_ sender: NSSlider) {
        h1_length = sender.floatValue		
       dodraw()					// This is only here to make a point about - it is not used normally
    }

    func dodraw() {
        graph.needsDisplay = true
        return
    }

<lots of other stuff to set up Metal drawing>

}

class Renderer: NSObject, MTKViewDelegate {

    public var graphRef = GameViewController()		//. Allows access to GameViewController from elsewhere

<lots of stuff to set up Metal>

   func draw(in view: MTKView) {    // Metal drawing loop
   	
   	grafRef.dodraw()		// this is supposed to cause GraphView to update its drawing
 
 <this is where the Metal drawing is done>
 
 }
} 
 
class GraphView:NSView
 {
 	
 	override func draw(_ dirtyRect:CGRect) {
 		
 		<This is where the Bezier drawing takes place in a View in the Storyboard View identified as an instance of GraphView
 		
 	}
 	
 }
 

Basically, the inner loop of the Metal drawing, which occurs 60 times a second, calls the dodraw() method in the GameViewController via the reference graphRef. This should set the needsDisplay Bool to true in GraphView so that the graph can be drawn. Instead I get "Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value" with the graph underlined in red.

Here is the inconsistency, as I see it. If I replace the "needsDisplay = true" in the dodraw() method with a print("Q") and do nothing else, the letter Q is printed many times in the output window of Xcode. This shows that the graphRef link in Renderer is working fine. Likewise, if I put back the "graph.needsDisplay = true" and remove the dodraw() call in the Metal drawing loop, the graph is displayed while I move the slider in the GameViewController class. This shows that there seems to be no trouble unwrapping the Optional value in the "graph.needsDisplay = true" statement.

I consider this to be a very inconsistent behavior: I have no trouble invoking the needsDisplay = true command by appending a "graph." before it and I have no trouble referring to the dodraw() method from the Metal inner loop. Only trouble is I can't do these at the same time. What is going wrong? Is it a misconception about how Swift works?

In addition to getting the "unexpectedly found nil" message, I also got in the output window "[Nib Loading] Failed to connect (controller) outlet from (Vectors.GameViewController) to (MTKView): missing setter or instance variable". This indicates that the linking between the classes didn't occur.

It would help to see more code, notably the loop. Are you sure you don't call Renderer multiple times, creating a new IBOutlet each time ?

What is Vectors?

If I replace the "needsDisplay = true" in the dodraw() method with a print("Q"

could you print more information:

print(#function, "graph =", graph)

I am not quite sure how I could easily post a minimal project. I think it would be very time consuming to parse it down to its basic components. I could post a zip archive of the entire project - I am not interested in selling it and have written it for my own use (It is a physics demo which I originally wrote in Obj. C and OpenGL and used it in a lecture demo at my University about 16 years ago. Swift and Metal are much, much more difficult to use, in my opinion).

I think your problem is this line:

    public var graphRef = GameViewController()		//. Allows access to GameViewController from elsewhere

This creates a view controller, but it's not initialized properly** and so I wouldn't expect to be able to cause any drawing. My guess is that you have another instance of GameViewController somewhere that is initialized properly, and that's the one that draws initially. However, that instance won't draw again because it's not the one you're setting needsDisplay to true on.

** Of course, it is possible, with quite a bit of custom code in GameViewController to create it like you've shown and have everything work properly. However, given the symptoms you describe, I'm guessing you created a second, non-functional instance that's swallowing needsDisplay.

Is there some way to access the method dodraw() from the Renderer class without creating another instance of GameViewController? I am fairly new to Swift and have been told that a delegate might serve but I have no idea how to do this. Thanks!

For example, making dodraw() a public method might work, but I understand that then GameViewController would also need to be public as well as all its methods. I have found that using global variables makes things much easier but it is not in the spirit of object oriented programming.

I tried to make dodraw() public but it is still not recognized by a call from the Renderer class. I tried calling it using GameViewController.dodraw() but this doesn't work either. Sorry for being so obtuse, but I can't see how to do this.

The storyboard identifies GameViewController as the controller for the single window that is created by the app. I have no idea how to access methods in this object.

After prompting from others, I created a protocol and delegate and removed the graphRef instantiation of GameViewController so there should be only one instance. The protocol was:

protocol link {
 dodraw()
}

I then created a delegate by putting the following code in the Renderer class:

var delegate: link?

I invoked the dodraw() from the draw in Renderer using the following statement:

        self.delegate?.dodraw()
 

Now, there is presumably only one instance of GameViewController. The protocol/delegate worked in accessing dodraw() in GameViewController but I still got nil for graph. Things are still a mystery.

The problem was solved by a posting on Stack Overflow. Basically, you were correct - the problem was that I was creating an additional instance of the GameViewController. In order to force graphRef to refer to the original instance, the suggestion was to re-bind it to the appropriate instance by placing this statement in the Renderer init:

        self.graphRef = graphRef

Then the guard statement in GameViewController needed to include the change so that graphRef referred to self and the init in Renderer needed to be notified of the same thing. After these changes, I was able to call dodraw() using graphRef.dodraw() and everything worked well.

Thanks for your help!

Inconsistent behavior in invoking a Swift method from a different class?
 
 
Q