I'm having trouble with an NSTextview which should continously update with the contents of a log file. The app is a master-detail UI, the master view contains an array of "backup" objects, while the detail view contains an NSTabView with one of the tabs containing the NSTextview.
Basically I want something like a tail -f logfile putting it's output into the NSTextview. Instead of using NSTask etc., I went for binding the NSTextview's "Attributed String" to a property of my "backup" object (so I can set the font):
backup.m
- (NSAttributedString *)logContent
{
NSDictionary *attributes = #{NSFontAttributeName:[NSFont fontWithName:#"Monaco" size:12]};
NSString *str = [NSString stringWithContentsOfURL:theLogfile encoding:NSUTF8StringEncoding error:nil];
if (str) {
NSAttributedString *attrstr = [[NSAttributedString alloc] initWithString:str attributes:attributes];
return attrstr;
} else
return nil;
}
Then I hook an FSEventStream to the logfile which informs a callback everytime the logfile changes. Inside the callback, I manually inform listeners that the property has changed and scroll down the NSTextview:
backup.m
- (void)_fsEventsCallback:(NSArray *)eventPaths{
if ([eventPaths containsObject:theLogfile.path]){
[self willChangeValueForKey:#"logContent"];
[self didChangeValueForKey:#"logContent"];
[_myAppDel.logTextView scrollRangeToVisible:NSMakeRange([[_myAppDel.logTextView string] length], 0)];
}}
The actual remove is done via an NSNotification:
App Delegate.m
- (void)removeBackupObject:(NSNotification *)notification
{
if (notification.object) {
[self.backupsArrayController removeObject:notification.object];
}
}
This works and I like the code better than using an NSTask, but the app occasionally crashes with a strange error when I tell the NSArrayController to remove a "backup" object:
Crashed Thread: 5 Dispatch queue: com.apple.root.low-priority
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Application Specific Information:
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSSetM: 0x60000045f1a0> was mutated while being enumerated.'
terminating with uncaught exception of type NSException
abort() called
Application Specific Backtrace 1:
0 CoreFoundation 0x00007fff8aec425c __exceptionPreprocess + 172
1 libobjc.A.dylib 0x00007fff8a7a4e75 objc_exception_throw + 43
2 CoreFoundation 0x00007fff8aec3b64 __NSFastEnumerationMutationHandler + 164
3 Foundation 0x00007fff8d0e3f05 -[NSISEngine chooseOutgoingRowHeadForIncomingRowHead:] + 305
4 Foundation 0x00007fff8d0e1aa8 -[NSISEngine minimizeConstantInObjectiveRowWithHead:] + 114
5 Foundation 0x00007fff8d0e1623 -[NSISEngine optimize] + 147
6 Foundation 0x00007fff8d0e851d -[NSISEngine constraintDidChangeSuchThatMarker:shouldBeReplacedByMarkerPlusDelta:] + 296
7 Foundation 0x00007fff8d0e839e -[NSISEngine tryToChangeConstraintSuchThatMarker:isReplacedByMarkerPlusDelta:undoHandler:] + 420
8 Foundation 0x00007fff8d0d3798 -[NSLayoutConstraint _tryToChangeContainerGeometryWithUndoHandler:] + 462
9 Foundation 0x00007fff8d0d31b3 -[NSLayoutConstraint _setSymbolicConstant:constant:] + 402
10 AppKit 0x00007fff8e2ac4ba -[NSView(NSConstraintBasedLayout) _autoresizingConstraints_frameDidChange] + 247
11 AppKit 0x00007fff8e2ab25f -[NSView setFrameOrigin:] + 901
12 AppKit 0x00007fff8e2b51b6 -[NSView setFrame:] + 259
13 AppKit 0x00007fff8e682c2f -[NSClipView _updateOverhangSubviewsIfNeeded] + 739
14 AppKit 0x00007fff8e2e80a1 -[NSClipView _scrollTo:animateScroll:flashScrollerKnobs:] + 1984
15 AppKit 0x00007fff8e2e76ff -[NSClipView _reflectDocumentViewFrameChange] + 128
16 AppKit 0x00007fff8e2ac0ac -[NSView _postFrameChangeNotification] + 203
17 AppKit 0x00007fff8e2b5852 -[NSView setFrameSize:] + 1586
18 AppKit 0x00007fff8e447bac -[NSTextView(NSPrivate) _setFrameSize:forceScroll:] + 764
19 AppKit 0x00007fff8e3b222f -[NSTextView setConstrainedFrameSize:] + 633
20 AppKit 0x00007fff8e443f70 -[NSLayoutManager(NSPrivate) _resizeTextViewForTextContainer:] + 1025
21 AppKit 0x00007fff8e35133e -[NSLayoutManager(NSPrivate) _recalculateUsageForTextContainerAtIndex:] + 2636
22 AppKit 0x00007fff8e343fb1 _enableTextViewResizing + 211
23 AppKit 0x00007fff8e34a6ef -[NSLayoutManager textStorage:edited:range:changeInLength:invalidatedRange:] + 557
24 AppKit 0x00007fff8e34a4aa -[NSTextStorage _notifyEdited:range:changeInLength:invalidatedRange:] + 149
25 AppKit 0x00007fff8e451a2c -[NSTextStorage processEditing] + 200
26 AppKit 0x00007fff8e44d832 -[NSTextStorage endEditing] + 110
27 Foundation 0x00007fff8d10b434 -[NSMutableAttributedString removeAttribute:range:] + 219
28 AppKit 0x00007fff8e4ca2c1 -[NSTextView setTextColor:] + 156
29 AppKit 0x00007fff8ea19baf -[_NSTextPlugin showValue:inObject:] + 128
30 AppKit 0x00007fff8e314797 -[NSValueBinder _adjustObject:mode:observedController:observedKeyPath:context:editableState:adjustState:] + 846
31 AppKit 0x00007fff8e3143aa -[NSValueBinder _observeValueForKeyPath:ofObject:context:] + 282
32 AppKit 0x00007fff8e314215 -[NSTextValueBinder _observeValueForKeyPath:ofObject:context:] + 43
33 Foundation 0x00007fff8d09af28 NSKeyValueNotifyObserver + 387
34 Foundation 0x00007fff8d0d7ed1 -[NSObject(NSKeyValueObservingPrivate) _notifyObserversForKeyPath:change:] + 1115
35 AppKit 0x00007fff8e306d88 -[NSController _notifyObserversForKeyPath:change:] + 209
36 AppKit 0x00007fff8e4385ff -[NSArrayController didChangeValuesForArrangedKeys:objectKeys:indexKeys:] + 125
37 AppKit 0x00007fff8e62179f -[NSArrayController _removeObjectsAtArrangedObjectIndexes:contentIndexes:objectHandler:] + 724
38 AppKit 0x00007fff8e621d1f -[NSArrayController _removeObjects:objectHandler:] + 502
Before I go into debugging what's going wrong, or implement the NSTask / tail -f approach, I'd like to know:
Are there are more elegant solutions to this problem?
This is an unsynchronized access issue. The notification is executing on one thread and the fsevent callback on another and both of them are accessing the ArrayController's underlying array and the textview at the same time.
Option 1 - Quick and dirty fix
Synchronize access accross threads. This done by acquiring a lock on the particular resource being accessed: the executing thread is gets the lock and all threads that attempt to acess that resource will be blocked until the locking thread releases the lock. More info can be found in the Threadding programming guide
Your code thus becomes:
- (void)_fsEventsCallback:(NSArray *)eventPaths{
if ([eventPaths containsObject:theLogfile.path])
#synchronized(self.logContent) {
[self willChangeValueForKey:#"logContent"];
[self didChangeValueForKey:#"logContent"];
}
#synchronized(_myAppDel.logTextView.string) {
[_myAppDel.logTextView scrollRangeToVisible:NSMakeRange([[_myAppDel.logTextView string] length], 0)];
}
}
}
- (void)removeBackupObject:(NSNotification *)notification
{
if (notification.object) {
#synchronized(self.backupsArrayController) {
[self.backupsArrayController removeObject:notification.object];
}
}
}
This will most likely solve your immediate problem, but however, IT IS A CHEAP AND DIRTY FIX and will effectively make your application's threads wait for each other every time.
Option 2 - The better way
Always update your ui on the main thread and do actual work on secondary threads.
The FSEvents callback is called on a secondary thread, the NSNotification that you are posting is responded to on another secondary thread and all of them operate on objects that are not really thread safe. Generally, NSMutable* objects are thread safe on access but not on mutations. In other words, if you're altering their contents, you'd better pay attention whos is doing what and when. :)
More info on which Cocoa Objects are thread safe and which are not can be found here in the Thread Safety section in the document I referred above. (That is quite a good piece of reading btw)
The idea is to tell the app to update the interface on the main thread, like so:
- (void)_fsEventsCallback:(NSArray *)eventPaths{
if ([eventPaths containsObject:theLogfile.path]){
[self willChangeValueForKey:#"logContent"];
[self didChangeValueForKey:#"logContent"];
[[NSApp delegate] performSelectorOnMainThread:#selector(scrollToWhereWeNeedTo) withObject:nil];
}}
AppDelegate.m
- (void)scrollToWhereWeNeedTo
{
[self.logTextView scrollRangeToVisible:NSMakeRange([[self.logTextView string] length], 0)];
}
- (void)removeBackupObject:(NSNotification *)notification
{
if (notification.object) {
[[NSApp delegate] performSelectorOnMainThread:#selector(removeObjectFromArrayController) withObject:notification.object];
}
}
- (void)removeObjectFromArrayController:(id)theObject
{
[self.backupsArrayController removeObject:theObject];
}
What you are effectively doing here is you are scheduling the scroll operation and the removing object operations on the main thread's runloop, thus eliminating any potential access conflict, because they will be in a queue, one after the other.
Also, please look at any other potential places in your app where access conflicts could happen.
I really hope this helps and does not confuse you even further. Cocoa can be a pain at first but hey, what doesn't kill you makes you stronger!
I solved the problem with the help of Cătălin Stan by executing the removeObject: call on the main thread like so:
- (void)removeBackupObject:(NSNotification *)notification
{
if (notification.object) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.backupsArrayController removeObject:notification.object];
});
}
}
Related
I got crash (probably while parent moc saved:
this is how moc's created (parent):
if (_managedObjectContextInMemoryForDynamicInformation) return _managedObjectContextInMemoryForDynamicInformation;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinatorInMemoryForDynamicInformation];
if (!coordinator) {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setValue:#"Failed to initialize the store" forKey:NSLocalizedDescriptionKey];
[dict setValue:#"There was an error building up the data file." forKey:NSLocalizedFailureReasonErrorKey];
NSError *error = [NSError errorWithDomain:#"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
[[NSApplication sharedApplication] presentError:error];
return nil;
}
_managedObjectContextInMemoryForDynamicInformation = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContextInMemoryForDynamicInformation setPersistentStoreCoordinator:coordinator];
[_managedObjectContextInMemoryForDynamicInformation setUndoManager:nil];
return _managedObjectContextInMemoryForDynamicInformation;
child:
AppDelegate *delegateMain = (AppDelegate *)[[NSApplication sharedApplication] delegate];
_mocInMemoryForDynamicInformation = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_mocInMemoryForDynamicInformation setUndoManager:nil];
_mocInMemoryForDynamicInformation.parentContext = delegateMain.managedObjectContextInMemoryForDynamicInformation;
this is how i save:
-(void) finalSaveMocInMemoryForDynamicInformation;
{
[_mocInMemoryForDynamicInformation performBlock:^{
// do something that takes some time asynchronously using the temp context
// push to parent
NSError *error;
if ([_mocInMemoryForDynamicInformation save:&error])
{
AppDelegate *delegateMain = (AppDelegate *)[[NSApplication sharedApplication] delegate];
// save parent to disk asynchronously
[delegateMain.managedObjectContextInMemoryForDynamicInformation performBlock:^{
#try {
NSError *error;
if (![delegateMain.managedObjectContextInMemoryForDynamicInformation save:&error])
{
[self logError:error];
}
}
#catch (NSException *exception) {
NSLog(#"ERROR:%#:%# exception->%# inside managedObjectContextInMemoryForDynamicInformation",[self class], NSStringFromSelector(_cmd),exception);
//[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:moc];
return ;
}
}];
} else [self logError:error];
}];
}
Application Specific Information:
* Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a
fault for '0x7fd8a71d87a0
''
abort() called terminate called throwing an exception
Application Specific Backtrace 1: 0 CoreFoundation
0x00007fff86e6f0a6 __exceptionPreprocess + 198 1 libobjc.A.dylib
0x00007fff849353f0 objc_exception_throw + 43 2 CoreData
0x00007fff8737eec4 _PFFaultHandlerLookupRow + 1348 3 CoreData
0x00007fff8737e61f _PF_FulfillDeferredFault + 239 4 CoreData
0x00007fff873892cd _PF_ManagedObject_WillChangeValueForKeyIndex + 77 5
CoreData 0x00007fff8738cc51
_sharedIMPL_setvfk_core + 129 6 CoreData 0x00007fff873a4eaa -[NSManagedObject(_NSInternalMethods)
_didChangeValue:forRelationship:named:withInverse:] + 1434 7 Foundation 0x00007fff878fe860
NSKeyValueNotifyObserver + 390 8 Foundation
0x00007fff87900279 NSKeyValueDidChange + 456 9 Foundation
0x00007fff878bb7df -[NSObject(NSKeyValueObserverNotification)
didChangeValueForKey:] + 130 10 CoreData
0x00007fff873898c1 -[NSManagedObject didChangeValueForKey:] + 113 11
CoreData 0x00007fff873e566d
-[NSManagedObject(_NSInternalMethods) _updateFromRefreshSnapshot:includingTransients:] + 685 12 CoreData 0x00007fff873b0969 -[NSManagedObjectContext(_NestedContextSupport)
_copyChildObject:toParentObject:fromChildContext:] + 121 13 CoreData 0x00007fff873b0505 -[NSManagedObjectContext(_NestedContextSupport)
_parentProcessSaveRequest:inContext:error:] + 629 14 CoreData 0x00007fff873df72a __82-[NSManagedObjectContext(_NestedContextSupport)
executeRequest:withContext:error:]_block_invoke_0 + 634 15
libdispatch.dylib 0x00007fff8aad6a2d
_dispatch_barrier_sync_f_slow_invoke + 77 16 libdispatch.dylib 0x00007fff8aad10b6 _dispatch_client_callout + 8 17 libdispatch.dylib
0x00007fff8aad60c8 _dispatch_main_queue_callback_4CF + 275 18
CoreFoundation 0x00007fff86e110fe __CFRunLoopRun
+ 1614 19 CoreFoundation 0x00007fff86e106b2 CFRunLoopRunSpecific + 290 20 HIToolbox
0x00007fff89d950a4 RunCurrentEventLoopInMode + 209 21 HIToolbox
0x00007fff89d94e42 ReceiveNextEventCommon + 356 22 HIToolbox
0x00007fff89d94cd3 BlockUntilNextEventMatchingListInMode + 62 23
AppKit 0x00007fff8ffcd613 _DPSNextEvent +
685 24 AppKit 0x00007fff8ffcced2
-[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 128 25 AppKit 0x00007fff8ffc4283
-[NSApplication run] + 517 26 AppKit 0x00007fff8ff68cb6 NSApplicationMain + 869 27 callsfreecall
0x000000010e6ab472 main + 34 28 libdyld.dylib
0x00007fff858a47e1 start + 0 29 ???
0x0000000000000002 0x0 + 2
Your var identifiers are really confusing. Names like parentContext, child Context, rootContext and so on are to tricky?
Anyway, the problem is probably not the save operation. On a first view it looks like you changed a relationship of an object to another object, but the other object does not exist any more. This can happen, if the other object is faulted into the child context and deleted in the store. (Probably because the parent context deleted the object and saved its state.)
For now, I found solution of that crash.
A crash was passed in that code:
[_mocInMemoryForDynamicInformation obtainPermanentIDsForObjects:#[session] error:&error];
This is know bug in apple, where I can't get permanent Id. If I try to do it on child context, save is product that crash.
This is how I solve it:
[_mocInMemoryForDynamicInformation.parentContext obtainPermanentIDsForObjects:#[session] error:&error];
This simple solution solve all issues (I can't explain how but this is work).
I have a standard NSTableView in Cell based content mode, it's dataSource is an array controller attached to my AppDelegate's managedObjectContext. In the delegate, this code deletes objects on a background thread (the db can have 30k+ objects so it's good to have a responsive UI and a progress bar and a cancel option):
-(void)deleteObjects:(NSArray*)objs completionBlock:(void (^)(void))aBlock
{
[self showCancelButton:YES];
__block __typeof__(self) blockself = self;
dispatch_group_t dispatchGroup = dispatch_group_create();
dispatch_group_async(dispatchGroup, dispatch_get_global_queue(0, 0), ^{
NSManagedObjectContext *mainContext = [(AppDelegate*)[[NSApplication sharedApplication] delegate] managedObjectContext];
[objs enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[mainContext deleteObject:obj];
if ((idx % saveSpace == 0) && (idx > 0)) {
dispatch_sync(dispatch_get_main_queue(), ^{
NSError *error;
[blockself.managedObjectContext save:&error];
theProgressOverlay.filesCount -= saveSpace; //
});
}
if (isCancelled) {
*stop = TRUE;
}
}];
NSError *error;
[mainContext save:&error];
NSLog(#"delete error: %#",error);
if (aBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
[self showCancelButton:NO];
aBlock();
});
}
});
}
this method is called by the code:
NSFetchRequest *toDelFetch = [NSFetchRequest fetchRequestWithEntityName:entity];
[toDelFetch setIncludesPropertyValues:NO];
NSError *error;
NSArray *toDelObjs = [self.managedObjectContext executeFetchRequest:toDelFetch error:&error];
NSLog(#"Error: %#",error);
if (!toDelObjs || toDelObjs.count < 1) {
return FALSE;
}
[self deleteObjects:toDelObjs completionBlock:{NSLog(#"Finished");}];
It all seems to work fine: the UI's responsive and the cancel button always works but every so often I get the error:
2012-12-13 19:31:53.352 QS[1399:403] *** Assertion failure in -[NSTableRowData _addRowViewForVisibleRow:withPriorView:], /SourceCache/AppKit/AppKit-1138.51/TableView.subproj/NSTableRowData.m:2484
2012-12-13 19:31:53.353 QS[1399:403] Row 1 should be in the valid visible section
2012-12-13 19:31:53.358 QS[1399:403] (
0 CoreFoundation 0x00007fff8b773f56 __exceptionPreprocess + 198
1 libobjc.A.dylib 0x00007fff88715d5e objc_exception_throw + 43
2 CoreFoundation 0x00007fff8b773d8a +[NSException raise:format:arguments:] + 106
3 Foundation 0x00007fff8db1a71f -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 169
4 AppKit 0x00007fff917c6003 -[NSTableRowData _addRowViewForVisibleRow:withPriorView:] + 164
5 AppKit 0x00007fff917c5ef2 -[NSTableRowData _addRowViewForVisibleRow:withPriorRowIndex:inDictionary:withRowAnimation:] + 184
6 AppKit 0x00007fff917c5e38 -[NSTableRowData _addRowViewForVisibleRow:] + 38
7 AppKit 0x00007fff917c557c -[NSTableRowData _unsafeUpdateVisibleRowEntries] + 448
8 AppKit 0x00007fff917c5397 -[NSTableRowData updateVisibleRowViews] + 95
9 AppKit 0x00007fff9175d854 -[NSTableView viewWillDraw] + 156
10 AppKit 0x00007fff916c1f08 -[NSView viewWillDraw] + 666
11 AppKit 0x00007fff916c1f08 -[NSView viewWillDraw] + 666
12 AppKit 0x00007fff916c2796 -[NSScrollView viewWillDraw] + 43
13 AppKit 0x00007fff916c1f08 -[NSView viewWillDraw] + 666
14 AppKit 0x00007fff916c1f08 -[NSView viewWillDraw] + 666
15 AppKit 0x00007fff916c0c4d -[NSView _sendViewWillDrawInRect:clipRootView:suppressRecursion:] + 1358
16 AppKit 0x00007fff916bf9b8 -[NSView displayIfNeeded] + 1039
17 AppKit 0x00007fff916bf375 _handleWindowNeedsDisplayOrLayoutOrUpdateConstraints + 648
18 CoreFoundation 0x00007fff8b7338e7 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
19 CoreFoundation 0x00007fff8b733846 __CFRunLoopDoObservers + 374
20 CoreFoundation 0x00007fff8b708af9 __CFRunLoopRun + 825
21 CoreFoundation 0x00007fff8b708486 CFRunLoopRunSpecific + 230
22 HIToolbox 0x00007fff8cb352bf RunCurrentEventLoopInMode + 277
23 HIToolbox 0x00007fff8cb3c4bf ReceiveNextEventCommon + 181
24 HIToolbox 0x00007fff8cb3c3fa BlockUntilNextEventMatchingListInMode + 62
25 AppKit 0x00007fff91683779 _DPSNextEvent + 659
26 AppKit 0x00007fff9168307d -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 135
27 AppKit 0x00007fff9167f9b9 -[NSApplication run] + 470
28 AppKit 0x00007fff918fbeac NSApplicationMain + 867
29 QuickSlide 0x00000001000019c2 main + 34
30 QuickSlide 0x0000000100001994 start + 52
A search with our dear friend, Google, shows a number of blogs stating that Row 1 should be in the valid visible section is a bug when the NSTableView is has -reloadData called 'too early', i.e. during -awakeFromNib. But that's not the case for me - deletions definitely happen after the app's fully awake. I've also commented out the showCancelButton: to make sure it's not my custom progress bar and tried
[contentArrayController setAutomaticallyRearrangesObjects:NO];
Can anyone tell me if there's a cunning trick to avoid this assert? Or even if it's matters? The app seems to continue ok but one of the blogs I saw said it was storing up trouble for later but didn't elaborate. The table does seem to refresh itself more slowly after it's thrown up the error.
I think I've figured it out. After about a week, I haven't had the problem... S*d's Law means that it will probably all kick off again now I've answered my own question.
Anyway, I was using the delegate methods:
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
and
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
The table seemed to be getting populated as I wanted via an NSArray which was cached with NSManagedObjects from my CD DB.
I was getting the error mostly when deleting objects (despite as I thought, updating the array cache with an NSFetchRequest).
So in a clutching at straws kind of way, I decided to sub-class the NSTextFieldCell in each table column and bind the NSTableColumns to an array controller in my main nib file. This went surprisingly well and has added benefits regarding tooltips when hovering over the table rows. Anyway, touching more wood than is right to say, it all appears to have calmed down now..... I'll update this answer if that changes any time.
UPDATE: it seems that NSTableView and/or multithreading doesn't sit well with the UI api. I found that on returning from background threads things like NSDrawers and sheets won't animate - they ignore their standard methods like -orderOut and -toggle. Using the above and an NSView with delayed animation (perform selector after delay) seems to have completely settled the app down.
I want to add some array elements to the menubar cocoa desktop application
here is my code with error
NSMenu *menu =[[NSMenu alloc]initWithTitle:#"menu"];
NSMenuItem *itemTest = [[NSMenuItem alloc] initWithTitle:#"Test" action:#selector(menuActionTest:) keyEquivalent:#"t"];
NSMenuItem *itemQuit = [[NSMenuItem alloc] initWithTitle:#"Quit App" action:#selector(menuActionQuit:) keyEquivalent:#"q"];
// add to menu
[menu addItem:itemTest];
[menu addItem:[NSMenuItem separatorItem]];
// handle the array, and here is the problem
NSArray* someList = [self getArray];
if ([someList count]>0) {
for(NSString *title in someList) {
NSLog(#"begin of add %#",title);
// here is the problem code
[menu addItem: [[NSMenuItem alloc] initWithTitle:title action:#selector(menuActionHelloWorld:) keyEquivalent:#""]];
NSLog(#"end of add %#",title);
}
[someList release];
[menu addItem:[NSMenuItem separatorItem]];
}
// add other to menu
[menu addItem:itemQuit];
//Define status bar-------------
NSStatusBar *bar = [NSStatusBar systemStatusBar];
NSStatusItem *statusItem = [[bar statusItemWithLength:NSVariableStatusItemLength]retain];
NSImage *menuImage = [NSImage imageNamed:#"status_off.png"];
[menuImage setTemplate:YES];
[statusItem setImage:menuImage];
[statusItem setHighlightMode:YES];
[statusItem setMenu:menu];
//Release----------------------
[itemTest release];
[itemQuit release];
[menu release];
when I use
[menu addItem: [[NSMenuItem alloc] initWithTitle:title action:#selector(menuActionHelloWorld:) keyEquivalent:#""]];
the application can build well, but when I click the status icon in the menubar, I got the error message
2012-02-24 16:20:38.393 MyApp[1546:503] -[__NSCFNumber length]: unrecognized selector sent to instance 0x202ecc3
2012-02-24 16:20:38.394 MyApp[1546:503] -[__NSCFNumber length]: unrecognized selector sent to instance 0x202ecc3
2012-02-24 16:20:38.398 MyApp[1546:503] (
0 CoreFoundation 0x00007fff85ebbfc6 __exceptionPreprocess + 198
1 libobjc.A.dylib 0x00007fff86c1cd5e objc_exception_throw + 43
2 CoreFoundation 0x00007fff85f482ae -[NSObject doesNotRecognizeSelector:] + 190
3 CoreFoundation 0x00007fff85ea8e73 ___forwarding___ + 371
4 CoreFoundation 0x00007fff85ea8c88 _CF_forwarding_prep_0 + 232
5 CoreFoundation 0x00007fff85e1f616 CFStringGetLength + 118
6 HIToolbox 0x00007fff8cb95d07 _Z12CheckForDashP8MenuDatathPKhPK10__CFString + 100
7 HIToolbox 0x00007fff8cb95c84 _Z26CleanupAfter1ItemInsertionP8MenuDatatPKhPK10__CFStringj + 41
8 HIToolbox 0x00007fff8cb95823 _Z31_InsertMenuItemTextWithCFStringP8MenuDataPK10__CFStringtjj + 204
9 AppKit 0x00007fff84ff8cfd -[NSCarbonMenuImpl _carbonMenuInsertItem:atCarbonIndex:] + 499
10 AppKit 0x00007fff8513c1a2 -[NSCarbonMenuImpl _privatePopulateCarbonMenu] + 298
11 AppKit 0x00007fff851c17a5 -[NSCarbonMenuImpl _populatePrivatelyIfNecessary] + 70
12 AppKit 0x00007fff851c174b -[NSCarbonMenuImpl _checkoutMenuRefWithToken:creating:populating:] + 298
13 AppKit 0x00007fff852edd46 -[NSCarbonMenuImpl _maximumSizeForScreen:] + 64
14 AppKit 0x00007fff8545f72b -[NSMenu size] + 35
15 AppKit 0x00007fff855517df +[NSStatusBarButtonCell popupStatusBarMenu:inRect:ofView:withEvent:] + 422
16 AppKit 0x00007fff85551b85 -[NSStatusBarButtonCell trackMouse:inRect:ofView:untilMouseUp:] + 147
17 AppKit 0x00007fff850d2bde -[NSControl mouseDown:] + 786
18 AppKit 0x00007fff8509d6e0 -[NSWindow sendEvent:] + 6306
19 AppKit 0x00007fff85552598 -[NSStatusBarWindow sendEvent:] + 66
20 AppKit 0x00007fff8503616d -[NSApplication sendEvent:] + 5593
21 AppKit 0x00007fff84fcc1f2 -[NSApplication run] + 555
22 AppKit 0x00007fff8524ab88 NSApplicationMain + 867
23 RandomApp 0x00000001067390e2 main + 34
24 RandomApp 0x00000001067390b4 start + 52
I am a newbie to Cocoa, I try to solve this problem by myself.
I find
[__NSCFNumber length]
so I think the problem occurs from this line
NSStatusItem *statusItem = [[bar statusItemWithLength:NSVariableStatusItemLength]retain];
This line is copied from apple example :) It Makes the status item length dynamic, adjusting to the width of its contents. NSStatusBar Class Reference
I think I set a variable length NSString as title, so the application can not decide how much its length value should be.
To prove that, I write my code as
[menu addItem: [[NSMenuItem alloc] initWithTitle:#"HelloWorld" action:#selector(menuActionHelloWorld:) keyEquivalent:#""]];
it works well.
But I really need to get the title dynamically.
So, how can I fix my code?
Thanks!
edit 1:
thanks
I add
NSString *newTitle = [NSString stringWithFormat:#"%d",title];
to my code, it works.
So I think the problem becomes my method getArray
here is some codes in my getArray method
NSMutableArray *List = [[NSMutableArray alloc] init];
for ( some conditions) {
// get something from a JSON result
NSString *title = [object valueForKeyPath:#"id"];
[List addObject:title];
}
return List;
To my point of view, the List is an array of lots of NSString elements, but I think I am wrong. what should I do to improve my code? To much better?
Sounds to me like you're accidentally passing an NSNumber in as your title. (I'm guessing this because code works fine when adding HelloWorlds, and length is a commonly-used method on NSString.)
I'd need to see your implementation of getArray to know exactly what went wrong, but I bet that knowing you've got an NSNumber in there by accident will be enough for you to solve it. (By the way, it's Cocoa-stylish to name your accessors simply array, not getArray.)
One of our customers is seeing a crash like this sometimes when pasting:
0 com.apple.Foundation 0x9143bd1d readPointerAt + 9
1 com.apple.Foundation 0x9153221f empty + 43
2 com.apple.Foundation 0x9145d41f dealloc + 21
3 com.apple.Foundation 0x9145d3ce -[NSConcreteMapTable dealloc] + 35
4 com.apple.AppKit 0x9092aa5d -[_NSPasteboardOwnersCollection dealloc] + 45
5 com.apple.AppKit 0x905cdb8f _NSPasteboardReportChangedOwner + 66
6 com.apple.AppKit 0x905cd4aa -[NSPasteboard _updateTypeCacheIfNeeded] + 51
7 com.apple.AppKit 0x905cd361 -[NSPasteboard _typesAtIndex:usesPboardTypes:] + 52
8 com.apple.AppKit 0x905cd327 -[NSPasteboard types] + 50
Does anyone have any idea what could cause this?
The pasteboard object is valid as far as I can tell but then crashes internally.
I think this is generally when pasting from another app, is it possible the other app is mismanaging its ownership of the pasteboard?
Further information from Does NSPasteboard retain owner objects? says that yes, the pasteboard retains the owners passed to it. Therefore, this crash must be either:
An over-release in my code. This causes an object to be deallocated while the pasteboard still holds a reference to it. I think this unlikely as you'd expect the problem to show up independent of the pasteboard, and I've not seen a single crash (report) to suggest that.
Something in the way NSPasteboard manages cross-application pastes is going wrong and crashing. Anyone encountered something like that or know what could cause it?
That appears to be when disposing of information about the previous owner (before the current owner as set by the user's most recent copy). Perhaps that's you? Check your copying code.
Also run your app under the Zombies instrument, if you haven't already.
If you accept a drop from a dragging paste and do the actual work asynchronously in another thread, you should retain the pasteboard itself, or otherwise, it might be deallocated when the function in the main thread returns.
It can probably happen in the dragging source side.
For example:
- (BOOL) outlineView:(NSOutlineView*)inOutlineView acceptDrop:(id<NSDraggingInfo>)inInfo item:(id)inItem childIndex:(NSInteger)inIndex
{
NSPasteboard* pboard = [[inInfo draggingPasteboard] retain]; // This is necessary!
BOOL result = YES;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^()
{
// Do the real work hear:
NSLog(#"types = %#", [pboard types]);
[pboard release];
});
return result;
}
I have included SQLite3 db within a cocoa application, from a Leopard system and saving and retrieving some data from it.
Problem is - although it is running fine on Leopard, it is crashing on Snow Leopard.
Part of the crash report is as follows:
Process: RCS [84283]
Path: /Volumes/RCS Project/~RCS APPLICATIONS/~RCS (Macintosh)/2011/26-January 2011/RCS.app/Contents/MacOS/RCS
Identifier: com.tprf.RCS
Version: 2.0 build-0235 (2.0)
Code Type: X86 (Native)
Parent Process: launchd [173]
Date/Time: 2011-01-04 07:51:59.950 -0800
OS Version: Mac OS X 10.6.5 (10H574)
Report Version: 6
Interval Since Last Report: 494528 sec
Crashes Since Last Report: 24
Per-App Interval Since Last Report: 25 sec
Per-App Crashes Since Last Report: 2
Anonymous UUID: A9023F03-79EA-4444-B7BF-25AB6DD07985
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000002, 0x0000000000000000
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Application Specific Information:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Failed to open database'
*** Call stack at first throw:
(
0 CoreFoundation 0x947e76ba __raiseError + 410
1 libobjc.A.dylib 0x9182e509 objc_exception_throw + 56
2 CoreFoundation 0x947e73e8 +[NSException raise:format:arguments:] + 136
3 Foundation 0x913e6bb3 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 116
4 RCS 0x002af73c -[LocalDBController openDB] + 214
5 RCS 0x00075157 -[SplashScreen awakeFromNib] + 32
6 CoreFoundation 0x9477f9b4 -[NSSet makeObjectsPerformSelector:] + 196
7 AppKit 0x94d2721c -[NSIBObjectData nibInstantiateWithOwner:topLevelObjects:] + 1566
8 AppKit 0x94d251f4 loadNib + 257
9 AppKit 0x94d245ed +[NSBundle(NSNibLoading) _loadNibFile:nameTable:withZone:ownerBundle:] + 228
10 AppKit 0x94d244fe +[NSBundle(NSNibLoading) loadNibFile:externalNameTable:withZone:] + 158
11 AppKit 0x94d24449 +[NSBundle(NSNibLoading) loadNibNamed:owner:] + 383
12 AppKit 0x94d2124d NSApplicationMain + 434
13 RCS 0x00002ce6 start + 54
)
Thread 0 Crashed: Dispatch queue: com.apple.main-thread
0 com.apple.CoreFoundation 0x94832a37 ___TERMINATING_DUE_TO_UNCAUGHT_EXCEPTION___ + 7
1 libobjc.A.dylib 0x9182e509 objc_exception_throw + 56
2 com.apple.CoreFoundation 0x947e73e8 +[NSException raise:format:arguments:] + 136
3 com.apple.Foundation 0x913e6bb3 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 116
4 com.tprf.RCS 0x002af73c -[LocalDBController openDB] + 214
5 com.tprf.RCS 0x00075157 -[SplashScreen awakeFromNib] + 32
6 com.apple.CoreFoundation 0x9477f9b4 -[NSSet makeObjectsPerformSelector:] + 196
7 com.apple.AppKit 0x94d2721c -[NSIBObjectData nibInstantiateWithOwner:topLevelObjects:] + 1566
8 com.apple.AppKit 0x94d251f4 loadNib + 257
9 com.apple.AppKit 0x94d245ed +[NSBundle(NSNibLoading) _loadNibFile:nameTable:withZone:ownerBundle:] + 228
10 com.apple.AppKit 0x94d244fe +[NSBundle(NSNibLoading) loadNibFile:externalNameTable:withZone:] + 158
11 com.apple.AppKit 0x94d24449 +[NSBundle(NSNibLoading) loadNibNamed:owner:] + 383
12 com.apple.AppKit 0x94d2124d NSApplicationMain + 434
13 com.tprf.RCS 0x00002ce6 start + 54
The method(s) which I call in beginning to initialize and open db are as follow:
-(void)dbInit{ // call in awake from NIB
databaseName = [[NSString alloc] initWithString:#"test10.sql"];
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
databasePath = [[NSString alloc] initWithString:[documentsDir stringByAppendingPathComponent:databaseName]];
NSLog(#"databasePath - %#",databasePath);
[self checkAndCreateDatabase];
}
-(void)checkAndCreateDatabase{
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
success = [fileManager fileExistsAtPath:databasePath];
NSLog(#"checkAndCreateDatabase, success - %d",success);
if(success){
[self openDB];
return;
}
NSString *databasePathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:databaseName];
[fileManager copyItemAtPath:databasePathFromApp toPath:databasePath error:nil];
[fileManager release];
// opening db
[self openDB];
}
- (void)openDB{
if (sqlite3_open([databasePath UTF8String], &database) != SQLITE_OK)
{
sqlite3_close(database);
NSAssert(0, #"Failed to open database");
}
else{
NSLog(#"db successfully opened");
}
}
Can anyone suggest some solution for it » solution is found. I have posted it as answer.
Now there are some doubts :
Is there any-other folder in which I should store SQLite3 db... other than documents.. should I store it in ~/Library/Application Support/ or any other folder?
Can we check it via our code that- folder in which we are storing the db file has appropriate permissions or not?
If folder does not have appropriate permission then can we change it via code?
Can anyone clear these doubts or tell me the standard way to implement my requirements?
Thanks,
Miraaj
The only problem I think of is that you're accidentally using a bad set of sqlite libraries. Check your system for any instance of them, or even statically link them? If it isn't that, I don't see anything wrong. (I'm assuming database is declared and set to nil elsewhere.)
To resolve the problem reported above I tried to log the result returned from sqlite3_open. I got 14 ie. SQLITE_CANTOPEN. I checked directory permission and found that some permission were not read & write. When I changed all to read & write, it started working properly :)