iPhone - writign a class that can be instanciated many times, each instance being able to access a shared property - iphone

I know this can be done with many languages, but I don't see how to do it using Objective-C. I've read about singletons but as they are designed to be instanciated only once, they do not feed this need.
So this class could be called like this :
MyClass* obj1 = [[MyClass alloc] initWithKey:#"oneKey"];
NSString* lib = obj1.lib;
or
int id = [MyClass idForKey:#"anotherKey"];
I've tried this code but I'm pretty sure it's really bad, but I don't see how to achieve this :
.h file
#interface MyClass : NSObject {
NSString* key;
}
#property(nonatomic, retain) NSString* key;
#property(nonatomic, readonly) int id;
#property(nonatomic, readonly) NSString* lib;
#property(nonatomic, readonly) int value;
+ (id) classWithKey:(NSString*)theKey;
#end
.m file
#import "MyClass.h"
#interface MyClass.h (Private)
-(id)initWithKey:(NSString*)theKey;
#end
#implementation MyClass
#synthesize key;
static NSMutableDictionary* vars = nil;
-(id)init
{
if (!(self = [super init])) return nil;
self.key = nil;
[MyClass initVars];
return self;
}
-(id)initWithKey:(NSString*)theKey
{
if (!(self = [super init])) return nil;
self.key = theKey;
[MyClass initVars];
return self;
}
+ (id) classWithKey:(NSString*) theKey
{
return [[[MyClass alloc] initWithKey:theKey] autorelease];
}
+(void)initVars
{
if (vars != nil) return;
#define mNum(x) [NSNumber numberWithInt:x]
#define k0 #"id"
#define k1 #"lib"
#define k2 #"val"
vars = [NSMutableDictionary dictionary];
[vars setObject:[NSDictionary dictionaryWithObjectsAndKeys:mNum(5), k0, #"One value", k1, mNum(0), k2, nil] forKey:#"oneKey"];
[vars setObject:[NSDictionary dictionaryWithObjectsAndKeys:mNum(8), k0, #"Another value", k1, mNum(1), k2, nil] forKey:#"anotherKey"];
...
[vars retain];
}
- (int)id { return [[[vars objectForKey:self.key] objectForKey:k0] intValue]; }
- (NSString*)lib { return [[vars objectForKey:self.key] objectForKey:k1]; }
- (int)value { return [[[vars objectForKey:self.key] objectForKey:k2] intValue]; }
-(void)dealloc
{
self.key = nil;
[vars release];
[super dealloc];
}
+(int) idForKey:(NSString*)theKey
{
if (vars == nil) [self initVars];
return [[[vars objectForKey: theKey] objectForKey:k0] intValue];
}
#end

take a look at singleton class concept
there are a lot of answer for singletons, just search
here's' one:
Is this really a singleton?

Related

Memory management of container classes

I've made a container class to store a single tweet. Its initialized by passing in a dictionary object which is a single tweet.
I then store an array of these 'tweets' which I process through to display in a table.
The project is now finished and I am reviewing everything at the moment and I was wondering is there a better way to do this in the future. Is the memory handled correctly. I declare the string member vars with 'copy' and later in the dealloc I use a 'release' rather than just setting them to 'nil'.
Is my init ok or could that be improved?
Tweet.h
#import
#interface Tweet : NSObject
{
NSString * _userName;
NSString * _tweetText;
NSString * _tweetURL;
}
#property (nonatomic, copy) NSString * userName;
#property (nonatomic, copy) NSString * tweetText;
#property (nonatomic, copy) NSString * tweetURL;
- (id) initWithDict:(NSDictionary *)productsDictionary;
#end
Tweet.m
#implementation Tweet
#synthesize userName = _userName;
#synthesize tweetText = _tweetText;
#synthesize tweetURL = _tweetURL;
- (id) initWithDict:(NSDictionary *)productsDictionary
{
NSDictionary *aDict = [productsDictionary objectForKey:#"user"];
self.userName = [aDict objectForKey:#"screen_name"];
self.tweetText = [productsDictionary objectForKey:#"text"];
NSRange match;
match = [self.tweetText rangeOfString: #"http://"];
if (match.location != NSNotFound)
{
NSString *substring = [self.tweetText substringFromIndex:match.location];
NSRange match2 = [substring rangeOfString: #" "];
if (match2.location == NSNotFound)
{
self.tweetURL = substring;
}
else
{
self.tweetURL = [substring substringToIndex:match2.location];
}
}
else
{
self.tweetURL = nil;
}
return self;
}
-(void) dealloc
{
[self.tweetText release];
[self.tweetURL release];
[self.userName release];
[super dealloc];
}
#end
Many Thanks,
Code
At first sight, I see no inherent flaws here. That looks fine. I would prefer to do:
-(void) dealloc
{
[_tweetText release];
[_tweetURL release];
[_userName release];
[super dealloc];
}
But what you do is good as well.

return a static const []

So in my model I have the following code... I am successfully able to return each individual value. I want to know how am I able to return the entire speakerTable []... Maybe some advice. Thanks!
typedef struct {
NSUInteger speakerID;
NSString * speakerName;
NSString * speakerPosition;
NSString * speakerCompany;
} SpeakerEntry;
static const SpeakerEntry speakerTable [] =
{
{0, #"name", #"position", #"company"},
{1, #"name", #"position", #"company"},
{-1, nil, nil, nil}
};
This works correctly...
-(NSString *) stringSpeakerCompanyForId:(NSUInteger) identifier{
NSString * returnString = nil;
if ([self helpCount] > identifier) {
returnString = speakerTable[identifier].speakerCompany;
}
return returnString;
}
This does not work at all..
-(id) getSpeaker{
//if ([speakerTable[0].speakerName isKindOfClass:[NSString class]])
// NSLog(#"YES");
NSArray * myArray3 = [NSArray arrayWithArray:speakerTable];
return myArray3;
}
arrayWithArray expects an NSArray, not a C array.
The first one works because you are using it like a C array.
Alternatively - don't use a struct, use an object instead:
Create a class called Speaker.
In Speaker.h
#interface Speaker : NSObject {}
#property (nonatomic, assign) NSUinteger id;
#property (nonatomic, copy) NSString name;
#property (nonatomic, copy) NSString position;
#property (nonatomic, copy) NSString company;
- (void)initWithId:(NSUInteger)anId name:(NSString *)aName position:(NSString *)aPosition company:(NSString *)aCompany;
#end
in Speaker.m
#import "Speaker.h"
#implementation Speaker
#synthesize id, name, position, company;
- (void)initWithId:(NSUInteger)anId name:(NSString *)aName position:(NSString *)aPosition company:(NSString *)aCompany {
if (!([super init])) {
return nil;
}
id = anId;
NSString name = [[NSString alloc] initWithString:aName];
NSString position = [[NSString alloc] initWithString:aPosition];
NSString company = [[NSString alloc] initWithString:aCompany];
return self;
}
- (void)dealloc {
[name release];
[position release];
[company release];
[super dealloc];
}
#end
And now in your calling code you can create an immutable array of speakers with:
Speaker *speaker0 = [[Speaker alloc] initWithId:0 name:#"name0" position:#"position0" company:#"company0"];
Speaker *speaker1 = [[Speaker alloc] initWithId:1 name:#"name1" position:#"position1" company:#"company1"];
Speaker *speakerNull = [[Speaker alloc] initWithId:-1 name:nil position:nil company:nil];
NSArray *speakerArray [[NSArray arrayWithObjects: speaker0, speaker1, speakerNull] retain]
[speaker0 release];
[speaker1 release];
[speakerNull release];
note: this is typed straight in, so feel free to mention/correct typos or errors
The method arrayWithArray takes in an NSArray as an argument, not a C array.

invalid CFArrayRef problem with Singleton object

I've built a singleton object to manage some data in my app
#interface MyCommon : NSObject {
NSArray *quizz;
int iCurrentQuestion;
};
+ (MyCommon *)singleton;
#property (retain) NSArray *quizz;
#property (assign) int iCurrentQuestion;
#end
MyCommon.m
#import "MyCommon.h"
// MyCommon.m:
#implementation MyCommon
static MyCommon * MyCommon_Singleton = nil;
#synthesize iCurrentQuestion;
+ (MyCommon *)singleton
{
if (nil == MyCommon_Singleton)
{
MyCommon_Singleton = [[MyCommon alloc] init];
NSLog(#"allocating MyCommon_Singleton at %#",MyCommon_Singleton);
}
else {
NSLog(#"accessing singleton : %#", MyCommon_Singleton);
}
return MyCommon_Singleton;
}
- (NSArray*) getQuizz{
return quizz;
}
- (void) setQuizz:(NSArray *)array {
quizz = [NSArray arrayWithArray:array];
NSLog(#"setQuizz : %#",quizz);
}
There is no problem for writing the quizz object (setQuizz), however when I try to access it for reading, I get a crash : the quizz looks invalid and Xcode notify me an invalid CFArrayRef
I don't know what's wrong with my code.
You provide a custom setter for quizz but it doesn't comply with how the property is declared.
You're not retaining quizz when you're setting a new value. It's likely to be released just after, leading to a crash when you access it.
You should write
- (void)setQuizz:(NSArray *)array {
if (quizz != array) {
NSArray *tmp = quizz;
quizz = [array retain]; // retain the new value
[tmp release]; // release the old one
}
NSLog(#"setQuizz : %#",quizz);
}
this is way more code than it needs to be. First if you are going to be providing your own method you should declare so in the #property declaration which you didn't. Also your not properly retaining your variables. Additionally you should be using dispatch_once() for a thread safe & fast way to guarantee the singleton is only created once.
#interface MyCommon : NSObject {}
#property(nonatomic, retain) NSArray *quiz;
#property (assign) int iCurrentQuestion;
+ (MyCommon *)singleton;
#end
#implementation MyCommon
#synthesize quiz;
#synthesize iCurrentQuestion;
-(id)init {
self = [super init];
if(self) {
quiz = [[NSMutableArray alloc init];
iCurrentQuestion = 0;
}
return self;
}
+ (MyCommon *)singleton {
static MyCommon *singleton = nil;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
singleton = [[MyCommon alloc] init];
});
return singleton;
}
#end
then you just do
[MyCommon singleton].quiz = //some array

How to use NSCoder

I am developing iphone application.
I use NSCoder.
MyApplication.h
#define ITEMS_KEY #"items"
#define CATEGORIES_KEY #"categories"
#import <UIKit/UIKit.h>
#interface MyApplicationData : NSObject <NSCoding, NSCopying> {
NSMutableArray* items;
NSMutableArray* categories;
}
#property (nonatomic ,retain) NSMutableArray* items;
#property (nonatomic, retain) NSMutableArray* categories;
#end
Myapplication.m
#import "MyApplicationData.h"
#implementation MyApplicationData
#synthesize items;
#synthesize categories;
#pragma mark NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:items forKey:ITEMS_KEY];
[aCoder encodeObject:categories forKey:CATEGORIES_KEY];
}
-(id)initWithCoder:(NSCoder *)aDecoder{
if(self = [super init]){
self.items = [aDecoder decodeObjectForKey:ITEMS_KEY];
self.categories = [aDecoder decodeObjectForKey:CATEGORIES_KEY];
}
return self;
}
#pragma mark -
#pragma mark NSCopying
-(id)copyWithZone:(NSZone *)zone{
MyApplicationData* copy = [[[self class]allocWithZone:zone]init];
items = [self.items copy];
categories = [self.categories copy];
return copy;
}
#end
But warnning.
'NSCoder' may not respond to '-decodeDataObjectForKey'
How to use NSCoder?
Use -decodeObjectForKey: and read the documentation.
Here is the NSCoding protocal methods from my LogEntry object, you can ignore the switch statement and the schema details though, those are from a base class I have written that allows me to keep sane track of changing data formats.
Please note that in addition to using decodeObjectForKey: you also need to make sure you retain/copy the given values as they are autoreleased when received.
- (id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self != nil) {
switch ([schemaVersion intValue]) {
case 2:
filepath = [[coder decodeObjectForKey:#"filepath"] copy];
identifier = [coder decodeInt64ForKey:#"identifier"];
level = [coder decodeIntForKey:#"level"];
lineNumber = [[coder decodeObjectForKey:#"lineNumber"] retain];
message = [[coder decodeObjectForKey:#"message"] retain];
timestamp = [[coder decodeObjectForKey:#"timestamp"] retain];
break;
default:
[self release], self = nil;
break;
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:filepath forKey:#"filepath"];
[coder encodeInt64:identifier forKey:#"identifier"];
[coder encodeInt:level forKey:#"level"];
[coder encodeObject:lineNumber forKey:#"lineNumber"];
[coder encodeObject:message forKey:#"message"];
[coder encodeObject:timestamp forKey:#"timestamp"];
[super encodeWithCoder:coder];
}
I think you should be using -decodeObjectForKey:
I wrote a helper function for using NSCoding. It's a part of VSCore Library. Check it out here:
#interface QuickCoding : NSObject
+ (void)quickEncode:(NSObject<NSCoding>*)object withEncoder:(NSCoder*)encoder;
+ (void)quickDecode:(NSObject<NSCoding>*)object withDecoder:(NSCoder*)decoder;
#end
And the .m file:
#import "QuickCoding.h"
#import "ReflectionHelper.h"
#define QUICK_CODING_HASH #"h4"
#implementation QuickCoding
+ (void)quickEncode:(NSObject<NSCoding>*)object withEncoder:(NSCoder *)encoder{
NSArray *codingKeys = [ReflectionHelper fieldsList:[object class]];
NSUInteger hash = [[codingKeys componentsJoinedByString:#""] hash];
[encoder encodeObject:#(hash) forKey:QUICK_CODING_HASH];
[codingKeys enumerateObjectsUsingBlock:^(NSString *key, __unused NSUInteger idx, __unused BOOL *stop) {
id val = [object valueForKey:key];
if ([val conformsToProtocol:#protocol(NSCoding)]){
[encoder encodeObject:val forKey:key];
}
}];
}
+ (void)quickDecode:(NSObject<NSCoding>*)object withDecoder:(NSCoder *)decoder{
NSArray *codingKeys = [ReflectionHelper fieldsList:[object class]];
NSUInteger hash = [[codingKeys componentsJoinedByString:#""] hash];
NSUInteger decodedHash = [[decoder decodeObjectForKey:QUICK_CODING_HASH] unsignedIntegerValue];
BOOL equalHash = hash == decodedHash;
[codingKeys enumerateObjectsUsingBlock:^(NSString *key, __unused NSUInteger idx, __unused BOOL *stop) {
id val = [decoder decodeObjectForKey:key];
if (equalHash || val){
[object setValue:val forKey:key];
}
}];
}
#end
Full code is here: https://github.com/voipswitch/VSCore/tree/master/VSCore/Storage

How to implement initWithObjects?

How can I create a class with the initializer initWithObjects?
Or does it just make more sense to inherit from NSArray and work around it that way?
initWithObjects: is implemented using a C variable argument list. Here's an example implementation:
- (void)setContentByAppendingStrings:(NSString *)firstArg, ...
{
NSMutableString *newContentString = [NSMutableString string];
va_list args;
va_start(args, firstArg);
for (NSString *arg = firstArg; arg != nil; arg = va_arg(args, NSString*))
{
[newContentString appendString:arg];
}
va_end(args);
[contents autorelease];
contents = [newContentString retain];
}
See this page for more info.
#interface foo : NSObject {
NSArray* objects;
}
-(id)initWithObjects:(NSArray*)array;
#end
#implementation foo
-(id)initWithObjects:(NSArray*)array{
if(self = [super init]){
objects = array;
}
return self;
}
#end