I have a problem of memory allocation (not leak anyway).
My program has a custom Window with a custom View containing a TextField, an ImageView and a Shadow.
Let's say that every 1 second I programmatically update the value of the TextField using [myTextField setStringValue:#"actual string"].
Obviously, every time the TextField get changed, the view is redrawn.
If I look in Activity Monitor I see that every time the TextField is updated, and therefore the view is redrawn, the allocated memory increases. The ImageView isn't supposed to change.
If I comment the line with setStringValue, the program runs without increasing memory at all. (See Update 4.)
Note that Instruments does not report memory leak or unreleased object and View is autorelease'd.
What can cause this?
UPDATE
I post a simplified version of the actual code:
.h
CustomTextField *myTextField;
int level;
#interface Dummy : NSObject {
NSString *level_string;
NSTimer *timer;
}
#end
.m
#implementation Dummy
- (void)awakeFromNib
{
// ...
timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(changestring:)
userInfo:nil
repeats:YES];
// ...
}
-(void)changestring:(NSTimer *)timer
{
level++;
level_string = [[NSString alloc] initWithFormat:#"%i",level];
[myTextField setStringValue:level_string];
[level_string release];
}
Where CustomTextField is a NSTextField class.
I don't know if what I'm going to add is important, anyway the custom window, the custom view and the custom textfield are defined and init programmatically in the code and they are not instantiated in interface builder.
UPDATE 2
I was wrong! Even if I comment setStringValue the memory still increases..a lot less, but it still increases..
The strange fact is that Instruments, in any case, reports a size of "Living Object" which remains constant and no leak is reported.
What is happening?
UPDATE 3
I have just used the amazing heapshots feature of Instruments and this is the result.
The memory increase that I see in activity monitor (which is of the order of thousands of kilobytes after few minutes) where does come from?
UPDATE 4
I think I have found what is causing the problem but I'm not able to solve it.
The View has a TextField, an ImageView and a Shadow. To make them appearing correctly on screen without glitches I have added [view setWantsLayer:YES].
If I comment this line, the memory allocation problem is definitely solved.
Now, as long as I need to use that command, how can I do? Am I supposed to release something related with Core Animation? Note that the only one command related with Core Animation is the above one.
Have a look at session 311 - Advanced Memory Analysis with Instruments of WWDC10 session videos. How to download these videos can be found here.
It could be that you see abandoned memory or that there is some caching going on. What is happening if you trigger a low memory warning in the simulator? Goes the memory down?
Also try using the heapshot feature of Instruments: make a heapshot, update the string and show which objects have been created. In the session video mentioned above the procedure is demonstrated.
EDIT: What I forgot: if you cannot figure out what is going on and you can show this behavior of increasing memory allocation without releasing it if a low memory warning is triggered in a simple example application, file a bug at Apple's bugreporter sending in this sample project and an Instruments trace file to show what is going on.
EDIT2: I was wrong assuming that this problem was on Mac OS X. The same principles apply for a Mac OS X application as well. Memory management in Cocoa is almost the same on the iPhone and the Mac. The biggest difference is that on the Mac there is garbage collection available. Of course on the Mac there are no low memory warnings, so leave that out.
What setStringValue: in CustomTextField does? My guess is that you're retaining passed string without releasing old value in your setter.
Related
I'm updating an existing project which has worked fine for years to High Sierra. The view loads correctly and looks as it always has:
Then, after I open a popup and close it again, the view moves things around, flips things, and generally looks very crazy:
Notice the Info title drops to the bottom, the left side text items reverse order, and the date and dropdown text flip. Mouse interaction seems very scrambled as well when this happens.
I'm not sure where to even start with solving this one, anyone have any ideas?
This is using xcode 9 beta 5 and High Sierra Beta 6.
Update: This is also in xcode 9 GM and High Sierra GM Seed
Update
I have determined this happens when I call reloadData on the NSTableView that this view is within. So the question now seems to be how to refresh the table data without it going wonky.
It seems that reloadData and also reloadDataForRowIndexes:columnIndexes: both produce odd behavior in High Sierra. My way around this is as follows:
-(void)refreshAllCells {
NSMutableIndexSet* rowIndexes = [[NSMutableIndexSet alloc] init];
if (self.myTable.numberOfRows != 0)
[rowIndexes addIndexesInRange:NSMakeRange(0, (self.myTable.numberOfRows))];
[self.myTable removeRowsAtIndexes:rowIndexes withAnimation:NO];
[self.myTable insertRowsAtIndexes:rowIndexes withAnimation:NO];
}
If there is a better answer I would still love to see it.
For me, adding wantsUpdateLayer in my subclass fixed the problem.
override open var wantsUpdateLayer: Bool {
return true
}
In IB, tick the checkbox to use CALayers on your tableView's views. I toggled them on for all the view's in the tableView view hierarchy and all the flipping stopped!
Can't post an image as I don't have enough reputation but this link shows the IB checkbox:
I've encountered this bug as well and while implementing another answer I found the real reason for the bug due to an exception that now got raised:
In my case, reloadData was called again while reloadData was still being executed. This caused the rendering issues.
The "loop" was happening as a side-effect of calling tableView.makeView(withIdentifier: identifier, owner: self) in tableView(_:, viewFor:, row:). The owner: self caused awakeFromNib() of the controller getting called again which in turn triggered the reloadData(). In my setup, I was not (yet) using separate XIBs for the table cells but was using the cells that were setup inside the table view as visible Interface Builder.
So the solution was to change tableView.makeView(withIdentifier: identifier, owner: self) to tableView.makeView(withIdentifier: identifier, owner: nil) and also use separate XIBs for the table cells.
I'm seeing this as well in 10.13, when 10.12 had worked just fine. I was able to work around this problem by, oddly enough, re-adding the cell view's subviews in its [layout]:
- (void)layout{
if([self needsLayout]){
for(NSView* v in [[self subviews] copy]){
[self addSubview:v];
}
}
[super layout];
}
I experienced this as well. Further testing revealed that the contents were being drawn WHILE the tableview Data source was being updated. By eliminating a spurious update, I was able to eliminate the issue.
I reload the data in the draw method, however I have moved the data update code outside of that method. Not sure if this helps the OP, but it worked for me.
All of a sudden I received several mails from customers who complained about all table texts being shown vertically flipped. All of them use macOS Mojave.
After a lot of hacking, and trying the solutions detailed above (to no avail alas), I decided to disable the autoSave property of the NSTableView:
//Swift.print("Now setting autosaveName of primaryView to \(self.className).primary")
//primaryTableView.autosaveName = "\(self.className).primary"
//primaryTableView.autosaveTableColumns = true
Then the problem was instantly gone. But now my customers weren't able anymore to change and save the column widths and order. So, instead of doing it programatically with tableView.autosaveName = "autosavename"; I now set up the autoSave property in the Table view properties, and also checked 'Column information' in the Attributes Inspector (while showing xib file). And then it works just fine..
I use the currently latest version of Xcode (10.1 10B61), and the problems only surfaced when using macOS Mojava 10.14.0 - 10.14.2 . High Sierra and Sierra gave no trouble. Whether I compiled the project in High Sierra or Mojave made no difference.
So, I hope this helps for other people confronted with this bizarre problem. I think it's a bug in macOS Mojave.
Had the same problem, reproducible on High Sierra and Mojave. The issue was with NSOutlineView autosaveName, other solutions didn't help. However I needed this feature.
Moving adding/reloading data to "viewDidAppear()" instead of "viewDidLoad()" solved the problem.
Edited:
It happens only when:
autosave is present
items get loaded on viewDidLoad before sorting is applied
the same list of items gets re-loaded after sorting is applied
all of the above happens on viewDidLoad
If you try to access tableView.sortingDescriptors (or NSOutlineView) - it automatically applies sorting. If it happens before loading the data - it works ok.
Loading data on viewDidAppear still looks like a better option.
I don't know if this is a bug with AutoLayout or Xcode but I've realised that if I run my application on a device which isn't the same size as the device in my storyboard, the elements are adopting the incorrect size.
I.e. Selecting View as iPhone SE and running the application on an iPhone 7+ The size of my tableview is the width of an iPhone SE screen, this fixes itself when I reload the view and then it then adopts the width of the iPhone 7+ screen.
Just a note as well the table view doesn't have a fixed width and is pinned to the top, bottom, left and right with 0 spacing and also I'm changing the height of the cells programmatically in code if this could affect it at all as well.
Video: https://drive.google.com/open?id=0B0QLbDLfJn6_YzljUGg4RTUwaTg
Views that come from a xib (or storyboard) have their IB frame values when they are created, before they are added to the view hierarchy. So a case like you describe is probably that something is accessing a view loaded from a xib before it has become part of the layout process and resized.
This would also explain why it's fixed when you go away and come back. The first time, it got the values before they were final, but the second time the values are already final and correct.
Layout code called fromviewDidLoad() for view controllers, or awakeFromNib() or initWith(coder:) for views is the likely cause. Layout code called from viewDidLoad() is especially problematic because it was fine up to iPhone 5s, but would now cause this problem.
It's impossible to say what exactly the issue just from this. From the NSLog statements visible in the video, the issue is whatever code calls that "weather view width". That is being called too early in the layout process. It needs to go in viewWillAppear or viewWillLayoutSubviews to make sure the correct values are ready for whatever calculation is dependent on that. Hope this helps.
I can't seem to recreate this. I would recommend double checking for any updates for Xcode and the Developer tools (softwareupdate --install -a).
Incase you haven't, restart Xcode.
EDIT: Disregard this answer, please read Mike Sand's post.
Try self.view.layoutIfNeeded in viewDidLoad or in viewWillAppear
I've been working a photo editing app that takes pictures and passes it on to the
next view controller.
Of course I use AVSession to have the camera preview shown.
When the picture is taken the current view controller passes the image on to the view controller that has all the editing features.
I use modal segues to control transition between views.
The Problem is that when the segue happens there is a increase in memory by 4mb that never gets released.
Viewcontoller-->EditorViewcontoller
EdiorViewcontroller-->Viewcontroller
I also have a segue from the editor to camera,again there is a accumulation of memory.
A few times back and forth and the app crashes.
I can't post picture due to lack of reputation.
How can I clean the memory of the previous Viewcontroller and just have the current view controller running.
Things I've tried
*setting all the instances to nil.
*dismissing the view controller.
*setting the entire view controller to nil.
*profiled it.
*using #autorealease.
*spent hours on the internet searching for a solution.
I use this line of code to perform the segue.
[self performSegueWithIdentifier:#"effectsegue" sender:self];
I even tried using a weak self hoping it might help.
__weak ViewController *weakself=self;
[weakself performSegueWithIdentifier:#"effectsegue" sender:self];
When I profile it,it tells me these lines of code
NSData *jpegData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
and
[self performSegueWithIdentifier:#"effectsegue" sender:self];
are taking memory.
I would like some suggestions and idea as to what can be done to clear memory of previous view controller and free memory,any other alternates other than segues.
I've been working on this for weeks and this is really not allowing me to proceed forward.
Thanks in advance.
If you're using storyboards and ARC you can create a button inside EditorViewcontroller (that you'll use to go back to Viewcontroller) and link it to the following action on your EditorViewcontroller.m file:
- (IBAction)goBack:(UIButton *)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
This should release the memory used by EditorViewcontroller
I have a cocoa application that has a dozen scrollViews. I love the elasticity, especially in some cases where I'd actually put some kind of "Easter egg" (kinda like the apple logo in the books app. you scroll down, you see an apple logo.)
My problem is, that I need to limit the amount of exposed content beyond the actual content area. When I scroll with the magic mouse, especially, the elasticity causes the whole scroll content to disappear! Until you release the scroll, it moves back in.
Now, I would like to limit the elasticity to a specific margin. how?
NSScrollView manages a view which has a "canvas" bigger than what is/can be display at any one time. So if you want a different behaviour:
Check (void)setHorizontalScrollElasticity: but that doesn't quite do what you want. (you want to allow a fixed amount of elasticity)
Subclass NSScrollView to implement the behaviour you want.
Create your own class from scratch (well... Anything inheriting from NSResponder since you want to handle events).
For example, I once wrote a world-map program but needed the map to loop forever on the horizontal axis. I just manually managed the scrolling with a subclassed NSView. (don't have access to code currently)
Something to ponder about: I understand your reasons but just wanted to mention it. The behaviour should be expected by the user. If it looks like a button, it should act like out. Currently, scrollviews have the elasticity so that when they scroll via momentum (user is no longer touching), it doesn't stop suddenly once it reaches the end... which would be jarring for users.
Example
If subclassing NSScrollview, I would try overriding - (void)scrollWheel:(NSEvent *) and detect what are the bounds of the contentView and cap it at a certain value. Something around the lines of:
- (void)scrollWheel:(NSEvent *)event
{
[super scrollWheel:event];
if (self.contentView.bounds.origin.y > SomeConstant)
/* cap the value */
}
I'm a newbie in Mac Dev. I come from iPhone dev.
My question relates to non-modal windows management. It's quite different from the iPhone and it's memory management model.
Say for example, i have a preference window, i can use something like that to show the window:
-(IBAction)showPreferenceController:(id)sender {
if (!preferenceController) {
preferenceController = [[PreferenceController alloc]init];
}
[preferenceController showWindow:preferenceController];
}
But with this code, the window will stay in memory during app life because the window is never released.
To avoid that, I could also use the method described here :
stackoverflow.com/questions/1391260/who-owns-an-nswindowcontroller-in-standard-practice
Create in PreferenceController a + (id) sharedInstance and release the window using (void)windowWillClose:(NSNotification *)notification
I see many cocoa code samples where non-modal windows are never released.
For example here : http://www.mattballdesign.com/blog/2008/10/01/building-a-preferences-window/ : The preference panel and all the subviews are created in awakeFromNib and so will live in memory during all app life.
If you take for example Xcode app, there are a lot of non-modal windows :
- Global Find window (CMD+MAJ+F)
- App Info Panel
- Help Window
- ...
I suppose that these windows are released when they are closed to keep memory as low as possible.
I would like some advices to know the best way to manage non-modal windows in a cocoa app.
Keep in memory? Releasing asap?
I know a mac has a lot of memory compared to an iPhone but I also think it's not good to keep in memory objects we are not using.
Thanks.
Edited with Rob post :
I send -autorelease to the window and set my pointer to nil so I'll recreate the window later. This is similar to the technique you cite, though whether or not to use +sharedController for the Controller isn't related; you can do this whether you have a shared controller or not.
I don't how to do that without a singleton (+sharedController).
I explain what I mean with this example:
In the app Controller :
#interface AppController : NSObject <NSApplicationDelegate> {
Implementation :
-(IBAction)showPreferenceController:(id)sender {
if (!preferenceController) {
preferenceController = [[PreferenceController alloc]init];
}
[preferenceController showWindow:preferenceController];
}
In the preferences controller :
#interface PreferenceController : NSWindowController <NSWindowDelegate>
Implementation :
- (void)windowWillClose:(NSNotification *)notification {
[self autorelease];self=nil;
}
It will crash when i close and reopen after the window : preferenceController is released but not equal to nil. So I need to set preferenceController to nil when the window is closed.
There is no problem to do that with a singleton.
Without singleton, I should set appController as the delegate of Preference Window to be able to set preferenceController to nil when the window is closed. But i don't like that way.
Edited with Preston comments
I didn't say it but I want only one instance of my non-modal window even if we call -(IBAction)showPreferenceController:(id)sender several times.
That's why I test if preferenceController is equal to nil or not in appController.
So, I need to set preferenceController to nil in appController if we close the window.
So the solution would be :
In AppController, listening NSWindowWillCloseNotification:
- (void)windowWillClose:(NSNotification *)notification {
if ([notification object] == [preferenceController window]) {
[preferenceController autorelease];
preferenceController = nil;
}
}
Is it correct? Is this the only one solution? because it seems a little bit complicated just to manage my non modal window...
You're thinking in all the right ways here. It's not ok to leak memory just because you have a lot of it. That said, it is common not to release windows just because they close on Mac. You should generally hold onto them if you think they're going to be opened again to avoid the cost of reloading them.
There are two schools of thought on NSWindow: owned and unowned. I am of the "owned" school of thought. I generally give every window an owner that retains it with an ivar and releases it when appropriate. Generally that is the delegate, sometimes it's the app controller. In -windowShouldClose:, I send -autorelease to the window and set my pointer to nil so I'll recreate the window later. This is similar to the technique you cite, though whether or not to use +sharedController for the Controller isn't related; you can do this whether you have a shared controller or not.
The unowned school of thought is to use NSWindow's -setReleasedWhenClosed: so that it automatically releases itself when it closes. I believe several of the windows you reference do it this way when they are provided by the system, since there might not be a delegate. This can be useful in certain kinds of panels, but I would be careful with it as a general pattern. It's better to have explicit memory management in almost all cases.
If you check "Release When Closed" for your window in Interface Builder or set it programmatically, it will release itself. You can also have the window's delegate autorelease the window in the windowShouldClose: delegate method.
If you want to release the window controller when the window closes, have your window controller listen for its window's NSWindowWillCloseNotification and do a [self autorelease] in that method. I'm not a fan of creating a singleton preferences controller always sticking around that the user will, in practice, rarely interact with.