Swift read file cause memory usage increase. Is it a leak? - swift

I am reading a file from disk on Mac. The following code in a function keeps making memory usage go up. I don't have any variable to reference the "content", just test to read it in and think the system will release it after the function exit. but it does not. System actually only releases the memory when the for-loop is done.
My question is, how to let system to release the "content" object after the read function exits? I even delay 10s after the function call in the loop.
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func onRead(_ sender: NSButton) {
for n in 0..<100
{
readFile()
sleep(10)//not enough time for system to recycle the memory??
}
}
func readFile()
{
print ("test read file")
let content = FileManager.default.contents(atPath: "/Users/swiftuser/Documents/testfile.txt")
}
}

Related

Perform function at the end of windodDidResize event

I would like to perform a function after the windowDidResize event ended. While this block is running, it prints the following:
NotificationCenter.default
.publisher(for: NotificationName.windowDidResize)
.sink { _ in
// Perform something
print("WindowDidResize")
}.store(in: &cancellableBag)
Print output:
WindowDidResize
WindowDidResize
WindowDidResize
WindowDidResize <-- Want to run a function here
How can I perform a function after the last windowDidResize only once? I'd like to hide some UI elements while the window is being resize (because it make operation sooo slow) and redraw them after the window was resized (with delay of 0.25 sec or something else).
Swift 5.3 | Xcode 12.2 | macOS 11.1
You can implement the optional method windowDidEndLiveResize which will be called only once at the end of the window resizing:
You can also monitor didendliveresizenotification
import Cocoa
class ViewController: NSViewController, NSWindowDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear() {
super.viewWillAppear()
view.window?.delegate = self
}
func windowDidEndLiveResize(_ notification: Notification) {
print(#function)
}
}

window.windowController is nil inside windowWillClose() but it isn't inside viewDidAppear()

I've tried, without success, respond to events such as windowWillClose() and windowShouldClose() inside NSWindowController (yes conforming to NSWindowDelegate).
Later, to my surprise, I was able to receive those events if I make my contentViewController (NSViewController) conform to NSWindowDelegate.
Unfortunately, later on, found out that view.window?.windowController is nil inside windowWillClose() or windowShouldClose(), code:
override func viewDidAppear() {
super.viewDidAppear()
self.view.window?.delegate = self
self.view.window?.windowController // not nil!
}
func windowWillClose(_ notification: Notification) {
self.view.window?.windowController // nil!!
}
func windowShouldClose(_ sender: NSWindow) -> Bool {
self.view.window?.windowController // nil!!
return true
}
After realizing that view.window?.windowController is not nil inside viewDidAppear() the next thing I thought was that Swift garbage collected the controller, so I changed viewDidAppear() in a way that creates another reference of windowController thus preventing garbage collection on said object, code:
var windowController: NSWindowController?
override func viewDidAppear() {
super.viewDidAppear()
self.view.window?.delegate = self
windowController = view.window?.windowController
}
func windowWillClose(_ notification: Notification) {
self.view.window?.windowController // NOT nil
}
func windowShouldClose(_ sender: NSWindow) -> Bool {
self.view.window?.windowController // NOT nil
return true
}
My hypothesis turned out to be correct (I think).
Is this the same issue that is preventing me from receiving those events inside NSWindowController?
Is there another way I can achieve the same thing without creating more object references?
In order to post code, I use the Answer option even though it is more of a comment.
I added in NSViewController:
override func viewDidAppear() {
super.viewDidAppear()
parentWindowController = self.view.window!.windowController
self.view.window!.delegate = self.view.window!.windowController as! S1W2WC. // The NSWC class, which conforms to NSWindowDelegate
print(#function, "windowController", self.view.window!, self.view.window!.windowController)
}
I get print log:
viewDidAppear() windowController Optional()
and notification is passed.
But if I change to
override func viewDidAppear() {
super.viewDidAppear()
// parentWindowController = self.view.window!.windowController
self.view.window!.delegate = self.view.window!.windowController as! S1W2WC
print(#function, "windowController", self.view.window!, self.view.window!.windowController)
}
by commenting out parentWindowController, notification don't go anymore to the WindowController…
Edited: I declared in ViewController:
var parentWindowController: NSWindowController? // Helps keep a reference to the controller
The proposed solutions are, in my opinion, hacks that can cause serious problems with memory management by creating circular references. You definitely can make instances of NSWindowController work as the window’s delegate. The proper way is to wire it up correctly in either code or in Interface Builder in Xcode. An example of how to do it properly is offered here.
If the delegate methods are not called is because the wiring up is not done correctly.
Another thing that must be done in Swift is when you add the name of the NSWindowController subclass in Interface Builder in Xcode is to check the checkbox of Inherits from Module. If you fail to do this, none of your subclass methods will be called.

NSEvent leak for key down in macOS

In Xcode 10.1 with Swift 4.2 I'm having a memory leak when I add a local monitor for key down events in my NSViewController, that it is be instanced as minimal version (without nib and xib).
override func loadView() {
self.view = NSView()
self.view.wantsLayer = true
}
override func viewDidLoad(){
super.viewDidLoad
NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: handler)
}
lazy var handler:(NSEvent)->NSEvent? = { [ weak self ,unowned picker = picker] event in
picker.keyDown(with: event)
return event
}
This memory leak does not have much information:Memory leak
EDIT
In deinit method removeMonitor is called
deinit {
NSEvent.removeMonitor(self)
}
EDIT 2
Issue solved :
override func loadView() {
self.view = NSView()
self.view.wantsLayer = true
}
var monitor:Any? // This is essential
override func viewDidLoad(){
super.viewDidLoad
monitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: handler)
}
lazy var handler:(NSEvent)->NSEvent? = { [ weak self ,unowned picker = picker] event in
picker.keyDown(with: event)
return event
}
deinit {
NSEvent.removeMonitor(monitor)
}
From the Apple Docs;
Note
The monitor Block is called for all future events that match mask. You must call removeMonitor(_:) to stop the monitor. Under garbage collection, the monitor (and everything the Block references) will not be collected until removeMonitor(_:) is invoked.
Meaning that the monitor will continue to look for matching events until removeMonitor() is invoked. So your system is using extra memory to keep looking for events, and if you never call this - it could lead to a fairly large memory leak. As it says even with garbage collection, this object is still allocated - because it is looking for events that could take place at any time (so it is not guaranteed that this will be collected). Make sure you call this when you want the system to stop looking for events.
You could also do something like this in your handler.
You can return the event unmodified, create and return a new NSEvent object, or return nil to stop the dispatching of the event.

Swift Threading: When to use DispatchQueue.main.async?

I believe I understand what the dispatch queue is doing when I call it, but I'm not sure when exactly I should use it and what it's advantages are when I do use it.
If my understanding is correct, DispatchQueue.main.async { // code } will schedule the code contained within the closure to run on the main dispatch queue in an asynchronous manner. The main queue has the highest priority, and is typically reserved for updating UI to maximize App responsiveness.
Where I'm confused is: What exactly is the difference in updating UI elements within a dispatch queue closure versus just writing the code outside the closure in the same spot? Is it faster to execute the code in the body of a view did load method rather than sending it to the dispatch queue? If not, why?
Code Example:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
}
Versus:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.async {
updateUI()
}
}
}
Which one is will update the UI faster?
The primary use of DispatchQueue.main.async is when you have code running on a background queue and you need a specific block of code to be executed on the main queue.
In your code, viewDidLoad is already running on the main queue so there is little reason to use DispatchQueue.main.async.
But isn't necessarily wrong to use it. But it does change the order of execution.
Example without:
class MyViewController: UIViewController {
func updateUI() {
print("update")
}
override func viewDidLoad() {
super.viewDidLoad()
print("before")
updateUI()
print("after")
}
}
As one might expect, the output will be:
before
update
after
Now add DispatchQueue.main.async:
class MyViewController: UIViewController {
func updateUI() {
print("update")
}
override func viewDidLoad() {
super.viewDidLoad()
print("before")
DispatchQueue.main.async {
updateUI()
}
print("after")
}
}
And the output changes:
before
after
update
This is because the async closure is queued up to run after the current runloop completes.
I just ran into the exact situation discribed in your Question: viewDidLoad() calling DispatchQueue.main.async.
In my case I was wanting to modify Storyboard defaults prior to displaying a view.
But when I ran the app, the default Storyboard items were momentarily displayed. The animated segue would finish. And only THEN would the UI components be modified via the code in viewDidLoad(). So there was this annoying flash of all of the default storyboard values before the real values were edited in.
This was because I was modifying those controls via a helper function that always first dispatched to the main thread. That dispatch was too late to modify the controls prior to their first display.
So: modify Storyboard UI in viewDidLoad() without dispatching to the Main Thread. If you're already on the main thread, do the work there. Otherwise your eventual async dispatch may be too late.

AVMIDIPlayer leaks memory

I would like to play a MIDI file on iOS 8.2 with AVMIDIPlayer.
This does work indeed, but for a very short number of times: the memory seems never to be freed, and it is eventually filled.
Below is my code in Swift 1.2, reduced to a bare minimum: everything takes place in the ViewController, there are no other classes, no delegates, no error checking, etc.
The interface has just three buttons: NEW (instantiates a new AVMIDIPlayer and prerolls), PLAY, STOP.
This memory problem happens on the Simulator and on an actual device, and the bigger the soundfont and the midifile, the worse it is.
I noticed that RAM grows when creating a new AVMIDIPlayer instance AND when playing the midifile (in the latter case you can get even +4Mb/sec).
What am I missing?
Thanks for your help.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var mp:AVMIDIPlayer?
#IBAction func newButton(sender: AnyObject) {
mp = AVMIDIPlayer(contentsOfURL: NSBundle.mainBundle().URLForResource("ahVous", withExtension:"mid"),
soundBankURL: NSBundle.mainBundle().URLForResource("TimGM6mb", withExtension:"sf2"),
error: nil)
mp!.prepareToPlay()
}
#IBAction func playButton(sender: AnyObject) {
mp!.play(nil)
}
#IBAction func stopButton(sender: AnyObject) {
mp!.stop()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}