I've seen many questions on SO concerning converting between NSURL and NSString. They all involve using either NSString *path = [myURL absoluteString]; or NSString *path = [myURL path];. What is the actual difference between these methods? Is there a time when one should be used over the other? I tried consulting the Apple Docs, but I found it less than helpful.
I'm used to URL's only being mentioned in discussions concerning websites and other topics regarding sending information between different machines, and never being mentioned when dealing with just the file structure on a single machine. Perhaps this is where some of my confusion is coming from, since NSURL seems to be the preferred way of accessing files, regardless of whether that file exists on a network or on the local device. Or maybe that's a totally unrelated topic. I'm not even sure.
Question 1:
What is the actual difference between these methods?
Let's analyze this writing 6 lines of code - 3 for a local and 3 for http URL - and playing around with them a little bit.
Let's create an NSURL using the file:// scheme. If you ask yourself why there are 3 / after file: you should remember that a complete URL exists of a scheme (file:// and absolute or relative path (you can find more information on creating URLs in RFC 1808 on page 3). We use an absolute path which starts with a / so that we end up with ///.
NSURL *aLocalURL = [NSURL URLWithString:#"file:///Users/dennis/Desktop/"];
NSLog(#"absolute string: %#", aLocalURL.absoluteString);
NSLog(#"path: %#", aLocalURL.path);
Output:
absolute string: file:///Users/dennis/Desktop/
path: /Users/dennis/Desktop
So we see that absoluteString still knows its scheme whereas path doesn't have this information anymore.
Note: path is a file (directory) URL and as the docs state, the trailing slash it is stripped.
Now let's take a look at remote URLs. With these type of URLs most people are more familiar. We create it using the same procedure as for local URLs. Our scheme is now http:// and our path is www.apple.com/.
NSURL *anHTTPURL = [NSURL URLWithString:#"http://www.apple.com/"];
NSLog(#"absolute string: %#", anHTTPURL.absoluteString);
NSLog(#"path: %#", anHTTPURL.path);
Output:
absolute string: http://www.apple.com/
path: /
Again, we see that the absolute string still knows its scheme but path is now /. So path seems to be not an appropriate way when working with remote URLs.
However, when we have an URL like http://www.apple.com/index.html we get
absolute string: http://www.apple.com/index.html
path: /index.html
Reading the docs helps here, too:
Per RFC 3986, the leading slash after the authority (host name and port) portion is treated as part of the path.
So the path is everything beginning (and including) at the slash after the authority which is www.apple.com in our case.
Question 2
Is there a time when one should be used over the other?
From the docs: (method: path)
If this URL object contains a file URL (as determined with isFileURL), the return value of this method is suitable for input into methods of NSFileManager or NSPathUtilities.
In my opinion that sentence states clearly that you should use path when you work with NSFileManager or NSPathUtilities.
Conclusion:
When you work with remote URLs you (generally) use absoluteString, otherwise the result is not what you (generally) want.
When you work with local URLs use path.
Sources:
http://www.ietf.org/rfc/rfc1808.txt
http://www.ietf.org/rfc/rfc3986.txt
NSURL Class Reference
Adding to HAS' response -- the Apple docs mention that Path-based URLs are simpler in some ways, however file reference URLs have the advantage that the reference remains valid if the file is moved or renamed while your app is running.
From the documentation for "Accessing Files and Directories":
"Path-based URLs are easier to manipulate, easier to debug, and are generally preferred by classes such as NSFileManager. An advantage of file reference URLs is that they are less fragile than path-based URLs while your app is running. If the user moves a file in the Finder, any path-based URLs that refer to the file immediately become invalid and must be updated to the new path. However, as long as the file moved to another location on the same disk, its unique ID does not change and any file reference URLs remain valid."
https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/AccessingFilesandDirectories/AccessingFilesandDirectories.html
One further note, and I've only tried this for Swift and URL not NSURL. The relativeTo form of URL:
URL(fileURLWithPath: aPath, relativeTo: URL)
generates a URL that behaves not fully like a remote URL (as in #HAS above) and not like a file URL.
So, for example:
let url0 = URL(fileURLWithPath: "/Foo")
let url1 = URL(fileURLWithPath: "Bar", relativeTo: url0)
print("\(url1.path)")
// Output: "/Bar\n"
(similar to results for a remote URL, but not a file URL).
If we use absoluteString, we get:
print("\(url1.absoluteString)")
// Output: "file:///Bar\n"
(not similar to either a file URL or a remote URL).
Related
I'm trying to add a base url for my networking code, the problem is that this URL gets broken when passing to the URLWithString:relativeToURL: method. This URL has the port i'm using, however, after calling the described, the URL is wrong, not including my current port number. I think this is a problem with percent escapers, but i've tried some methods to solve this problem without success.
Here is my code:
// absolute string returns a url a with broken path
[[NSURL URLWithString:#"api/whatever" relativeToURL:[NSURL URLWithString:#"193.178.0.99:9000"]] absoluteString]
// printed absolute path 193.173.0.99:///api/whatever
Other tried approaches:
NSString *baseURLString = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,(CFStringRef)#"193.178.0.99:9000",NULL,(CFStringRef)#":",kCFStringEncodingUTF8);
[[NSURL URLWithString:#"api/whatever" relativeToURL:[NSURL URLWithString:baseURLString]]
// Printed path : 193.173.0.99%3A8000/api/whatever, this path is still not working, although i have the percent escape set.
NSString *baseURLString = [#"193.173.0.99:8000" stringByAddingPercentEscapesUsingEncoding : NSUTF8StringEncoding];
// ... The same final code from above.
// Printed -> the very same first result.
EDIT : From the comment above the URLWithString:relativeToURL: : "These methods expect their string arguments to contain any percent escape codes that are necessary."
Does anybody have a solution to this problem ?
Thanks.
Actually the solution is pretty simple... just add the scheme.
Something like this:
NSURL *baseURL = [NSURL URLWithString:#"http://193.178.0.99:9000"];
NSString *absoluteString = [[NSURL URLWithString:#"api/whatever" relativeToURL:baseURL] absoluteString];
// Prints => http://193.178.0.99:9000/api/whatever
That behavior is actually quite understandable (from an RFC point of view): the relativeToURL part is expected to be a full-fledged URL root, including the URL scheme.
So here in your example, as you didn't provide an http:// scheme or similar, 193.178.0.99 is considered to be the scheme — like it would be http or https or ftp or tel or mailto — and the 9000 port considered to be the host part of your URL (but as 9000 is probably not a valid host according to the RFC, it's probably why you have the warning by the way)
In a way, 193.178.0.99:9000 is interpreted in a similar manner a phone-number URL tel:1-541-754-3010 or a mail URL mailto:john.doe#nowhere.com would; the : separating the URL scheme from the host, not separating the host from the port.
To solve this, simply include the URL scheme (like http or https or whatever the protocol you intend to use) in the relativeToURL parameter:
[[NSURL URLWithString:#"api/whatever"
relativeToURL:[NSURL URLWithString:#"http://193.178.0.99:9000"]]
absoluteString]; // ^^^^^~~ this is the important part
Note: as an alternate solution to build your URL, you could use the iOS7's NSURLComponents class to manipulate NSURL parts separately, that's another way to break down and build up URLs
The docs for NSURL state that:
An NSURL object represents a URL that can potentially contain the
location of a resource on a remote server, the path of a local file on
disk, or even an arbitrary piece of encoded data.
I have a blob of in-memory data that I'd like to hand to a library that wants to load a resource via an NSURL. Sure, I can first write this NSData to a temp file and then create a file:// NSURL from that, but I'd prefer to have the URL point directly to the buffer that I already have present in memory.
The docs quoted above seem to suggest this is possible, but I can't find any hint of how to accomplish it. Am I missing something?
NSURL supports the data:// URL-Scheme (RFC 2397).
This scheme allows you to build URLs in the form of
data://data:MIME-Type;base64,<data>
A working Cocoa example would be:
NSImage* img = [NSImage imageNamed:#"img"];
NSData* imgData = [img TIFFRepresentation];
NSString* dataFormatString = #"data:image/png;base64,%#";
NSString* dataString = [NSString stringWithFormat:dataFormatString, [imgData base64EncodedStringWithOptions:0]];
NSURL* dataURL = [NSURL URLWithString:dataString];
Passing around large binary blobs with data URLs might be a bit inefficient due to the nature of base64 encoding.
You could also implement a custom NSURLProtocol that specifically deals with your data.
Apple has some sample code that uses a custom protocol to pass around image objects: https://developer.apple.com/library/mac/samplecode/SpecialPictureProtocol/Introduction/Intro.html#//apple_ref/doc/uid/DTS10003816
What you are missing is the NSURLProtocol class. Takes about three dozen lines of code, and any code that handles URLs properly can access your in-memory data. Read the documentation, it's not difficult and there is sample code available.
Unfortunately there are some APIs that take an NSURL as a parameter, but can only handle file URLs.
I am using Omniture SiteCatalyst in my iPhone app.It uses get request to hit the servers internally via its sdk.However i am facing an issue where some of the request are not reaching the Omniture servers.The get request which is being sent is of variable length depending on the type of request(around 900 + characters).
My question is whether there any limit for the get request length in an iOS app? and if yes
how it would behave in case the request crosses the limit?
Theoretically if URL conforms to RFC 2396 it is fine. According to documentation
The NSURL class fails to create a new NSURL object if the path being
passed is not well-formed; the path must comply with RFC 2396.
Examples of cases that will not succeed are strings containing space
characters and high-bit characters. Should creating an NSURL object
fail, the creation methods return nil, which you must be prepared to
handle. If you are creating NSURL objects using file system paths, you
should use fileURLWithPath: or initFileURLWithPath:, which handle the
subtle differences between URL paths and file system paths. If you
wish to be tolerant of malformed path strings, you’ll need to use
functions provided by the Core Foundation framework to clean up the
strings.
But some time there is issue with specail character e.g. space, accents and others. You must [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
It is also possible server could not handle very long urls, if there are any limitation on server, server will simple truncate the rest of url string, if this is an issue then server will return 414 error url too long.
I have found that so far the path sent to application:openURL:sourceApplication:annotation: is:
file://localhost/private/var/mobile/Applications/<GUID>/Documents/Inbox/file
To check that the filesystem operations that I am about to perform are indeed likely to succeed (and that the url given to me is not a location outside the sandbox), it looks like I have to do this:
NSString* hdurl = [[#"file://localhost/private" stringByAppendingString:NSHomeDirectory()] stringByAppendingString: #"/"];
NSString* path = url.absoluteString;
if ([path hasPrefix:hdurl]) {
// now ready to e.g. call fopen on: [path substringFromIndex:#"file://localhost".length]
Now, I seem to vaguely recall (and this is probably wrong) that in the past I have seen the file:/// style URL being used. That would clearly cause this code to fail.
How am I to know that it will always give me a file://localhost URL prefix?
Apple's documentation on URLs is strangely missing a section on file URLs.
An NSURL that points to a file on the local file system is called a "file URL". To convert the NSURL to an NSString representing the file's path you use:
NSString *filePath = [url path];
To check to see if an NSURL represents a file URL, use:
BOOL isFileURL = [url isFileURL];
Keep in mind that if your app is passed a file URL, you will always have access to the file. There is no need to check if it starts with any prefix. Why would iOS pass you a file that you don't have access to?
Starting to look into iOS and am in need of Restful networking so I'm looking at AFNetworking. I've followed some good examples and I think I understand how to use it but the request that gets built does not have the expected URL.
In AFHTTPClient requestWithMethod path parameters method,
po urlBase
$0 = 0x003b23b8 https://mysite.com/developer/api
(lldb) po url
$1 = 0x1eddf140 /users/current -- https://mysite.com/developer/api/
(lldb) po request
$2 = 0x1ed50580 <NSMutableURLRequest https://mysite.com/users/current>
The developer section is getting stripped for some reason.
I've seen examples where the relative path string passed into the getPath method has a leading slash and therefore one would expect the base URL to not have a trailing slash but I've seen examples where folks put a slash in both.
My base URL var is
NSString *const ServiceUrlBaseProduction = #"https://mysite.com/developer/api/";
and the relative path to get current user is
#"/users/current";
This is because of AFHTTPSessionManager's interpretation of URLs. In AFHTTPSessionManager.h here there is an explanation of how to write your URLs correctly. I believe you will want to get the current user with the path users/current instead (without the leading slash)
try using NSURL:
NSURL *url = [NSURL URLWithString:#"https://mysite.com/developer/api/"];
Then:
[NSURL URLWithString:#"users/current" relativeToURL:url];