How to get NSError object to capture potential IO errors

I’m trying to write an ASOC application that goes through and changes the names of a bunch of folders on a remote mount. If something goes wrong (like a permissions issue on a folder I’m trying to rename) I’m trying to capture what that error is so that I can present that information to the user in a log file.

I’ve tried passing ‘reference’ as the NSError parameter instead of ‘missing value’, but this keeps crashing the application (I have no skills to interpret the thread crashes).


on renamePathAndLog(sourcePath, destPath)
	set fileManager to current application's NSFileManager's defaultManager()
	set {theRenamed, theError} to fileManager's moveItemAtPath_toPath_error_(sourcePath, destPath, reference)
	if not (theRenamed as boolean) then
		set theErrorSting to theError's localizedFailureReason as string
		return {false, theErrorString}
	else
		return {true, destPath}
	end if
end renamePathAndLog

Is there a proper way to get back an NSError object for the moveItemAtPath:toPath:error method? If I pass nil using ‘missing value’, like:


	.
set theRenamed to fileManager's moveItemAtPath_toPath_error_(sourcePath, destPath, missing value)
	if not (theRenamed as boolean) then
		return {false}
	else
		return {true, destPath}
	end if
...

this works, and doesn’t crash the application, but I can’t report what the real error was, only that the attempt was unsuccessful.

You’re on the right track with reference and your code, although I’d use localizedDescription() instead of localizedFailureReason. Either way, though, you should include the parentheses: you’re calling a method.

Shane-

Thanks for the tip. I tried the recommended NSError method with parentheses:


on renamePathAndLog(sourcePath, destPath, destName)
	set {theRenamed, theError} to fileManager's moveItemAtPath_toPath_error_(sourcePath, destPath, reference)
	if not (theRenamed as boolean) then
		set theErrorString to theError's localizedDescription() as string
		return {false, theErrorString}
	else
		return {true, destPath}
	end if
end renamePathAndLog

but my application continues to crash at runtime. Below is what I get in the crash log. I don’t know if anyone might be able to steer me in the right direction, but the crash has something to do with trying to trap this error. If I just return the boolean and pass missing value for the error parameter, the app works, I just can’t get the specific error (not the end of the world), but passing ‘reference’ and trying to parse out something from the NSError object causing my app crashes.

Here’s a chunk of the crash report:

Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: EXC_I386_GPFLT

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 com.apple.CoreFoundation 0x00007fff8ace3ae3 CFRetain + 51
1 com.apple.AppleScriptObjC 0x00007fff8dd68574 specifierForObjCInstance + 120
2 com.apple.AppleScriptObjC 0x00007fff8dd680f2 descriptorForData + 350
3 com.apple.AppleScriptObjC 0x00007fff8dd6a0fb -[NSInvocation(AppleScriptObjC) returnValueAsDescriptor] + 472
4 com.apple.AppleScriptObjC 0x00007fff8dd6c645 Invoke + 2026
5 com.apple.AppleScriptObjC 0x00007fff8dd6d372 BASendProc + 2241
6 com.apple.applescript 0x000000010a9c5323 ComponentSend(AEDesc const*, AEDesc*, int, int) + 510
7 com.apple.applescript 0x000000010a9d3fcd TUASApplication::Send(TStackFrame_UASRemoteSend*, AEDesc*, AEDesc*, unsigned char, unsigned char, unsigned char) + 2565
8 com.apple.applescript 0x000000010a9efee2 UASRemoteSend(unsigned char, unsigned char, unsigned char, unsigned char, unsigned char, unsigned char*) + 432
9 com.apple.applescript 0x000000010a9d84ba UASExecute1() + 369
10 com.apple.applescript 0x000000010a9b1495 ASExecuteEvent(AEDesc const*, unsigned int, int, unsigned int*) + 848
11 com.apple.applescript 0x000000010a9aa33a AppleScriptComponent + 1778
12 com.apple.openscripting 0x00007fff937280d0 OSAExecuteEvent + 66
13 com.apple.AppleScriptObjC 0x00007fff8dd660a2 +[BAObjectProto invokeScriptHandler:forObject:args:error:] + 249
14 com.apple.AppleScriptObjC 0x00007fff8dd670bc -[BAObjectProto forwardInvocation:] + 347
15 com.apple.CoreFoundation 0x00007fff8ad63197 forwarding + 775
16 com.apple.CoreFoundation 0x00007fff8ad62e18 _CF_forwarding_prep_0 + 232
17 com.apple.AppKit 0x00007fff92b6c959 -[NSApplication sendAction:to:from:] + 342
18 com.apple.AppKit 0x00007fff92b6c7b7 -[NSControl sendAction:to:] + 85
19 com.apple.AppKit 0x00007fff92b6c6eb -[NSCell _sendActionFrom:] + 138
20 com.apple.AppKit 0x00007fff92b6abd3 -[NSCell trackMouse:inRect:ofView:untilMouseUp:] + 1855
21 com.apple.AppKit 0x00007fff92b6a421 -[NSButtonCell trackMouse:inRect:ofView:untilMouseUp:] + 504
22 com.apple.AppKit 0x00007fff92b69b9c -[NSControl mouseDown:] + 820
23 com.apple.AppKit 0x00007fff92b6150e -[NSWindow sendEvent:] + 6853
24 com.apple.AppKit 0x00007fff92b5d644 -[NSApplication sendEvent:] + 5761
25 com.apple.AppKit 0x00007fff92a7321a -[NSApplication run] + 636
26 com.apple.AppKit 0x00007fff92a17bd6 NSApplicationMain + 869
27 libdyld.dylib 0x00007fff8b8197e1 start + 1

I’m not convinced your error is where you think it is. I’ve just run your code in ASObjC Explorer and it works fine:

on renamePathAndLog(sourcePath, destPath, destName)
	set fileManager to current application's NSFileManager's defaultManager()
	set {theRenamed, theError} to fileManager's moveItemAtPath:sourcePath toPath:destPath |error|:(reference)
	if not (theRenamed as boolean) then
		set theErrorString to theError's localizedDescription() as string
		return {false, theErrorString}
	else
		return {true, destPath}
	end if
end renamePathAndLog

And I see:

0000.000 -- Start --
0000.070 [12] set fileManager to current application's NSFileManager's defaultManager()
--> <NSFileManager: 0x60800000b7b0>
0000.093 [13] set {theRenamed, theError} to fileManager's moveItemAtPath:sourcePath toPath:destPath |error|:(reference)
--> {false, «class ocid» id «data optr00000000F00B050100600000»}
0000.099 [14] if not (theRenamed as boolean) then
--> {false, «class ocid» id «data optr00000000F00B050100600000»}
0000.101 [15] set theErrorString to theError's localizedDescription() as string
--> "no" couldn't be moved to "no" because either the former doesn't exist, or the folder containing the latter doesn't exist.
0000.102 -- End -- (~0.102s)

You’re going to have to add more logging. Start with something like “display dialog theErrorString” before the return statement.

I can’t explain it. I added a dialog box before and after the:


   set {theRenamed, theError} to fileManager's moveItemAtPath_toPath_error_(sourcePath, destPath, reference)

method is called. The first time through a loop when a set of paths are sent as parameters, the handler works. The second set of paths passed to the handler causes the application to crash.

I can successfully run the handler in AppleScriptObjcC Explorer 2 using the same set of parameters that make the application crash and it works as it should. So, I’m mystified, but if I take out trying to get the NSError object then the application works, so I guess I’m going to have to punt on trying to get the NSError object.

Thanks.
Jim

Are you using GC or ARC? If so, try the other for a test.

Shane-

I’m on OS X 10.8.5 running Xcode 5.0.2. I saved out a version with ARC on (that was the default setting) and my app crashed. I saved out a version with ARC off and GCC enabled and my app runs without crashing.

I also copied both of these versions to my Mavericks laptop. Both the ARC on version and the GCC enabled version run on my Mavericks laptop.

So, with GCC enabled, I’m able to successfully work with the NSError object in 10.8.

I’m very new to ASOC and Xcode, so any explanation into why one works over the other is much appreciated.

Thanks.
Jim

One other question about build settings. I set my “OS X Deployment Target” to be OS X 10.6, but the application will not run on 10.6. When launching on 10.6, it bounces in the dock for a split second in the dock, then immediately dies. The console logs:

Unable to load nib file: MainMenu, exiting
Exited with exit code: 1

The Architectures is “Standard Architectures (64-bit Intel) (x86_64)”. I built the MainMenu.xib file with “Use Autolayout” unchecked.

The application works on 10.8-10.9.

Thoughts?

I really don’t know. The CFRetain in your crash dump suggests a memory problem, so that’s why I suggested you try there change. The difference in behaviour between 10.9 and 10.8 is more perplexing. One thing I would suggest is doing a Product → Clean after changing the memory settings.

If, after a clean, the app still crashes with ARC, you should definitely log a bug. Try to repeat it in a simple project.

With MainMenu.xib selected, show the File Inspector. Check the Builds for setting.

The File Inspector for MainMenu.xib says “Builds for: Project Deployment Target (10.6)”. If I explicitly set it to 10.6 or leave it at Project Deployment Target (10.6), with a Clean then Build, I get the same failure when attempting to launch the application on a 10.6 workstation.

And you are sure you are not using any widgets or capabilities that were introduced after 10.6?

I just tried making a new project (matching my other apps settings) with a single push button on the main window connected to the BeginMainLoop_ handler and it has the same failure to launch under 10.6.

Main settings are:
Architectures: Standard Architectures (64-bit Intel)(x86_64)
Base SDK: OS X 10.8
Valid Architectures: i386 x86_64

Objective-C Automatic Reference Counting: No
User-Defined
GCC_ENABLE_OBJC_GC: required


script AppDelegate
.

    on BeginMainLoop_(sender)
        display dialog "Hello World!"
    end BeginMainLoop_

end script

Does anything stand out? It appears my workstation is incapable of compiling an app that works on 10.6 at the moment.

Shane saved me! The issue involves Base Internationalization. In the Xcode release notes:

Xcode 4.4 Release Notes/New Features/General says…

The Use Base Internationalization setting in the project editor works only on Mac products for deployment on OS X v.10.8 and later. Xcode must also be running on OS X v.10.8 or later. This setting is not supported on iOS projects. 11712855

My project defaulted to using this setting, but I was trying to deploy to 10.6 and above.

For anyone following along, Xcode 5 now defaults to Base Internationalization, which only works with 10.8 and later. To turn it off, choose the .xib file and in the File Inspector, under Localization, click off Base. You will get a dialog asking you where you want it moved to; choose English, not Trash. English will then be checked. (Next time you reopen the project, Base will no longer appear as an option.)

But…

As of Xcode 5.0.2, if you do something different or play with it much, it’s very easy to either lose the .xib or get Xcode into a state where you have to force-quit. So I’d strongly advise taking a snapshot first if you have done much work on the .xib.