I have a problem with a #sum binding of a column in my program:
I'm doing a Coredata, NSPersistentDocument based program. I'm doing mostly everything from IB, the creation of the data model, NSArrayController and NSTableView...
I have just 1 entity with 62 attributes (61 NSString and 1 NSNumber). I import a CSV file with 12722 records. Import works well, can save to xml, binary, sqlite... I've double checked that the overall process works perfect. Can save/load. Everything is there.
The problem that I have: I've created a label that I BIND to #sum of the column with the NSNumber property. This is how I did
> label->Bindings Inspector->Value
> Bind to: My_Entity_NSArrayController
> Controller Key: selection
> Model Key Path: #sum.myNumericAttribute
When I run the program, click on Import, Select ALL the rows, the #sum works well. It's fast, however and here is the first problem: once I save the file (tried all... binary/xml/sqlite) and later load it and try to Select ALL again, the program crash without error.
Tried through "Profile"->Allocations. I noticed:
I don't have memory leaks
When loading from disk and then select all: Goes extremelly slow. After 5 minutes didn't yet finished (I stopped it) and I saw +45MB of CFNumber (Live Bytes) and >1.500.00#Overall. So, something is wrong here, as I'm talking about 12722 rows/registers of type Interger32.
The second problem is the same but reproduced from a different angle. Instead of using "selection" I've tried to use "arrangedObjects".
In this case the problem appears even while importing from CSV, it goes extremely slow and it finally crash. Trying to open an already created file also crash.
This is how I did label->Bindings Inspector->Value
> label->Bindings Inspector->Value
> Bind to: My_Entity_NSArrayController
> Controller Key: arrangedObjects
> Model Key Path: #sum.myNumericAttribute
Can you please help me with some light on what to look for or ideas that can help me find where the problem is?.
Thanks a lot.
Luis
---- NEW EDIT AFTER MORE RESEARCH ----
I've found a workaround which I DONT' UNDERSTAND, please comments/answers really appreciated.
My program uses Coredata (SQLite), NSPersistentDocument, NSTableView and an NSArrayController. I want to have a working NSTextField bound to a #sum Collection Operation
Problem: As soon as I open an existing document with SQLite DB populated and I try to bind to the arrangedObjects.#sum.t_24_Bookings from the NSWindowController, the program crash.
My initial guess it's related to the Cannot access contents of an object controller after a nib is loaded however I've followed the recommendation of performing a first Fetch like this without success:
- (void) awakeFromNib
{
:
BOOL ok = [[self out_CtEn_Transaction] fetchWithRequest:nil merge:NO error:&error];
:
Continuing with this idea I've found that if I create a "real" complete Fetch + I perform a #sum access from the Document subclass, then it works.
Here is the code with comments I've put in place in order to have the workaround working.
ABDocument interface (a NSPersistentDocument subclass)
#interface ABDocument : NSPersistentDocument {
BOOL ivNewDocument;
NSArray *ivFetchedTransactions;
NSNumber *ivTotalBookings;
}
#property (nonatomic, getter=isNewDocument) BOOL newDocument;
#property (nonatomic, retain) NSArray *fetchedTransactions;
#property (nonatomic, retain) NSNumber *totalBookings;
:
ABDocument implementation
#import "ABDocument.h"
#import "ABWindowController.h"
#implementation ABDocument
#synthesize newDocument = ivNewDocument;
#synthesize totalBookings = ivTotalBookings;
#synthesize fetchedTransactions = ivFetchedTransactions;
:
/** #brief Create one instance of my custom NSWindowController subclass (ABWindowController)
*
* In my NSPersistentDocument I do override makeWindowControllers, where I create
* one instance of my custom NSWindowController subclass and use addWindowController:
* to add it to the document.
*
*/
- (void) makeWindowControllers
{
// Existing Document?
if ( ![self isNewDocument]) {
// NSLog(#"%#:%# OPENING EXISTING DOCUMENT", [self class], NSStringFromSelector(_cmd));
// Opening existing document (also has an existing DDBB (SQLite)), so
// make sure I do perform a first complete "fetch + #sum" to void issues
// with my NIB bind's.
[self firstFetchPreventsProblems];
}
// Now I can create the Window Controller using my "MainWindow.xib".
ABWindowController *windowController = [[ABWindowController alloc] init];
[self addWindowController:windowController];
[windowController release];
}
/** #brief First complete "fetch + #sum" to void issues with my NIB bind's.
*
* Before I create the Window Controller with "MainWindow.xib" I have to perform a
* first Fetch AND also retrieve a #sum of an NSNumber column.
*
* My NIB has an NSTextField BOUND to #arrangedObjects.#sum.<property> through a NSArrayController
* If I don't call this method before the NIB is loaded, then the program will crash.
*
*/
- (void) firstFetchPreventsProblems {
// Prepare the Fetch
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Transaction"];
// 1) Perform the Fetch
NSError *error = nil;
[self setFetchedTransactions:[[self managedObjectContext ] executeFetchRequest:request error:&error]];
if ([self fetchedTransactions] == nil)
{
NSLog(#"Error while fetching\n%#",
([error localizedDescription] != nil) ? [error localizedDescription] : #"Unknown Error");
exit(1);
}
// 2) Execute Collection Operation #sum
[self setTotalBookings:[[self fetchedTransactions] valueForKeyPath:#"#sum.t_24_Bookings"]];
}
ABWindowController (The controller that loads my NIB)
- (void)windowDidLoad
{
:
// PROGRAM CRASH HERE
// IF [self firstFetchToPreventsProblems]; is NOT CALLED
// ABDocument's "makeWindowControllers:"
[[self totalSumField] bind: #"value" toObject: [self out_CtEn_Transaction]
withKeyPath:#"arrangedObjects.#sum.t_24_Bookings" options:nil];
}
Please If you can comment really appreciated, I've got a solution but I don't understand why.
Tanks,
Luis
I found the problem myself after several days researching. It was easy (now that I know):
In parallel I was creating a secondary thread and happened that I was accessing the data model from two different threads. As it's been explained in several Q&As here in Stackoverflow, it's very dangerous.
I've applied the commented solutions in several posts of creating a secondary MOC in the secondary thread.
Now my code is thread safe as per coredata related actions, so program is not crashing.
Thanks again to the community.
Luis
Related
I understand what the error is, but in this case not what is causing it. In general use it occurs maybe 1% of the time (probably less) but I have found an extreme way to cause it which I will describe below. First, I am using an in-app purchase process I found on Ray Wenderlich's site. Below are the specific pieces of concern here:
.h:
typedef void (^RequestProductsCompletionHandler)(BOOL success, NSArray * products);
#interface IAPHelper : NSObject
- (void)requestProductsWithCompletionHandler:RequestProductsCompletionHandler)completionHandler;
#end
.m
#implementation IAPHelper
{
SKProductsRequest * _productsRequest;
RequestProductsCompletionHandler _completionHandler;
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSLog(#"Loaded list of products...");
_productsRequest = nil;
NSArray * skProducts = response.products;
for (SKProduct * skProduct in skProducts) {
NSLog(#"Found product: %# %# %0.2f",
skProduct.productIdentifier,
skProduct.localizedTitle,
skProduct.price.floatValue);
}
_completionHandler(YES, skProducts); // here is where bad access occurs
_completionHandler = nil;
}
Again, 99%+ of the time this works just fine. Given how infrequent the bad access happens in regular use and it has been difficult to diagnose. However, I found an extreme way to cause the issue. The setup is "Tab 1" is a table view controller and "Tab 2" is a table view controller that uses the code from above. If I quickly switch back and forth between the two tabs I can usually cause the problem to occur anywhere from a few seconds into it to 20-30 seconds. Doesn't happen every time in this scenario but it does the vast majority. As marked above the following line gets a bad access error with Parent is Null.
_completionHandler(YES, skProducts);
To solve the issue I simple do the following:
if (_completionHandler)
{
_completionHandler(YES, skProducts);
_completionHandler = nil;
}
While that fix does work and does solve the problem I am still bothered by why this is occurring. Any thoughts as to the cause of this?
Update:
Apologies to all as I did forget to include the following in what I pasted above.
- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler {
// 1
_completionHandler = [completionHandler copy];
// 2
_productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers];
_productsRequest.delegate = self;
[_productsRequest start];
}
You need to treat your completion block as any other object when you are storing it. So if you are storing your block as a variable and then using it within a different scope from where you assign it, you need to increment the reference count by either copying it or retaining it. The simple solution is to create a strong property to store your block.
Depending on unseen bits of code, your completion handler block might not be being assigned correctly. You need to copy a block if you intend to use it outside of the scope in which it was created.
In your interface, declare your completion handler's storage attribute as "copy".
#property (nonatomic, readwrite, copy) void (^completionHandler)(BOOL, NSArray *);
If you want to control the local variable, you can synthesize the property manually in your implementation:
#synthesize completionHandler = _completionHandler;
First of all: I already searched on google and SO for solutions - none worked.
I've got an application which loads the artwork of the current iTunes track and displays it; this is stored in a NSImage instance, among some other variables, in a class:
#interface infoBundle : NSObject
#property (strong) NSImage *track_artwork;
#property (weak) NSString *track_title;
#property (weak) NSString *track_album;
#property (weak) NSString *track_artist;
#end
Then, an instance of this class is created:
-(infoBundle*)returnInfoBundle {
infoBundle* tmpBundle = [[infoBundle alloc]init];
tmpBundle.track_artwork = [[NSImage alloc]initWithData:[(iTunesArtwork *)[[[iTunes currentTrack] artworks] objectAtIndex:0] rawData]];
[...]
return tmpBundle;
}
And later used:
-(void)iTunesDidChange {
infoBundle* tmpBundle = [self returnInfoBundle];
[...]
[imageView setImage:tmpBundle.track_artwork];
}
That's eating up ~2MB (Cover size, I'd guess) per call of iTunesDidChange.
I already tried:
[tmpBundle autorelease];
[tmpBundle release];
[tmpBundle dealloc];
tmpBundle = nil;
and, after that didn't help:
- Enabling ARC.
=> Why is this eating up memory, although the object (tmpbundle) should get removed?
=> How may I achieve leak-less NSImage usage?
Thanks for any tips/suggestions/solutions :)
Issue
You will have a memory leak if you create your object on your method and not release it inside that method or you have to reference it when you pass it as a parameter by reference : Passing arguments by value or by reference in objective C
Your problem is that you are creating an instance of infoBundle two times, and when you are initializing another instance of it, you are leaving the first one without reference, so it remains in memory, and without connection to remove it (memory leak).
Solution
To make your things easier you should create an instance of your object
#implementation
{
infoBundle* tmpBundle;
}
Use it where ever you need it
-(infoBundle*)returnInfoBundle
{
tmpBundle = [[infoBundle alloc]init];
tmpBundle.track_artwork = [[NSImage alloc]initWithData:[(iTunesArtwork *)[[[iTunes currentTrack] artworks] objectAtIndex:0] rawData]];
[...]
return tmpBundle;
}
-(void)iTunesDidChange
{
tmpBundle = [self returnInfoBundle];
[...]
[imageView setImage:tmpBundle.track_artwork];
}
And when you are finished with that object dealloc will automatically release it if you add it to dealloc method:
- (void) dealloc
{
[tmpBundle release];
tmpBundle = nil;
}
Hope it helps! :)
Just modifiy this line :-
infoBundle* tmpBundle = [[[infoBundle alloc]init]autorelease];
I can’t tell from your code what you are doing in [imageView setImage:tmpbundle.track_artwork]; but you may be having the same problem I had.
I was using
self.imageToDisplay = [UIImage imageNamed:pictFileName];
and kept getting leaks. I switched to
self.imageToDisplay = [UIImage imageWithContentsOfFile:pictFile];
and they went away.
According to the documentation for imageNamed,
This method looks in the system caches for an image object with the
specified name and returns that object if it exists… If you have an
image file that will only be displayed once and wish to ensure that it
does not get added to the system’s cache, you should instead create
your image using imageWithContentsOfFile:. This will keep your
single-use image out of the system image cache, potentially improving
the memory use characteristics of your app.
It sounds like you have either the same or a similar issue.
I am writing an app which is a sort of dictionary - it presents the user with a list of terms, and when clicked on, pops up a dialog box containing the definition. The definition itself may also contain terms, which in turn the user can click on to launch another definition popup.
My main app is stored in 'myViewController.m'. It calls a custom UIView class, 'CustomUIView.m' to display the definition (this is the dialog box that pops up). This all works fine.
The text links from the CustomUIView then should be able to launch more definitions. When text is tapped in my CustomUIView, it launches another CustomUIView. The problem is, that this new CustomUIView doesn't have access to the hash map which contains all my dictionary's terms and definitions; this is only available to my main app, 'myViewController.m'.
Somehow, I need to make my hash map, dictionaryHashMap, visible to every instance of the CustomUIView class. dictionaryHashMap is created in myViewController.m when the app opens and doesn't change thereafter.
I don't wish to limit the number of CustomUIViews that can be opened at the same time (I have my reasons for doing this!), so it would be a little resource intensive to send a copy of the dictionaryHashMap to every instance of the CustomUIView. Presumably, the solution is to make dictionaryHashMap a global variable.
Some of my code:
From myViewController.m:
- (void)viewDidLoad
{
self.dictionaryHashMap = [[NSMutableDictionary alloc] init]; // initialise the dictionary hash map
//... {Code to populate dictionaryHashMap}
}
// Method to pop up a definition dialog
- (void)displayDefinition:(NSString *) term
{
NSArray* definition = [self.dictionaryHashMap objectForKey:term]; // get the definition that corresponds to the term
CustomUIView* definitionPopup = [[[CustomUIView alloc] init] autorelease]; // initialise a custom popup
[definitionPopup setTitle: term];
[definitionPopup setMessage: definition];
[definitionPopup show];
}
// Delegation for sending URL presses in CustomUIView to popupDefinition
#pragma mark - CustomUIViewDelegate
+ (void)termTextClickedOn:(CustomUIView *)customView didSelectTerm:(NSString *)term
{
myViewController *t = [[myViewController alloc] init]; // TODO: This instance has no idea what the NSDictionary is
[t displayDefinition:term];
}
From CustomUIView.m:
// Intercept clicks on links in UIWebView object
- (BOOL)webView: (UIWebView*)webView shouldStartLoadWithRequest: (NSURLRequest*)request navigationType: (UIWebViewNavigationType)navigationType {
if ( navigationType == UIWebViewNavigationTypeLinkClicked ) {
[myViewController termTextClickedOn:self didSelectTerm:request];
return NO;
}
return YES;
}
Any tips on how to make the dictionaryHashMap visible to CustomUIView would be much appreciated.
I have tried making the dictionaryHashMap global by doing the following:
Changing all instances of 'self.dictionaryHashMap' to 'dictionaryHashMap'
Adding the line 'extern NSMutableDictionary *dictionaryHashMap;' to CustomUIView.h
Adding the following outside of my implementation in myViewController.m: 'NSMutableDictionary *dictionaryHashMap = nil;'
However, the dictionaryHashMap remains invisible to CustomUIView. As far as I can tell, it actually remains a variable which is local to myViewController...
It's not resource-intensive to pass around the reference (pointer) to dictionaryHashMap. A pointer to an object is only 4 bytes. You could just pass it from your view controller to your view.
But I don't know why you even need to do that. Your view is sending a message (termTextClickedOn:didSelectTerm:) to the view controller when a term is clicked. And the view controller already has a reference to the dictionary, so it can handle the lookup. Why does the view also need a reference to the dictionary?
Anyway, if you want to make the dictionary a global, it would be more appropriate to initialize it in your app delegate, in application:didFinishLaunchingWithOptions:. You could even make the dictionary be a property of your app delegate and initialize it lazily.
UPDATE
I didn't notice until your comment that termTextClickedOn:didSelectTerm: is a class method. I assumed it was an instance method because myViewController starts with a lower-case letter, and the convention in iOS programming is that classes start with capital letters. (You make it easier to get good help when you follow the conventions!)
Here's what I'd recommend. First, rename myViewController to MyViewController (or better, DefinitionViewController).
Give it a property that references the dictionary. Whatever code creates a new instance of MyViewController is responsible for setting this property.
Give CustomUIView properties for a target and an action:
#property (nonatomic, weak) id target;
#property (nonatomic) SEL action;
Set those properties when you create the view:
- (void)displayDefinition:(NSString *)term {
NSArray* definition = [self.dictionaryHashMap objectForKey:term];
CustomUIView* definitionPopup = [[[CustomUIView alloc] init] autorelease]; // initialise a custom popup
definitionPopup.target = self;
definitionPopup.action = #selector(termWasClicked:);
...
In the view's webView:shouldStartLoadWithRequest: method, extract the term from the URL request and send it to the target/action:
- (BOOL)webView: (UIWebView*)webView shouldStartLoadWithRequest: (NSURLRequest*)request navigationType: (UIWebViewNavigationType)navigationType {
if ( navigationType == UIWebViewNavigationTypeLinkClicked ) {
NSString *term = termForURLRequest(request);
[self.target performSelector:self.action withObject:term];
return NO;
}
return YES;
}
In the view controller's termWasClicked: method, create the new view controller and set its dictionary property:
- (void)termWasClicked:(NSString *)term {
MyViewController *t = [[MyViewController alloc] init];
t.dictionary = self.dictionary;
[t displayDefinition:term];
}
Create a class that will be used as singleton. Example.
You Should always keep your data in separate class as the mvc pattern suggest and that could be achieved by using a singleton class for all your dictionary terms and accesing them from every custom view when needed.
I am trying to nest NSCollection view inside of one another. I have tried to create a new project using the Apple Quick Start Guide as a base.
I start by inserting a collection view into my nib, to the view that is automatically added I drag another collection view onto it. The sub-collection view added gets some labels. Here is a picture of my nib:
I then go back and build my models:
My second level model .h is
#interface BPG_PersonModel : NSObject
#property(retain, readwrite) NSString * name;
#property(retain, readwrite) NSString * occupation;
#end
My First level model .h is:
#interface BPG_MultiPersonModel : NSObject
#property(retain, readwrite) NSString * groupName;
#property(retain,readwrite) NSMutableArray *personModelArray;
-(NSMutableArray*)setupMultiPersonArray;
#end
I then write out the implementation to make some fake people within the first level controller(building up the second level model):
(edit) remove the awakefromnibcode
/*- (void)awakeFromNib {
BPG_PersonModel * pm1 = [[BPG_PersonModel alloc] init];
pm1.name = #"John Appleseed";
pm1.occupation = #"Doctor";
//similar code here for pm2,pm3
NSMutableArray * tempArray = [NSMutableArray arrayWithObjects:pm1, pm2, pm3, nil];
[self setPersonModelArray:tempArray];
} */
-(NSMutableArray*)setupMultiPersonArray{
BPG_PersonModel * pm1 = [[BPG_PersonModel alloc] init];
pm1.name = #"John Appleseed";
pm1.occupation = #"Doctor";
//similar code here for pm2,pm3
NSMutableArray * tempArray = [NSMutableArray arrayWithObjects:pm1, pm2, pm3, nil];
return tempArray;
}
Finally I do a similar implementation in my appdelegate to build the multiperson array
- (void)awakeFromNib {
self.multiPersonArray = [[NSMutableArray alloc] initWithCapacity:1];
BPG_MultiPersonModel * mpm1 = [[BPG_MultiPersonModel alloc] init];
mpm1.groupName = #"1st list";
mpm1.personModelArray = [mpm1 setupMultiPersonArray];
(I'm not including all the code here, let me know if it would be useful.)
I then bind everything as recommended by the quick start guide. I add two nsarraycontrollers with attributes added to bind each level of array controller to the controller object
I then bind collectionview to the array controller using content bound to arrangedobjects
Finally I bind the subviews:
with the grouptitle label to representedobject.grouptitle object in my model
then my name and occupation labels to their respective representedobjects
I made all the objects kvo compliant by including the necessary accessor methods
I then try to run this app and the first error I get is: NSCollectionView item prototype must not be nil.
(edit) after removing awakefromnib from the first level model I get this
Has anyone been successful at nesting nscollection views? What am I doing wrong here? Here is the complete project zipped up for others to test:
http://db.tt/WPMFuKsk
thanks for the help
EDITED:
I finally contacted apple technical support to see if they could help me out.
Response from them is:
Cocoa bindings will only go so far, until you need some extra code to make it all work.
When using arrays within arrays to populate your collection view the
bindings will not be transferred correctly to each replicated view
without subclassing NSCollectionView and overriding
newItemForRepresentedObject and instantiating the same xib yourself,
instead of using the view replication implementation provided by
NSCollectionView.
So in using the newItemForRepresentedObject approach, you need to
factor our your NSCollectionViewItems into separate xibs so that you
can pass down the subarray of people from the group collection view to
your inner collection view.
So for your grouped collection view your override looks like this:
- (NSCollectionViewItem *)newItemForRepresentedObject:(id)object
{
BPG_MultiPersonModel *model = object;
MyItemViewController *item = [[MyItemViewController alloc] initWithNibName:#"GroupPrototype" bundle:nil];
item.representedObject = object;
item.personModelArray = [[NSArrayController alloc] initWithContent:model.personModelArray];
return item;
}
And for your inner collection subclass your override looks like this:
- (NSCollectionViewItem *)newItemForRepresentedObject:(id)object
{
PersonViewController *item = [[PersonViewController alloc] initWithNibName:#"PersonPrototype" bundle:nil];
item.representedObject = object;
return item;
}
here is a sample project that they sent back to me -
http://db.tt/WPMFuKsk
I am still unable to get this to work with my own project. Can the project they sent back be simplified further?
Please take a closer look at this answer
Short answer:
Extracting each NSView into its own .xib should solves this issue.
Extended:
The IBOutlet’s specified in your NSCollectionViewItem subclass are not connected when the prototype is copied. So how do we connect the IBOutlet’s specified in our NSCollectionViewItem subclass to the controls in the view?
Interface Builder puts the custom NSView in the same nib as the NSCollectionView and NSCollectionViewItem. This is dumb. The solution is to move the NSView to its own nib and get the controller to load the view programmatically:
Move the NSView into its own nib (thus breaking the connection between the NSCollectionViewItem and NSView).
In I.B., change the Class Identity of File Owner to the NSCollectionViewItem subclass.
Connect the controls to the File Owner outlets.
Finally get the NSCollectionViewItem subclass to load the nib:
Usefull links:
how to create nscollectionview programatically from scratch
nscollectionview tips
attempt to nest an nscollectionview fails
nscollectionview redux
I have a property
#property (strong, nonatomic) NSArray *techniqueSetObjects;
It's exposed as a custom binding in +initialize
+ (void)initialize {
[self exposeBinding:#"techniqueSetObjects"];
}
In app delegate's applicationDidFinishLaunching: it gets bound to NSArrayController.arrangedObjects
[self.futureBoxesController bind:#"techniqueSetObjects" toObject:self.techqSetController withKeyPath:#"arrangedObjects" options:nil];
The array controller in question operates in Core Data Entity mode, it gets it's content from another array controller's selection (with path pointing to a relationship in master entity).
Then I have a custom setter for techniqueSetObjects property:
- (void)setTechniqueSetObjects:(NSArray *)newTechqSetObjects {
[self willChangeValueForKey:#"techniqueSetObjects"];
techniqueSetObjects = [newTechqSetObjects copy];
[self didChangeValueForKey:#"techniqueSetObjects"];
NSLog(#"New count: %ld", [techniqueSetObjects count]);
}
Most of the time everything works as expected and right after the application starts I see the following log lines:
New count: 0
New count: 2
But occasionally (with no apparent reason) setter gets called 3 times with the same new array, like this:
New count: 0
New count: 2
New count: 2
New count: 2
Not to say that causes a major performance hit or something, it just annoys me that I don't know what exactly is going on. Is that a normal behavior for bindings? I suspect that might be an early symptom of some other more serious problem that I'm not aware of yet.