ASOC NSURL to POSIX Path with NSNull (missing value)

5 days new to ASOC and purchasing Shane Stanley’s book, I can tell you that I have made enormous progress. I highly recommend it.

I’d like to ask if anyone has any suggestions for improving my code? Am I referencing everything correctly? Is there a better way to handle NSNull’s because any new list I create needs to keep the same count of items.

Although it does what I want, I’m not sure if I’ve missed any pieces of the puzzle or potentially if there is a better way? I’m running a Music.app (iTunes) script over approx. 15,000 tracks so any optimisation tips would be appreciated.

tell application "Music" to set theLocation to (get location of every file track in library playlist 1)
set {thePosixPath, theFolder} to my getLocationLists(theLocation)

on getLocationLists(someList)
	set theArray to current application's NSArray's arrayWithArray:someList
	set thePosixPathList to {}
	set theParentFolderList to {}
	
	repeat with i from 1 to count of theArray
		try
			--get the posix path
			set theNSURL to (theArray's objectAtIndex:(i - 1))
			set thePosixPath to theNSURL's |path|()
			set end of thePosixPathList to thePosixPath as text
			
			--get the parent folder
			set pathString to (current application's NSString's stringWithString:thePosixPath)
			set theComponentArray to pathString's pathComponents()
			set theIndex to (theComponentArray's |count|()) - 2
			set end of theParentFolderList to (theComponentArray's objectAtIndex:theIndex) as text
			
		on error
			set end of thePosixPathList to missing value
			set end of theParentFolderList to missing value
		end try
	end repeat
	
	return {thePosixPathList, theParentFolderList} as list
end getLocationLists

Thanks.

You could replace

--get the parent folder
set pathString to (current application's NSString's stringWithString:thePosixPath)
set theComponentArray to pathString's pathComponents()
set theIndex to (theComponentArray's |count|()) - 2
set end of theParentFolderList to (theComponentArray's objectAtIndex:theIndex) as text

with


set end of theParentFolderList to (theNSURL's URLByDeletingLastPathComponent)'s lastPathComponent() as text

getting the name of the parent folder with the NSURL API is more efficient.

Thanks, Stefan. Your suggestion helped me understand the syntax for nested properties and improved runtime significantly.

Which leads me to ask about the parenthesis after |path|() and lastPathComponent()…

Through trial, error, and typo’s I found that omitting parenthesis actually improved runtime by sometimes more than 40 seconds while still returning accurate results. Is there a reason for the runtime improvement? Could there be any adverse affects with this practice? If anyone can explain what’s actually happening ‘under the hood’ to result in this runtime improvement that would be great.

tell application "Music" to set theLocation to (get location of every file track in library playlist 1)
set {thePosixPath, theFolder} to my getLocationLists(theLocation)

on getLocationLists(someList)
	set theArray to current application's NSArray's arrayWithArray:someList
	set thePosixPathList to {}
	set theParentFolderList to {}
	
	repeat with i from 1 to count of theArray
		try
			set theNSURL to (theArray's objectAtIndex:(i - 1))
			
			--set end of thePosixPathList to theNSURL's |path|() as text
			--set end of theParentFolderList to (theNSURL's URLByDeletingLastPathComponent)'s lastPathComponent() as text
			
			set end of thePosixPathList to theNSURL's |path| as text
			set end of theParentFolderList to theNSURL's URLByDeletingLastPathComponent's lastPathComponent as text
			
		on error
			set end of thePosixPathList to missing value
			set end of theParentFolderList to missing value
		end try
	end repeat
	
	return {thePosixPathList, theParentFolderList} as list
end getLocationLists

Thanks.

When you include the parentheses, AppleScript looks up the method’s signature, and converts any parameters, and the result, to what it judges are the equivalent AS/Cocoa classes or types. When you leave of the parentheses, it’s the same as calling valueForKey:“methodName”.

The valueForKey: method has it’s own rules for conversions, so there’s a fraction of time saved by not having to look up the method signature and so on.

Yes. valueForKey: will always return Cocoa object that you have to coerce to their AS equivalents, whereas AppleScript’s bridging will often do it for you. For example, a method that returns a BOOL will return true or false with parentheses, but the NSNumber 0 or 1 without them.

Once you get comfortable with the difference, it can sometimes be a time saver. However, it can also be the sort of hidden trap that can make debugging difficult. I’ve also seen cases where it simply fails. For me personally, I rarely take the risk.

In the current case, however, there are big gains to be made using valueForKey: and its sibling valueForKeyPath: because they allow you to avoid the repeat loop altogether:

	set theArray to current application's NSArray's arrayWithArray:someList
	set thePosixPathList to theArray's valueForKey:"path"
	set theParentFolderList to theArray's valueForKeyPath:"URLByDeletingLastPathComponent.lastPathComponent"

Thanks, Shane.
Wow, results for 15,000 tracks in 3 seconds… it doesn’t get better than that.

Seeing joined keys with valueForKeyPath and easily returning valueForKey without a repeat loop in the context of Music.app (iTunes) has really helped me to understand the last section of Chapter 5.

I’ve since simplified my code with the following and I am now ready to revisit the rest of my script with this optimised approach in mind. Cheers!

tell application "Music" to set theLocation to current application's NSArray's arrayWithArray:(get location of every file track in library playlist 1)
set thePosixPathList to theLocation's valueForKey:"path"
set theParentFolderList to theLocation's valueForKeyPath:"URLByDeletingLastPathComponent.lastPathComponent"