Building a custom NSArchiver serialize to string - objective-c

How does NSArchiver serialize to file? I assume it's serialized in binary format, is that correct? What if I want to store it in string so I can store into SQLite database? Do I need to write my own custom NSArchiver? If so, how do I go about doing that? Are there any tutorials out there?
p.s. I do realize Core Data can do this but let me cross that option out for now.

You can archive to an NSData object instead of to a file, if you want, with +archivedDataWithRootObject:. It won't be a "string," but that's fine, because an NSString in Cocoa represents a sequence of Unicode characters, while an NSData represents a sequence of bytes (which you could easily store wherever you want, including in a database).
Note that you really should be using NSKeyedArchiver instead:
+ (NSData *)archivedDataWithRootObject:(id)rootObject
+ (id)unarchiveObjectWithData:(NSData *)data

Related

Writing objects to plain text in NSUserDefaults

How can I write an object to NSUserDefaults as plain text?
I've written a unit conversion system for a scientific application that basically operates around the principal of "unit objects". These objects represent a specific unit with a specific value in a given domain. The domain is represented by the class name, since everything is organized so that a time unit is literally called "TimeUnit" and a length unit is literally called "LengthUnit".
The current value of a "unit" is represented by a double, and the "unit" itself (ie, "meters", "kelvin", "hours", "pounds", etc) is represented by an NSString.
I need to be able to write these objects to NSUserDefaults.
I've already implemented a system using NSCoding and NSKeyedArchiver. This works great, I can store and retrieve units from NSUserDefaults and everything works.
The problem is that the output from NSKeyedArchiver is too big. The base64 encoded data you get from it is this gigantic chunk of characters, which is impossible to change through a plain text editor and somewhat difficult to debug. I know you can override this to a certain extent by using NSPropertyListXMLFormat_v1_0 with NSKeyedArchiver's setOutputFormat, but even that produces a fairly verbose output with a whole bunch of stuff I don't really care about (I know all that data is there for a reason, but I'm guessing that's because NSKeyedArchiver is designed to handle a lot of different situations).
It would be much easier if I could encode these objects using my own format. Something almost like:
class_name:double_value:unit_name
So a temperature unit with a value of 22 degrees celsius would become:
TemperatureUnit:22:celsius
What is the best way of achieving something like this? Should I be subclassing NSCoder? Will this let me store the objects as a plain text string in NSUserDefaults and not a base64 encoded chunk of binary data?
For something like this you could add "serialize" and "deserialize" methods (or whatever names you want). The former returns a string and the latter takes a string and returns your object.
+ (MyUnitClass *)deserialize:(NSString *)encodedString {
// split string, create new object, assign values
}
- (NSString *)serialize {
return [NSString stringWithFormat:#"%#:%f:%#", self.classname, self.double_value, self.unit_name];
}
Now you can call serialize on one of your objects and store the string in NSUserDefaults. Use deserialize to create an object from a string you have stored in NSUserDefaults.

Why NSJSONSerialization uses NSData instead of NSString?

Is there any reason for NSJSONSerialization to use NSData instead of NSString for representing JSON data?
NSString seems like a more obvious choice to me...
I imagine it would be more efficient to encourage parsing NSData instead of NSString. If you are parsing a response from a server, for example, you'll get an NSData object representing a buffer of raw bytes returned from the server (note that NSJSONSerialization also includes a method for parsing an NSInputStream directly). Parsing the whole thing into an NSString would be a waste since that would just be an intermediate object that would get thrown out. Instead, NSJSONSerialization is probably parsing the bytes in the NSData object directly and only construct NSStrings for the appropriate keys and values in the resulting data structure.

NSString substituting NSData possible, what are consequences?

Suppose I'm storing a stream of ASCII, say 0x0a0b0c00. What would happen to the data if I store it in an NSData instance vs. an NSString? Would the data get converted into something else? I'm a little confused because they are both buffers holding the exact same thing.
NSData is a container to store, as its name suggests, raw binary data. NSData makes no assumptions of the format of the binary data. It can be text, images, audio, etc.
NSString interprets the data as text with a given encoding: which could be ASCII, Unicode, etc. In most cases, NSString will copy the bytes to its internal data structure to store the raw binary.
If it's not text, use NSData. It's clearer in code to know what's being managed and avoids having to fight string encodings.

NSLocalizedString using external sources in Objective C?

Is there a way to make the localization or Localizable.strings read from directories outside NSBundle?
I am trying to make my app read localizations from a file that is downloaded via a server, is there a way good way to do this?
Thanks in advance.
You will have to write your own MyLocalizedString function which reads the file manually. A .strings file is actually an old type of property list, so it can be read using the NSPropertyListSerialization class like this:
id plist = [NSPropertyListSerialization
propertyListWithData:[NSData dataWithContentsOfFile:stringsFilePath]
options:0
format:NULL
error:&error];
plist is just an NSDictionary, so you can read a string value from the result like this:
[plist objectForKey:#"my_string"];
You should probably implement some sort of cache so that you aren't parsing the whole file for each string lookup.
Note that if you are using genstrings and the other related command line tools, you can use the following option to specify the name of your custom lookup function:
Usage: genstrings [OPTION] file1.[mc] ... filen.[mc]
...[snip]...
-s substring substitute 'substring' for NSLocalizedString.
If these strings files are being uploaded to a server, you can improve performance in the app a bit by first converting them to the binary plist format (this is what Xcode would normally do during a build):
plutil -convert binary1 myStrings.strings
You will have to put together the pieces yourself. There isn't any built-in facility to do that. Your localizations can just be serialized dictionaries with the development language string as the key and the localized string as the value. You can serialize as a plist, which may be most convenient to edit, or using a keyed archive, which may be more compact.

How to use NSFileHandle to read integer from file

I want to read a list of integers from a text file, I just want to write code like
int temp;
fin>>temp;
But when I read the Cocoa documentation, I found NSFileHandle is suggested, and there is no method like I assumed, only one related method:
- (NSData *)readDataOfLength:(NSUInteger)length
Is there any class/method can help me do this in Objective C? Thanks.
I want to read a list of integers from a text file, I just want to
write code like
int temp; fin>>temp;
You have a lot of choices for file access. If you want to use C++-style access, you can, but you naturally need to open the file using the appropriate C++ file or stream methods. If you use Objective-C++, though, you can easily mix C++ code into your Objective-C.
You can also use the C standard library file routines like fopen(), fread(), etc.
Using C or C++ to read files is often a good choice if the files are coming from a source other than your program, something beyond your control.
But when I read Cocoa document, I found NSFileHandle is suggested, and
there is no method like I assumed, only one related method:
Again, lots of choices. Yes, you can use NSFileHandle to read bytes from the file into a NSData object, and then you can get ranges of bytes out of the data object. A much more common way to write and read data, though, is to use NSKeyedArchiver and NSKeyedUnarchiver:
NSData *data = [NSData dataWithContentsOfFile:pathToFile];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
int age = [unarchiver decodeIntForKey:#"Age"];
int weight = [unarchiver decodeIntForKey:#"Weight"];
NSString *name = [unarchiver decodeObjectForKey:#"Name"];
That's just the tip of the iceberg, though. It seems like a lot of code compared to what you were looking for, but it can also be a lot less work. Because objects and their relationships can be stored and read, you can read in a complex graph of objects with very little code:
OrgChart *chart = [NSUnarchiver unarchiveObjectWithFile:pathToFile];
Another option is to use property lists, which are very easy to use, but limited in the data types that can be used.
If you want to learn more about these topics, read Archives and Serializations Programming Guide, Property List Programming Guide, and File System Programming Guide.
You could use Objective-C++ and iostreams as you're used to.
You could use the C I/O functions like fopen and fscanf. (Probably what I'd do.)
Apple provides NSScanner for parsing, but it only reads from a string, not from an input stream. If you really want to use it, first you'll have to read your whole file (or a large chunk) into an NSString (or an NSData and then convert that to NSString).
If you can require the text file to be in JSON format, you can read it in one gulp and use NSJSONSerialization to parse it.