NSCF Array Out of Bounds? - objective-c

I have a fairly simple application using Core Data, and a couple Array Controllers (in IB, no code files for them, other than the xcdatamodel file). When I run my application, I get the following error in the log (the app still runs, but no window appears until I go to File > New Document):
2011-02-08 18:45:10.434 MyApp[35178:a0f] *** -[NSCFArray objectAtIndex:]: index (0) beyond bounds (0)
I have no references in my myDocument.h or .m files to an array. The error is not critical (the app still runs), but it disables some of my code to load the last used document or (if that doesn't exist) load a fresh document. The app simply loads up with no visible windows. I would greatly appreciate some help on this and will award the answer quickly :)
EDIT: Here is the applicationShouldOpenUntitledFile code:
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
{
NSArray* urls = [[NSDocumentController sharedDocumentController] recentDocumentURLs];
if ([urls count] > 0){
NSURL *lastURL= [urls objectAtIndex: 0];
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:lastURL display:YES error:nil];
return NO;
}
return YES;
}

Sorry to post here, I don't have the right to comment everywhere for the moment.
#Zakman411: Justin is saying you should enable the "Stop on Objective-C Exceptions" item in the Run menu, then launch the debugger. gdb should show you where the exception is thrown exactly.

Related

While loop with NSFileManager directory enumerator not running

I am seeking help to understand why a tutorial I am following is not working for me. I am running macOS 12.3.1, Xcode 13.3.1. The project is in Objective-C and using XIB.
This is a view-based NSTableView, using a folder of PNGs stored on my SSD for the imageView and the stringByDeletingPathExtension as stringValue for the cell's text field. I filled my code with NSLog calls to try and catch what could have been going awry.
Most setup is happening in applicationDidFinishLaunching:, where I initialise an NSMutableArray for the table's content, an NSString for the file path, then set up the file manager and the directory enumerator with said path (note: all working up to here).
Now comes the loop to populate the table contents' mutable array. I cannot understand why said loop gets skipped entirely! Its condition is to set an NSString equal to the nextObject of the directory enumerator. I am sure the loop gets skipped because the NSLog call after the loop runs!
Here is the entire code of applicationDidFinishLaunching:, including my comments and logs (I have just replaced my account name with ):
-(void)applicationDidFinishLaunching:(NSNotification *)aNotification {
_tableContents = [[NSMutableArray alloc] init];
NSString *path = #"/Users/<myUsername>/Developer/Apple-Programming-YT/Cocoa Programming/Flags/PNG/40x30";
// MARK: Debug 1
NSLog(#"path found: %#", path); // the correct path gets printed, as expected
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator *directoryEnum = [fileManager enumeratorAtPath:path];
NSString *file;
// MARK: Debug 2
NSLog(#"Checking that file is empty: %#", file); // (null) gets printed, as expected
// MARK: Debug 3
if (file != directoryEnum.nextObject) {
NSLog(#"File cannot be assigned to the Directory Enumerator");
} else if (file == directoryEnum.nextObject) {
NSLog(#"File properly assigned. Proceed!"); // this gets printed! Is it correct?
} else {
NSLog(#"Something went wrong during assignment of nextObject to file");
}
while (file = [directoryEnum nextObject]) {
NSLog(#"While loop entered!"); // this doesn't get printed! Why?!
// MARK: Debug 4
NSLog(#"File: %#", file);
NSString *filePath = [path stringByAppendingFormat:#"/%#", file];
// MARK: Debug 5
NSLog(#"Image filepath: %#", filePath);
NSDictionary *obj = #{#"image": [[NSImage alloc] initByReferencingFile:filePath],
#"name": [file stringByDeletingPathExtension]};
[self.tableContents addObject:obj];
}
[self.tableView reloadData];
NSLog(#"Table View Reloaded"); // This gets printed!
}
I have uploaded the full app to GitHub, in case you may want to look at it and see if something else could be wrong, but every outlet, delegate, data source is connected.
Now for my diagnosis & ideas:
The Debug 3 mark is what I find most interesting. AFAIK file should still be (null), so how checking if it is equal to directoryEnum.nextObject returns YES?
I created Debug 3 because the NSLog checking whether the loop had been entered didn't get printed. I therefore assumed the condition for the while loop had a problem.
I then tried to create a do-while loop instead of this while loop and, of course, the code ran. For the log with "Image filepath" it returned the address above followed by (null), as if it didn't find the file. But how is it possible if the file is indeed there? Do I require some sort of permission to access it? Being the object empty, the next line in the console was quite clear: "attempt to insert nil object from objects[1]".
But now, how do I solve this?
Any help here is much appreciated. If you download it from GitHub, please replaces the *path string with a folder of PNGs on your SSD.
Thank you.
I don't think you can access the filesystem directly with a path like that any more. If you check the value of file in your code, it is nil, which means that file == directoryEnum.nextObject will evaluate to true.
You have to create a path starting with NSHomeDirectory() or similar and add components to it. This makes a path that goes via your application support folder, which contains an alias to the desktop. I'm not sure why that's OK and accessing it directly is not, but I'm not a Mac developer.
I'd have to say following a tutorial as old as that, you're going to struggle with a lot of things.

Crash when trying to present UIActivityViewController

I see a crash in Crashlytics than happens to my users sometimes. The crash happens when presenting UIActivityViewController in the last line of the following code:
NSData* snapShot = ... ;
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:[NSArray arrayWithObjects:activityTextsProvider, snapShot ,nil] applicationActivities:[NSArray arrayWithObjects:customActivityA, customActivityB, customActivityC, nullptr]];
activityViewController.excludedActivityTypes = [NSArray arrayWithObjects:UIActivityTypePrint, UIActivityTypeAssignToContact, UIActivityTypeMail, UIActivityTypeCopyToPasteboard, nil];
activityViewController.popoverPresentationController.sourceView = self.myButton;
activityViewController.popoverPresentationController.sourceRect = self.myButton.bounds;
activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError)
{
...
};
[self presentViewController:activityViewController animated:YES completion:nil];
I perform this in the main thread and unable to reproduce this crash locally. What could be the reason of this crash?
Edit: I changed nullptr to nil and the issue still happened. I managed to reproduce the issue: the crash happens only if before opening the activity controller i showed a UIMenuController. When creating UIActivityViewController it is not nil, but when presenting the controller i see the crash in the presentViewController line and the activity controller there is shown as nil
József addressed the use of nullptr in comments, and Fogh is spot-on that the actual crash log is important (please edit your question and post the full crash log), but I'd like to point out something else.
You're assuming your call to initialize activityViewController is succeeding. You should code defensively (by assuming everything that can fail probably will fail and testing for this at runtime). Wrap the rest of the configuration and presentation inside an if (activityViewController != nil) {} condition (you should probably have an else with proper error handling/reporting too) so you're properly detecting an all-out initialization failure for multiple reasons (like a misplaced nib, missing resource, etc.).
In your case, I think it's likely the initialization is failing because your class is doing something with a faulty array, as József's nullptr catch suggests. Perhaps you're using one or more pre-C++11 c libraries / compiling with a non-C11/gnu11 "C Language Dialect" build setting and nullptr is not equivalent to nil, leading to strange results in a supposed-to-be-nil-terminated array?
Note: If that turns out to be the case, I'll happily take an upvote but would rather József post his comment as an answer so you can give him proper credit. (Feel free to edit this request out of my answer if/when that happens.)

Cocoa Document Won't Open Again After It's Closed

This is a pretty weird issue. I have a table in my Cocoa application that displays a list of recently opened files. You can double click on an entry to open the associated file. Trouble is, though, after the file is opened once, whether through the Open panel, the Recent Documents menu, or through the aforementioned table, it can't be opened again until the application has quit and re-opened. Other documents, however, can be opened, but once they're closed they can't be opened again either.
This is pretty odd behavior, and I'm not sure what's causing it. But it's certainly annoying. For reference, the Release on Closed attribute of the window from Xcode does nothing and, if selected, does not do anything. I can't think of any other attributes which might cause this behavior. For reference, here's a photo of the attributes panel:
Here's the code for the table which opens the recently opened file:
- (void)respondToRecentFileDoubleClick {
NSInteger clickedRow = [_recentFileBrowser clickedRow];
if (clickedRow != -1) { // We're in the row.
NSDocumentController *docControl = [NSDocumentController sharedDocumentController];
NSURL *selectedDocument = (NSURL *)[docControl recentDocumentURLs][clickedRow];
NSLog(#"Selected row %ld.", (long)clickedRow);
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:selectedDocument display:YES completionHandler:nil];
}
}
The documentation for openDocumentWithContentsOfURL: says that the document won't be opened if it's already opened, but in this case, all of the document windows are closed, so that can't be what causes this behavior. And the NSLog() statement inside the if block prints, so I know the code is being executed.
Anyone know what might be causing this bizarre issue?
From the Xcode image, it appears that you are using your own WindowController. The default close menu item is wired up to call performClose. The performClose method is implemented in NSWindow. So, what is happening is that the window is closing, but the document is not removed from the open document list. Try adding < NSWindowDelegate> to your WindowController interface (in the .h file). Then add to your WindowController .m file:
- (void) windowWillClose:(NSNotification *)notification {
[ourdoc close];
}
Substitute whatever variable you using to hold your document reference for ourdoc. Typically the method setDocument will get called with your document reference. (Also in your WindowController.)
- (void) setDocument:(NSDocument *)document {
ourdoc = (yourNSDocumentsubclass *)document;
}
completionHandler:nil change for debugging:
completionHandler:^(NSDocument *doc, BOOL documentWasAlreadyOpened, NSError *error) {
if (documentWasAlreadyOpened) {
NSLog(#"document was already opened");
NSArray *rats = [[NSDocumentController sharedDocumentController] documents];
NSLog(#"%s seriously: %#", __PRETTY_FUNCTION__, rats);
}
}

Drag Files come across Sandbox(__CFPasteboardIssueSandboxExtensionForPath)

I processed drag operation from browser view to custom view.It work well in snow lepoard,but not in Mountain Lion with sandbox.
in browser view:
NSMutableArray* urls = [[[NSMutableArray alloc] init] autorelease];
..............put some NSUrl to urls array....................
[pasteboard writeObjects:[NSArray arrayWithArray:urls]];
in my receive custom view:
NSArray* pasteboardItems = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSString class]] options:nil];
NSArray* pasteboardItems2 = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]] options:nil];
NSArray* pasteboardItems3 = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSImage class]] options:nil];
NSLog(#"%#",pasteboardItems);
NSLog(#"%#",pasteboardItems2);
NSLog(#"%#",pasteboardItems3);
my log is:
2012-08-09 18:33:43.886 iCollage[6885:303] __CFPasteboardIssueSandboxExtensionForPath: error for [/Users/xxxx/Library/Containers/xxxxxxxxxxxx/Data/Downloads/1343902069.jpg]
2012-08-09 18:33:44.546 iCollage[6885:303] ( "file://localhost/Users/xxx/Library/Containers/xxxxxxxx/Data/Downloads/1343902069.jpg")
2012-08-09 18:33:44.547 iCollage[6885:303] ( "file://localhost/Users/xxxxx/Library/Containers/xxxxxx/Data/Downloads/1343902069.jpg")
2012-08-09 18:33:44.547 iCollage[6885:303] ()
my question is:
1.how to fix this error __CFPasteboardIssueSandboxExtensionForPath;I refer the docs and found nothing about that.I am ensuer that i have the permission to access the file!google says, may be "startAccessingSecurityScopedResource" will help me, then i try and failed
2.why pasteboardItems2 have value?i write to pasteboard only url but not string.It disgusted me that I can get the url both from NSString type and NSUrl type! (I try drag a file from iFinder, the url will only exist in pasteboardItems but not pasteboardItems2).Anybody know why? I think the first problem will auto fixed when some one help me fix this problem.
I believe Apple answer question 1:
Important: Although you can support dragging file paths, in general,
you should avoid doing so unless you are certain that the destination
app will never be run in an app sandbox. If you use an NSString, OS X
has no way to know whether that string should be interpreted as a
path; thus, OS X does not expand the destination app’s sandbox to
allow access to the file or directory at that location. Instead, use
an NSURL, a bookmark, or a filename pasteboard type.
WRT to question 2, it looks like you have pasted URLs so reading NSURL objects would seem to be correct. However I think you should implement the dragging using the following code (also from the link above):
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
NSPasteboard *pboard = [sender draggingPasteboard];
if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
int numberOfFiles = [files count];
// Perform operation using the list of files
}
return YES;
}
You need to generate security-scoped URL bookmark data on the sender side, and turn that data back into a URL on the receiver side. There's some other stuff you have to do after that when you want to actually access the URL; the documentation elaborates.
The receiving application, when running in a sandbox, will not be able to handle bare paths. This is a core part of being sandboxed; you are not allowed to use bare paths or their corresponding URLs to access files that aren't in your sandbox container and haven't been explicitly handed to you by the user.
Your pasteboardItems read object of NSString type, but you dragged a file(with jpg extension), you should register for NSString type in your init method:
[self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeString]];
You need to have Document Types defined in your application so that the sandboxing mechanism knows your application should be opening files with those extensions. You can do this by clicking the project on the left in Xcode, and in the Info tab, under Document Types add a new document type for each extension.
You just need to fill in the name and extensions field.
Also if you want to persist your permission to access the files dragged onto your application, you can use this class to wrap up all that logic. https://github.com/leighmcculloch/AppSandboxFileAccess

Correct way to multithread in objective-c?

I have a UITableView which displays images. Every cell has an image and every time a cell loads, I call a selector (from the cellForRowAtIndexPath) in the background like this:
[self performSelectorInBackground:#selector(lazyLoad:) withObject:aArrayOfData];
The only problem is that sometimes I get a crash (because I am changing data in the background while it's trying to be read elsewhere). Here's the error:
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <CALayerArray: 0xce1a920> was mutated while being enumerated.'
When updating the data in the background, should I move it to the main selector and change it? Or should I call the #selector() differently?
Thanks!
If you can leave the operation on the main thread and have no lagginess nor problems you are done.
However: Let's assume you've already done that and encounter problems. The answer is: don't modify the array in the lazy load. Switch to the main thread to modify the array. See Brad's answer here:
https://stackoverflow.com/a/8186206/8047
for a way to do it with blocks, so you can send your objects over to the main queue (you should probably also use GCD for the call to the lazy load in the first place, but it's not necessary).
You can use #synchronized blocks to keep the threads from walking over each other. If you do
#synchronized(array)
{
id item = [array objectAtIndex:row];
}
in the main thread and
#synchronized(array)
{
[array addObject:item];
}
in the background, you're guaranteed they won't happen at the same time. (Hopefully you can extrapolate from that to your code—I'm not sure what all you're doing with the array there..)
It seems, though, like you'd have to notify the main thread anyway that you've loaded the data for a cell (via performSelectorOnMainThread:withObject:waitUntilDone:, say), so why not pass the data along, too?
Given the term 'lazy load' I am assuming that means you are pulling your images down from a server. (If the images are local then there is really no need for multithreading).
If you are downloading images off a server I would suggest using something along these lines (using ASIHTTPRequest)
static NSCache *cellCache; //Create a Static cache
if (!cellCache)//If the cache is not initialized initialize it
{
cellCache = [[NSCache alloc] init];
}
NSString *key = imageURL;
//Look in the cache for image matching this url
NSData *imageData = [cellCache objectForKey:key];
if (!imageData)
{
//Set a default image while it's loading
cell.icon.image = [UIImage imageNamed:#"defaultImage.png"];'
//Create an async request to the server to get the image
__unsafe_unretained ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:imageURL]];
//This code will run when the request finishes
[request setCompletionBlock:^{
//Put downloaded image into the cache
[cellCache setObject:[request responseData] forKey:key];
//Display image
cell.icon.image = [UIImage imageWithData:[request responseData]];
}];
[request startAsynchronous];
}
else
{
//Image was found in the cache no need to redownload
cell.icon.image = [UIImage imageWithData:imageData];
}