What is the best practices for sharing methods across classes in an app? - objective-c

I'm still trying to wrap my head around how things should be done in the object-oriented world and I think my problem is that I don't understand how to best utilize encapsulation. Specifically, I have lots of small bits of code that I use in several classes in my project. For example:
+ (NSString *)getFormattedDate;
+ (NSString *)getResultsFilePath;
+ (NSError *)removeFileFromCache:(NSString *)fileName;
These are all 3-5 line methods that I use in more than one class. My standard practice has been to put these snippets into a Utility.inc file and call them when I need them. Is that appropriate in the object-oriented world or should each class be self-contained? And if it's appropriate, would you put the code into a singleton or just a regular class file and [[Utilities alloc] init] in each class where you want to use the methods?

Look into using Categories. For the examples you gave, these are methods related to objects of a particular class that happen to be used in several of your own classes. Categories will allow you to park these often used methods where they can be associated with the common factors.

Create a utitity singelton which will be created only ones and then used by the other classes.

Thanks for the answers. I'm not sure that this the right way to do things, but this is what I've done on the projects I just submitted.
I made two classes, one for Utility methods and one for globals. The methods in the Utilities class are all class methods since they operate on files and constants or globals. Then I made a singleton for global variables. I have all of my global constants in the .pch file. Also in the .pch file I put the following two lines of code so that the utilities and globals are available everywhere.
// File for utilities like file delete, find Documents file
#import "Utilities.h"
#import "Globals.h"
Accessing the methods is straightforward. Here's an example of a call to both methods to generate an HTML header for an email.
NSString *gameNameHeader = [NSString stringWithFormat:#"<p>&nbsp</p><h1>%# Results</h1><h2>%#%#</h2>",GAME_NAME_TITLE,[Utilities formattedClientName], [Utilities formattedDate]];
In case anyone can use it, here is my current version of the code. (Sorry for the formatting-I can't seem to get the wiki to cooperate.)
#interface Utilities : NSObject {
}
+ (NSString *)formattedDate;
+ (NSString *)formattedClientName;
+ (NSString *)cachedResultsFilePath;
+ (NSString *)cachedResultsFileContents;
+ (NSString *)resultsFileName;
+ (NSError *)removeFileFromCache:(NSString *)fileName;
+ (NSString *)applicationCachesDirectory;
+ (NSString *)applicationDocumentsDirectory;
+ (NSString *)applicationLibraryDirectory;
+ (NSError *)copyCachedResultsToFile;
#end
#import "Utilities.h"
#implementation Utilities {
}
+ (NSString *)formattedDate {
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd"];
NSString *todaysDate = [dateFormatter stringFromDate:[NSDate date]];
return todaysDate;
}
+ (NSString *)formattedClientName {
NSString *client = [NSString stringWithFormat:#" "];
if( [Globals sharedInstance].currentClient ) client = [NSString stringWithFormat:#" %# ",[Globals sharedInstance].currentClient];
return client;
}
+ (NSString *)cachedResultsFilePath {
NSString *resultsFilePath = [[self applicationCachesDirectory] stringByAppendingPathComponent:#"Results.txt"];
return resultsFilePath;
}
+ (NSString *)cachedResultsFileContents {
NSStringEncoding encoding; NSError* error = nil;
NSString *resultsText = [NSString stringWithContentsOfFile:[self cachedResultsFilePath] usedEncoding:&encoding error:&error];
return resultsText;
}
+ (NSString *)resultsFileName {
return [NSString stringWithFormat:#"%# Results%#%#.html",GAME_NAME_TITLE,[self formattedClientName],[self formattedDate] ];
}
+ (NSError *)removeFileFromCache:(NSString *)fileName {
NSError *error = nil;
NSFileManager *localFileManager=[[NSFileManager alloc] init];
NSString *fullPath = [NSString stringWithFormat:#"%#/%#", [self applicationCachesDirectory],fileName];
[localFileManager removeItemAtPath: fullPath error:&error ];
return error;
}
+ (NSString *)applicationCachesDirectory {
return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
}
+ (NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
+ (NSString *)applicationLibraryDirectory {
return [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
}
+ (NSError *)copyCachedResultsToFile {
// Grab the header and footer and put it around the cached data
NSStringEncoding encoding; NSError *error = nil;
NSString *htmlHeaderTextPath = [[NSBundle mainBundle] pathForResource:#"HTML_header" ofType:#"html" ];
NSString *htmlHeaderText = [NSString stringWithContentsOfFile:htmlHeaderTextPath usedEncoding:&encoding error:&error];
NSString *cachedResultsText = [NSString stringWithContentsOfFile:[self cachedResultsFilePath] usedEncoding:&encoding error:&error];
// Write the results to a file if there are any
if (cachedResultsText) {
NSString *htmlFooterTextPath = [[NSBundle mainBundle] pathForResource:#"HTML_footer" ofType:#"html" ];
NSString *htmlFooterText = [NSString stringWithContentsOfFile:htmlFooterTextPath usedEncoding:&encoding error:&error];
NSString *gameNameHeader = [NSString stringWithFormat:#"<h1>%# Results for%#%#</h1>",GAME_NAME_TITLE,[self formattedClientName],[self formattedDate] ];
NSString *tempStringP1 = [htmlHeaderText stringByAppendingString:gameNameHeader];
NSString *tempStringP2 = [tempStringP1 stringByAppendingString:cachedResultsText];
NSString *formattedTextForPrinting = [tempStringP2 stringByAppendingString:htmlFooterText];
NSString *resultsFilePath = [ [Utilities applicationDocumentsDirectory] stringByAppendingPathComponent:[self resultsFileName] ];
if ( !([[NSFileManager defaultManager] fileExistsAtPath:resultsFilePath]) ) {
if (! ([[NSFileManager defaultManager] createFileAtPath:resultsFilePath contents:nil attributes:nil]) ) {
NSLog(#"Error was code: %d - message: %s", errno, strerror(errno));
}
}
NSFileHandle *fileHandler = [NSFileHandle fileHandleForUpdatingAtPath:resultsFilePath];
[fileHandler writeData:[formattedTextForPrinting dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandler closeFile];
}
return error;
}
#end
Globals in a singleton. Probably not thread-safe, but I don't care right now.
#interface Globals : NSObject {
}
#property (nonatomic, strong) NSString *currentClient;
#property (nonatomic, strong) NSString *showmePict;
#property BOOL checkBoxes;
+ (Globals *)sharedInstance;
- (void)resetClient;
#end
#implementation Globals {
}
static Globals *singleton = nil;
#synthesize currentClient = _currentClient;
#synthesize showmePict = _showmePict;
#synthesize checkBoxes = _checkBoxes;
+(Globals *) sharedInstance {
NSLog (#"sharedInstance of Globals called.");
if (nil != singleton) return singleton;
static dispatch_once_t pred; // lock
dispatch_once(&pred, ^{ // this code is at most once
singleton = [[Globals alloc] init];
});
return singleton;
}
- (void)resetClient {
self.currentClient = nil;
}
#end

Related

I am trying to pull the value based on the key in a NSArray in Objective-C that are passed in a URL. How do I get the value form the key? [duplicate]

I have an NSURL:
serverCall?x=a&y=b&z=c
What is the quickest and most efficient way to get the value of y?
Thanks
UPDATE:
Since 2010 when this was written, it seems Apple has released a set of tools for that purpose. Please see the answers below for those.
Old-School Solution:
Well I know you said "the quickest way" but after I started doing a test with NSScanner I just couldn't stop. And while it is not the shortest way, it is sure handy if you are planning to use that feature a lot. I created a URLParser class that gets these vars using an NSScanner. The use is a simple as:
URLParser *parser = [[[URLParser alloc] initWithURLString:#"http://blahblahblah.com/serverCall?x=a&y=b&z=c&flash=yes"] autorelease];
NSString *y = [parser valueForVariable:#"y"];
NSLog(#"%#", y); //b
NSString *a = [parser valueForVariable:#"a"];
NSLog(#"%#", a); //(null)
NSString *flash = [parser valueForVariable:#"flash"];
NSLog(#"%#", flash); //yes
And the class that does this is the following (*source files at the bottom of the post):
URLParser.h
#interface URLParser : NSObject {
NSArray *variables;
}
#property (nonatomic, retain) NSArray *variables;
- (id)initWithURLString:(NSString *)url;
- (NSString *)valueForVariable:(NSString *)varName;
#end
URLParser.m
#implementation URLParser
#synthesize variables;
- (id) initWithURLString:(NSString *)url{
self = [super init];
if (self != nil) {
NSString *string = url;
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:#"&?"]];
NSString *tempString;
NSMutableArray *vars = [NSMutableArray new];
[scanner scanUpToString:#"?" intoString:nil]; //ignore the beginning of the string and skip to the vars
while ([scanner scanUpToString:#"&" intoString:&tempString]) {
[vars addObject:[tempString copy]];
}
self.variables = vars;
[vars release];
}
return self;
}
- (NSString *)valueForVariable:(NSString *)varName {
for (NSString *var in self.variables) {
if ([var length] > [varName length]+1 && [[var substringWithRange:NSMakeRange(0, [varName length]+1)] isEqualToString:[varName stringByAppendingString:#"="]]) {
NSString *varValue = [var substringFromIndex:[varName length]+1];
return varValue;
}
}
return nil;
}
- (void) dealloc{
self.variables = nil;
[super dealloc];
}
#end
*if you don't like copying and pasting you can just download the source files - I made a quick blog post about this here.
So many custom url parsers here, remember NSURLComponents is your friend!
Here is an example where I pull out a url encoded parameter for "page"
Swift
let myURL = "www.something.com?page=2"
var pageNumber : Int?
if let queryItems = NSURLComponents(string: myURL)?.queryItems {
for item in queryItems {
if item.name == "page" {
if let itemValue = item.value {
pageNumber = Int(itemValue)
}
}
}
}
print("Found page number: \(pageNumber)")
Objective-C
NSString *myURL = #"www.something.com?page=2";
NSURLComponents *components = [NSURLComponents componentsWithString:myURL];
NSNumber *page = nil;
for(NSURLQueryItem *item in components.queryItems)
{
if([item.name isEqualToString:#"page"])
page = [NSNumber numberWithInteger:item.value.integerValue];
}
"Why reinvent the wheel!" - Someone Smart
I'm pretty sure you have to parse it yourself. However, it's not too bad:
NSString * q = [myURL query];
NSArray * pairs = [q componentsSeparatedByString:#"&"];
NSMutableDictionary * kvPairs = [NSMutableDictionary dictionary];
for (NSString * pair in pairs) {
NSArray * bits = [pair componentsSeparatedByString:#"="];
NSString * key = [[bits objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString * value = [[bits objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[kvPairs setObject:value forKey:key];
}
NSLog(#"y = %#", [kvPairs objectForKey:#"y"]);
In Swift you can use NSURLComponents to parse the query string of an NSURL into an [AnyObject].
You can then create a dictionary from it (or access the items directly) to get at the key/value pairs. As an example this is what I am using to parse a NSURL variable url:
let urlComponents = NSURLComponents(URL: url, resolvingAgainstBaseURL: false)
let items = urlComponents?.queryItems as [NSURLQueryItem]
var dict = NSMutableDictionary()
for item in items{
dict.setValue(item.value, forKey: item.name)
}
println(dict["x"])
I've been using this Category: https://github.com/carlj/NSURL-Parameters.
It's small and easy to use:
#import "NSURL+Parameters.h"
...
NSURL *url = [NSURL URLWithString:#"http://foo.bar.com?paramA=valueA&paramB=valueB"];
NSString *paramA = url[#"paramA"];
NSString *paramB = url[#"paramB"];
You can use Google Toolbox for Mac.
It adds a function to NSString to convert query string to a dictionary.
http://code.google.com/p/google-toolbox-for-mac/
It works like a charm
NSDictionary * d = [NSDictionary gtm_dictionaryWithHttpArgumentsString:[[request URL] query]];
Here's a Swift 2.0 extension that provides simple access to parameters:
extension NSURL {
var params: [String: String] {
get {
let urlComponents = NSURLComponents(URL: self, resolvingAgainstBaseURL: false)
var items = [String: String]()
for item in urlComponents?.queryItems ?? [] {
items[item.name] = item.value ?? ""
}
return items
}
}
}
Sample usage:
let url = NSURL(string: "http://google.com?test=dolphins")
if let testParam = url.params["test"] {
print("testParam: \(testParam)")
}
I wrote a simple category to extend NSString/NSURL that lets you extract URL query parameters individually or as a dictionary of key/value pairs:
https://github.com/nicklockwood/RequestUtils
I did it using a category method based on #Dimitris solution
#import "NSURL+DictionaryValue.h"
#implementation NSURL (DictionaryValue)
-(NSDictionary *)dictionaryValue
{
NSString *string = [[self.absoluteString stringByReplacingOccurrencesOfString:#"+" withString:#" "]
stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:#"&?"]];
NSString *temp;
NSMutableDictionary *dict = [[[NSMutableDictionary alloc] init] autorelease];
[scanner scanUpToString:#"?" intoString:nil]; //ignore the beginning of the string and skip to the vars
while ([scanner scanUpToString:#"&" intoString:&temp])
{
NSArray *parts = [temp componentsSeparatedByString:#"="];
if([parts count] == 2)
{
[dict setObject:[parts objectAtIndex:1] forKey:[parts objectAtIndex:0]];
}
}
return dict;
}
#end
All of the current answers are version specific or needlessly wasteful. Why create a dictionary if you only want one value?
Here's a simple answer that supports all iOS versions:
- (NSString *)getQueryParam:(NSString *)name fromURL:(NSURL *)url
{
if (url)
{
NSArray *urlComponents = [url.query componentsSeparatedByString:#"&"];
for (NSString *keyValuePair in urlComponents)
{
NSArray *pairComponents = [keyValuePair componentsSeparatedByString:#"="];
NSString *key = [[pairComponents firstObject] stringByRemovingPercentEncoding];
if ([key isEqualToString:name])
{
return [[pairComponents lastObject] stringByRemovingPercentEncoding];
}
}
}
return nil;
}
You can do that easy :
- (NSMutableDictionary *) getUrlParameters:(NSURL *) url
{
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
NSString *tmpKey = [url query];
for (NSString *param in [[url query] componentsSeparatedByString:#"="])
{
if ([tmpKey rangeOfString:param].location == NSNotFound)
{
[params setValue:param forKey:tmpKey];
tmpKey = nil;
}
tmpKey = param;
}
[tmpKey release];
return params;
}
It return Dictionary like it : Key = value
I edited Dimitris' code slightly for better memory management and efficiency. Also, it works in ARC.
URLParser.h
#interface URLParser : NSObject
- (void)setURLString:(NSString *)url;
- (NSString *)valueForVariable:(NSString *)varName;
#end
URLParser.m
#import "URLParser.h"
#implementation URLParser {
NSMutableDictionary *_variablesDict;
}
- (void)setURLString:(NSString *)url {
[_variablesDict removeAllObjects];
NSString *string = url;
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:#"&?"]];
NSString *tempString;
[scanner scanUpToString:#"?" intoString:nil]; //ignore the beginning of the string and skip to the vars
while ([scanner scanUpToString:#"&" intoString:&tempString]) {
NSString *dataString = [tempString copy];
NSArray *sepStrings = [dataString componentsSeparatedByString:#"="];
if ([sepStrings count] == 2) {
[_variablesDict setValue:sepStrings[1] forKeyPath:sepStrings[0]];
}
}
}
- (id)init
{
self = [super init];
if (self) {
_variablesDict = [[NSMutableDictionary alloc] init];
}
return self;
}
- (NSString *)valueForVariable:(NSString *)varName {
NSString *val = [_variablesDict valueForKeyPath:varName];
return val;
return nil;
}
-(NSString *)description {
return [NSString stringWithFormat:#"Current Variables: %#", _variablesDict];
}
#end
Quickest is:
NSString* x = [url valueForQueryParameterKey:#"x"];

run applescript from cocoa app stopped working

This code had been working fine until just recently. I hadn't' changed anything nor upgraded my system and I'm completely flummoxed.
I've been using it for 6 years and now it dies on me.
Is there an easier or better way of running an applescript from within a cocoa application? At this point I'm happy to pay to fix this problem!
utils.h
#import <Foundation/Foundation.h>
#interface Utils : NSObject
// Runs an applescript with a given map of variables (name/value)
+ (NSArray *)runApplescript:(NSString *)source withVariables:(NSDictionary *)variables;
// Runs an applescript from a file pathwith a given map of variables
// (name/value)
+ (NSArray *)runApplescriptFromFile:(NSString *)scriptName withVariables:(NSDictionary *)variables;
+ (NSArray *)arrayFromDescriptor:(NSAppleEventDescriptor *)descriptor;
// String is empty or only has white characters (space, tab...)
+ (BOOL)stringIsEmptyOrWhite:(NSString *)string;
#end
Utils.M
#import "Utils.h"
#implementation Utils
+ (NSArray *)arrayFromDescriptor:(NSAppleEventDescriptor *)descriptor {
// Enumerate the apple descriptors (lists) returned by the applescript and
// make them into arrays
NSMutableArray *returnArray = [NSMutableArray array];
NSInteger counter, count = [descriptor numberOfItems];
for (counter = 1; counter <= count; counter++) {
NSAppleEventDescriptor *desc = [descriptor descriptorAtIndex:counter];
if (nil != [desc descriptorAtIndex:1]) {
[returnArray addObject:[Utils arrayFromDescriptor:desc]];
} else {
NSString *stringValue = [[descriptor descriptorAtIndex:counter] stringValue];
if (nil != stringValue) {
[returnArray addObject:stringValue];
} else {
[returnArray addObject:#""];
}
}
}
return returnArray;
}
+ (NSString *)escapeCharacters:(NSString *)string {
return [string stringByReplacingOccurrencesOfString:#"\"" withString:#"\\\""];
}
+ (NSArray *)runApplescript:(NSString *)source withVariables:(NSDictionary *)variables {
NSString *input = #"";
NSArray *variableNames = [variables allKeys];
// Transform the dictionary of names/values to set sentences of applescript
for (NSString *variableName in variableNames) {
NSObject *variableValue = [variables objectForKey:variableName];
if ([variableValue isKindOfClass:[NSString class]]) {
input =
[input stringByAppendingString:[NSString stringWithFormat:#"set %# to (\"%#\" as text)\n", variableName,
[Utils escapeCharacters:variableValue], nil]];
} else if ([variableValue isKindOfClass:[NSNumber class]]) {
input = [input stringByAppendingString:[NSString stringWithFormat:#"set %# to (%# as integer)\n",
variableName, variableValue, nil]];
} else if ([variableValue isKindOfClass:[NSArray class]]) {
// Initialize a list
NSString *entry;
NSArray *values = (NSArray *)variableValue;
input = [input stringByAppendingString:[NSString stringWithFormat:#"set %# to {", variableName]];
BOOL first = TRUE;
for (entry in values) {
if (!first) {
input = [input stringByAppendingString:#", "];
}
input = [input
stringByAppendingString:[NSString stringWithFormat:#"\"%#\"", [Utils escapeCharacters:entry], nil]];
first = FALSE;
}
input = [input stringByAppendingString:#"}\n"];
}
}
NSString *finalScript = [input stringByAppendingString:[NSString stringWithFormat:#"\n\n%#", source]];
NSLog(#"Final script: %#", finalScript);
NSAppleScript *script = [[NSAppleScript alloc] initWithSource:finalScript];
NSDictionary *error;
NSAppleEventDescriptor *descriptor = [script executeAndReturnError:&error];
NSLog(#"applescript error: %#", [error description]);
// Transform the return value of applescript to nested nsarrays
return [Utils arrayFromDescriptor:descriptor];
}
+ (NSArray *)runApplescriptFromFile:(NSString *)scriptName withVariables:(NSDictionary *)variables {
NSString *scriptPath = [[NSBundle mainBundle] pathForResource:scriptName ofType:#"applescript"];
NSString *scriptSource =
[[NSString alloc] initWithContentsOfFile:scriptPath encoding:NSASCIIStringEncoding error:nil];
return [Utils runApplescript:scriptSource withVariables:variables];
}
+ (BOOL)stringIsEmptyOrWhite:(NSString *)string {
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
return [string isEqualToString:#""];
}
#end
Easier, yes, although whether that’s your actual problem is another question.
http://appscript.sourceforge.net/asoc.html
I assume you’ve already got other details, including sandboxing and hardening settings and plist entries, taken care of. (Recent Xcode upgrades also had a habit of breaking it when auto-upgrading your project files, by turning on hardening for you so Apple events can’t get out.)

"The data couldn’t be written because it isn’t in the correct format" occurs while archiving a custom class with NSKeyedArchiver

I was trying a new API in iOS12:
[NSKeyedArchiver archivedDataWithRootObject:<#(nonnull id)#> requiringSecureCoding:<#(BOOL)#> error:<#(NSError * _Nullable __autoreleasing * _Nullable)#>]
What I was trying to do is very simple, archive a custom class, here is the code:
A class named Cat:
#interface Cat : NSObject <NSCoding>
#property (nonatomic, copy) NSString *name;
#property (nonatomic, assign) NSInteger age;
+ (void)saveThisCat:(Cat *)cat;
+ (Cat *)getThisCat;
#end
#implementation Cat
- (void)encodeWithCoder:(nonnull NSCoder *)aCoder {
[aCoder encodeObject:self.name forKey:#"name"];
[aCoder encodeInteger:self.age forKey:#"age"];
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder {
if (self = [super init]) {
self.name = [aDecoder decodeObjectForKey:#"name"];
self.age = [aDecoder decodeIntegerForKey:#"age"];
}
return self;
}
+ (void)saveThisCat:(Cat *)cat {
NSError *error = nil;
NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [docPath stringByAppendingPathComponent:#"cat.plist"];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:cat requiringSecureCoding:YES error:&error];
// ***Error occurs here!!!***
NSLog(#"=== Error Info: %# ===", [error localizedDescription]);
[data writeToFile:filePath atomically:YES];
}
+ (Cat *)getThisCat {
NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [docPath stringByAppendingPathComponent:#"cat.plist"];
Cat *retVal = [NSKeyedUnarchiver unarchivedObjectOfClass:[Cat class] fromData:[NSData dataWithContentsOfFile:filePath] error:nil];
return retVal;
}
#end
Usage:
Cat *totoro = [Cat new];
totoro.name = #"totoro";
totoro.age = 1;
NSLog(#"=== The cat's name is %#", totoro.name);
NSLog(#"=== The cat's age is %d", totoro.age);
[Cat saveThisCat:totoro];
Cat *resultCat = [Cat getThisCat];
NSLog(#"=== The cat's name is %#", resultCat.name);
NSLog(#"=== The cat's age is %d", resultCat.age);
And the error info (Generated by using archivedDataWithRootObject while executing saveThisCat method)
=== Error Info: The data couldn’t be written because it isn’t in the correct format. ===
Is there anything wrong? Please point it out, thanks a lot!
You have to adopt NSSecureCoding
#interface Cat : NSObject <NSSecureCoding>
and in the implementation add the required class property
+ (BOOL)supportsSecureCoding {
return YES;
}
When you call unarchivedObjectOfClass, use the error parameter and it will supply an NSError:
Error Domain=NSCocoaErrorDomain Code=4864 "This decoder will only decode classes that adopt NSSecureCoding. Class 'Cat' does not adopt it." UserInfo={NSDebugDescription=This decoder will only decode classes that adopt NSSecureCoding. Class 'Cat' does not adopt it.}
As vadian said, adopt NSSecureCoding.
Whenever you have a failure, always avail yourself of available NSError parameters.

How to manage data in a iOS app

I'm developing an app where I have some data. It would be some strings I want to use.
The issue is I would like to initialize with the data of a file. something like:
#property (strong, nonatomic) Cow *cow;
_cow = [[Cow alloc] init (NSString *) #"Some string"];
I have the proper constructor for cow that accepts an string .
What I want is the way I can initialize severals cows with different strings and repeat some values if I want.
I was thinking in a text file where I have all the strings, and just pick those I want.
Another possibility is a db like SQlite.
Reading your comments to other answers, it seems that you are interested in an object graph persistence mechanism for your application. The answer given by #Greg - object graph serialization - is excellent. In the default case, you'd be reading your entire object graph into memory, which may or may not be what you want. (There are ways of dealing with that too.) You asked about how to choose data you want. In the case where your Cow's are in memory, you can find your Cow of choice in a lot of ways. For example:
#import <Foundation/Foundation.h>
#interface Cow : NSObject
#property (nonatomic,copy) NSString *color;
#end
#implementation Cow
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
Cow *brownCow = [Cow new];
brownCow.color = #"brown";
Cow *whiteCow = [Cow new];
whiteCow.color = #"white";
NSArray *cows = #[brownCow,whiteCow];
NSUInteger cowIndex = [cows indexOfObjectPassingTest:^BOOL(Cow *obj, NSUInteger idx, BOOL *stop) {
return [[obj color] isEqualToString:#"brown"];
}];
if( cowIndex != NSNotFound )
NSLog(#"Your brown cow is: %#",cows[cowIndex]);
else
NSLog(#"No brown cow");
}
}
But there are other object persistence frameworks that you may consider. For example, Core Data is commonly used. sqlite3 is another possibility, which when used with an object-oriented abstraction layer like FMDB is another good choice.
The answer boils down to your requirements. How many Cow objects might there be? What are the performance requirements? And so on.
You can make your object Cow conform to a NSCoding protocol:
//Cow.h file
#interface Cow : NSObject <NSCoding>
#property(nonatomic, copy) NSString *someString;
#end
//Cow.m
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
_someString = [aDecoder decodeObjectForKey:#"someString"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.someString forKey:#"someString"];
}
And after that you can save it to a file (you can create array of Cow objects and save it to a file):
NSrray *data = #[cow1, cow2, cow3];
[NSKeyedArchiver archiveRootObject:data toFile:[self pathToFile]];
And you can read it like that:
NSFileManager* fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:[self pathToData]])
{
NSData *data = [NSData dataWithContentsOfFile:[self pathToData]];
NSMutableArray *dataArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
// dataArray -> array with your data
}
Hopefully I understood your question.
//EDITED
Get file to path:
-(NSString*)pathToData
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"MyData"];
NSFileManager* fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:filePath])
{
[fileManager createDirectoryAtPath:filePath withIntermediateDirectories:NO attributes:nil error:nil];
}
return [filePath stringByAppendingPathComponent:#"mydata.plist"];
}
Save and remove data:
-(BOOL)save
{
NSMutableArray *data = [self cachedDataFromFile];
if (!data)
{
NSLog(#"data = nil");
data = [NSMutableArray arrayWithArray:#[self]];
}
else
[data addObject:self];
return [NSKeyedArchiver archiveRootObject:data toFile:[self pathToData]];
}
-(void)removeFromFile
{
NSMutableArray *data = [self cachedDataFromFile];
if (!data)
return;
else
{
[data removeObject:self];
[NSKeyedArchiver archiveRootObject:data toFile:[self pathToData]];
}
}
-(NSMutableArray*)cachedDataFromFile
{
NSFileManager* fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:[self pathToData]])
{
NSData *data = [NSData dataWithContentsOfFile:[self pathToData]];
NSMutableArray *dataArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
return dataArray;
}
return nil;
}
Let me know is it work for you.

Converting NSObject to NSDictionary

Hello I a class of type NSObject:
ProductDetails *details = [[ProductDetails alloc] init];
details.name = #"Soap1";
details.color = #"Red";
details.quantity = 4;
I want to pass the "details" object to a dictionary.
I did,
NSDictionary *dict = [NSDictionary dictionaryWithObject:details forKey:#"details"];
I am passing this dict to another method which performs a check on JSONSerialization:
if(![NSJSONSerialization isValidJSONObject:dict])
And I am getting a crash on this check. Am I doing anything wrong here? I know that the details I am getting is a JSON object and I am assigning it to the properties in my ProductDetails class.
Please help me. I am a noob in Objective-C.
I now tried:
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:(NSData*)details options:kNilOptions error:&error];
All I need here is an easy way to convert details to NSData.
I noticed that I have an array inside my object may be thats why all the ways I tried is throwing an exception. However since this question is becoming to big, I have started an another question thread for it where I have displayed the data I am getting inside the object - https://stackoverflow.com/questions/19081104/convert-nsobject-to-nsdictionary
This may well be the easiest way to achieve it. Do import #import <objc/runtime.h> in your class file.
#import <objc/runtime.h>
ProductDetails *details = [[ProductDetails alloc] init];
details.name = #"Soap1";
details.color = #"Red";
details.quantity = 4;
NSDictionary *dict = [self dictionaryWithPropertiesOfObject: details];
NSLog(#"%#", dict);
//Add this utility method in your class.
- (NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
[dict setObject:[obj valueForKey:key] forKey:key];
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
NSDictionary *details = {#"name":product.name,#"color":product.color,#"quantity":#(product.quantity)};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:details
options:NSJSONWritingPrettyPrinted // Pass 0 if you don't care about the readability of the generated string
error:&error];
if (! jsonData) {
NSLog(#"Got an error: %#", error);
} else {
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
Second part's source: Generate JSON string from NSDictionary in iOS
As mmackh said, you want to define a custom method for your ProductDetails object that will return a simple NSDictionary of values, e.g.:
#implementation ProductDetails
- (id)jsonObject
{
return #{#"name" : self.name,
#"color" : self.color,
#"quantity" : #(self.quantity)};
}
...
Let's assume that we added manufacturer property to our ProductDetails, which referenced a ManufacturerDetails class. We'd just write a jsonObject for that class, too:
#implementation ManufacturerDetails
- (id)jsonObject
{
return #{#"name" : self.name,
#"address1" : self.address1,
#"address2" : self.address2,
#"city" : self.city,
...
#"phone" : self.phone};
}
...
And then change the jsonObject for ProductDetails to employ that, e.g.:
#implementation ProductDetails
- (id)jsonObject
{
return #{#"name" : self.name,
#"color" : self.color,
#"quantity" : #(self.quantity),
#"manufacturer" : [self.manufacturer jsonObject]};
}
...
If you have potentially nested collection objects (arrays and/or dictionaries) with custom objects that you want to encode, you could write a jsonObject method for each of those, too:
#interface NSDictionary (JsonObject)
- (id)jsonObject;
#end
#implementation NSDictionary (JsonObject)
- (id)jsonObject
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([obj respondsToSelector:#selector(jsonObject)])
[dictionary setObject:[obj jsonObject] forKey:key];
else
[dictionary setObject:obj forKey:key];
}];
return [NSDictionary dictionaryWithDictionary:dictionary];
}
#end
#interface NSArray (JsonObject)
- (id)jsonObject;
#end
#implementation NSArray (JsonObject)
- (id)jsonObject
{
NSMutableArray *array = [NSMutableArray array];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj respondsToSelector:#selector(jsonObject)])
[array addObject:[obj jsonObject]];
else
[array addObject:obj];
}];
return [NSArray arrayWithArray:array];
}
#end
If you do something like that, you can now convert arrays or dictionaries of your custom objects object into something that can be used for generating JSON:
NSArray *products = #[[[Product alloc] initWithName:#"Prius" color:#"Green" quantity:3],
[[Product alloc] initWithName:#"Accord" color:#"Black" quantity:1],
[[Product alloc] initWithName:#"Civic" color:#"Blue" quantity:2]];
id productsJsonObject = [products jsonObject];
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:productsJsonObject options:0 error:&error];
If you're simply trying to save these objects in a file, I'd suggest NSKeyedArchiver and NSKeyedUnarchiver. But if you need to generate JSON objects for your own private classes, you can do something like the above might work.
In .h File
#import <Foundation/Foundation.h>
#interface ContactDetail : NSObject
#property (nonatomic) NSString *firstName;
#property (nonatomic) NSString *lastName;
#property (nonatomic) NSString *fullName;
#property (nonatomic) NSMutableArray *mobileNumbers;
#property (nonatomic) NSMutableArray *Emails;
#property (assign) bool Isopen;
#property (assign) bool IsChecked;
-(NSDictionary *)dictionary;
#end
in .m file
#import "ContactDetail.h"
#import <objc/runtime.h>
#implementation ContactDetail
#synthesize firstName;
#synthesize lastName;
#synthesize fullName;
#synthesize mobileNumbers;
#synthesize Emails;
#synthesize IsChecked,Isopen;
//-(NSDictionary *)dictionary {
// return [NSDictionary dictionaryWithObjectsAndKeys:self.fullName,#"fullname",self.mobileNumbers,#"mobileNumbers",self.Emails,#"emails", nil];
//}
- (NSDictionary *)dictionary {
unsigned int count = 0;
NSMutableDictionary *dictionary = [NSMutableDictionary new];
objc_property_t *properties = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
id value = [self valueForKey:key];
if (value == nil) {
// nothing todo
}
else if ([value isKindOfClass:[NSNumber class]]
|| [value isKindOfClass:[NSString class]]
|| [value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSMutableArray class]]) {
// TODO: extend to other types
[dictionary setObject:value forKey:key];
}
else if ([value isKindOfClass:[NSObject class]]) {
[dictionary setObject:[value dictionary] forKey:key];
}
else {
NSLog(#"Invalid type for %# (%#)", NSStringFromClass([self class]), key);
}
}
free(properties);
return dictionary;
}
#end
if any crash ,You check the property (NSMutableArray,NSString,etc ) in else if condition inside of for.
In Your Controller, in any func...
-(void)addItemViewController:(ConatctViewController *)controller didFinishEnteringItem:(NSMutableArray *)SelectedContact
{
NSLog(#"%#",SelectedContact);
NSMutableArray *myData = [[NSMutableArray alloc] init];
for (ContactDetail *cont in SelectedContact) {
[myData addObject:[cont dictionary]];
}
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:myData options:NSJSONWritingPrettyPrinted error:&error];
if ([jsonData length] > 0 &&
error == nil){
// NSLog(#"Successfully serialized the dictionary into data = %#", jsonData);
NSString *jsonString = [[NSString alloc] initWithData:jsonData
encoding:NSUTF8StringEncoding];
NSLog(#"JSON String = %#", jsonString);
}
else if ([jsonData length] == 0 &&
error == nil){
NSLog(#"No data was returned after serialization.");
}
else if (error != nil){
NSLog(#"An error happened = %#", error);
}
}
Try this:
#import <objc/runtime.h>
+ (NSDictionary *)dictionaryWithPropertiesOfObject:(id)obj {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
[dict setObject:[obj valueForKey:key] ? [obj valueForKey:key] : #"" forKey:key];
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
The perfect way to do this is by using a library for serialization/deserialization
many libraries are available but one i like is
JagPropertyConverter
https://github.com/jagill/JAGPropertyConverter
it can convert your Custom object into NSDictionary and vice versa
even it support to convert dictionary or array or any custom object within your object (i.e Composition)
JAGPropertyConverter *converter = [[JAGPropertyConverter alloc]init];
converter.classesToConvert = [NSSet setWithObjects:[ProductDetails class], nil];
//For Object to Dictionary
NSDictionary *dictDetail = [converter convertToDictionary:detail];
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:dictDetail options:NSJSONWritingPrettyPrinted error:&error];
You can convert object (say modelObject) to dictionary at runtime with the help of objc/runtime.h class but that has certain limitations and is not recommended.
Considering MVC, mapping logic should be implemented in Model class.
#interface ModelObject : NSObject
#property (nonatomic) NSString *p1;
#property (nonatomic) NSString *p2;
-(NSDictionary *)dictionary;
#end
#import "ModelObject.h"
#implementation ModelObject
-(NSDictionary *)dictionary
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setValue:self.p1 forKey:#"p1"];// you can give different key name here if you want
[dict setValue:self.p2 forKey:#"p2" ];
return dict;
}
#end
Uses:
NSDictionary *modelObjDict = [modelObj dictionary];
Try using
NSDictionary *dict = [details valuesForAttributes:#[#"name", #"color"]];
And compare what the dictionary contains. Then try to convert it to JSON. And look at the JSON spec - what data types can go into a JSON encoded file?
You also can use the NSObject+APObjectMapping category which is available on GitHub: https://github.com/aperechnev/APObjectMapping
It's a quit easy. Just describe the mapping rules in your class:
#import <Foundation/Foundation.h>
#import "NSObject+APObjectMapping.h"
#interface MyCustomClass : NSObject
#property (nonatomic, strong) NSNumber * someNumber;
#property (nonatomic, strong) NSString * someString;
#end
#implementation MyCustomClass
+ (NSMutableDictionary *)objectMapping {
NSMutableDictionary * mapping = [super objectMapping];
if (mapping) {
NSDictionary * objectMapping = #{ #"someNumber": #"some_number",
#"someString": #"some_string" };
}
return mapping
}
#end
And then you can easily map your object to dictionary:
MyCustomClass * myObj = [[MyCustomClass alloc] init];
myObj.someNumber = #1;
myObj.someString = #"some string";
NSDictionary * myDict = [myObj mapToDictionary];
Also you can parse your object from dictionary:
NSDictionary * myDict = #{ #"some_number": #123,
#"some_string": #"some string" };
MyCustomClass * myObj = [[MyCustomClass alloc] initWithDictionary:myDict];
Swift
Now the swift is very popular and most of the SDK's are written in Objective C, we need to convert NSObject to NSDictionary, With the Help of #thatzprem Answer, I wrote an extension for Swift which will convert our NSObject into NSDictionary, then we can use that NSDictionary to simple Dictionary or JSON Object or other purpose. I hope so this will help out the Swift User.
extension NSObject {
func convertNSObjectToNSDictionary() -> [AnyHashable : Any]? {
var dict: [AnyHashable : Any] = [:]
var count: UInt32 = 0
let properties = class_copyPropertyList(type(of: self), UnsafeMutablePointer<UInt32>(mutating: &count)) //as? objc_property_t
for i in 0..<Int(count) {
var key: String? = nil
if let property = properties?[i] as? objc_property_t {
key = String(utf8String: property_getName(property))
}
//dict[key] = (obj as? NSObject)?.value(forKey: key ?? "")
dict[key] = (self).value(forKey: key ?? "")
}
free(properties)
return dict
}
}