I am trying to create a singleton User class in my app, here's the code:
#import "User.h"
#import "Login.h"
#import "SFHFKeychainUtils.h"
// Constants
static NSString* const kDBUserCurrentUserIDDefaultsKey = #"kDBUserCurrentUserIDDefaultsKey";
// Current User singleton
static User* currentUser = nil;
#implementation User
#synthesize username = _username;
#synthesize password = _password;
#synthesize delegate = _delegate;
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
}
return self;
}
+ (NSString*)primaryKeyProperty {
return #"username";
}
+ (User*)currentUser {
if (nil == currentUser) {
id username = [[NSUserDefaults standardUserDefaults] objectForKey:#"kApplicationUserNameKey"];
if (!username) {
currentUser = [self new];
} else{
NSLog(#"CURRENT USER");
return self;
}
[currentUser retain];
}
return currentUser;
}
+ (void)setCurrentUser:(User*)user {
[user retain];
[currentUser release];
currentUser = user;
}
/**
* Implementation of a RESTful login pattern. We construct an object loader addressed to
* the /login resource path and POST the credentials. The target of the object loader is
* set so that the login response gets mapped back into this object, populating the
* properties according to the mappings declared in elementToPropertyMappings.
*/
- (void)loginWithUsername:(NSString*)username andPassword:(NSString*)password delegate:(NSObject<DBUserAuthenticationDelegate>*)delegate {
_delegate = delegate;
//[RKObjectManager sharedManager].client.username = username;
//[RKObjectManager sharedManager].client.password = password;
self.username = username;
self.password = password;
RKObjectMapping * userMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForKeyPath:#"LoginViewController"];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/account/verify.json" objectMapping:userMapping delegate:self];
}
/**
* Implementation of a RESTful logout pattern. We POST an object loader to
* the /logout resource path. This destroys the remote session
*/
- (void)logout/*:(NSObject<DBUserAuthenticationDelegate>*)delegate */{
NSError * error = nil;
[[NSUserDefaults standardUserDefaults] setValue:nil forKey:#"kApplicationUserNameKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
[SFHFKeychainUtils deleteItemForUsername:self.username andServiceName:#"convore" error:&error];
NSLog(#"LOGGING OUT");
if ([self.delegate respondsToSelector:#selector(userDidLogout:)]) {
[self.delegate userDidLogout:self];
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"DBUserDidLogoutNotification" object:nil];
}
- (void)loginWasSuccessful {
// Upon login, we become the current user
[User setCurrentUser:self];
NSError * error = nil;
// Persist the username for recovery later
[[NSUserDefaults standardUserDefaults] setValue:self.username forKey:#"kApplicationUserNameKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
[SFHFKeychainUtils storeUsername:self.username andPassword:self.password forServiceName:#"convore" updateExisting:TRUE error:&error];
// Inform the delegate
if ([self.delegate respondsToSelector:#selector(userDidLogin:)]) {
[self.delegate userDidLogin:self];
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"DBUserDidLoginNotification" object:self];
}
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response
{
NSLog(#"Loaded payload: %#", [response bodyAsString]);
}
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObject:(id)object
{
if ([objectLoader wasSentToResourcePath:#"/account/verify.json"]) {
Login * login = (Login *) object;
if ([login.username length] > 0)
[self loginWasSuccessful];
}
}
- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError*)error {
if ([objectLoader wasSentToResourcePath:#"/account/verify.json"]) {
NSLog(#"Encountered an error: %#", error);
// Login failed
if ([self.delegate respondsToSelector:#selector(user:didFailLoginWithError:)]) {
[self.delegate user:self didFailLoginWithError:error];
}
}
}
- (BOOL)isLoggedIn {
return self.username != nil;
//return self.singleAccessToken != nil;
}
- (void)dealloc {
_delegate = nil;
[_password release];
[_passwordConfirmation release];
[super dealloc];
}
#end
The issue is that whenever I tried to access currentUser it always breaks down. I first called the loginWithUsernameandPassword and then tried calling the currentUser, but when I call the currentUser on logout, it gives me an error:
calling this:
if ([[User currentUser] isLoggedIn])
gives me:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[User isLoggedIn]: unrecognized selector sent to class 0x1a671c'
seems that currentUser is nil, why is this?
Quick Singleton 101 (I wish I had this when I started, lol. Everyone just pointed me to the docs which didn't help much). The name of the singleton is going to be "Singleton"
//Singleton.h
#import <Foundation/Foundation.h>
#interface SingletonManager : NSObject
{
NSDictionary* randomDictionary; //just using a dictionary for demonstrative purposes. You can make this a string or whatever you want.
}
+ (Singleton*)sharedSingleton;
#property (nonatomic, retain) NSDictionary *randomDictionary;
#end
And now the .m
//Singleton.m
#import "Singleton.h"
static Singleton *sharedSingleton = nil;
#implementation Singleton
#synthesize randomDictionary;
#pragma mark Singleton Method
+ (Singleton*)sharedSingleton
{
#synchronized(self)
{
if(sharedSingleton == nil)
{
sharedSingleton = [[super allocWithZone:NULL] init];
}
}
return sharedSingleton;
}
#end
And to set/get, first import the singleton in whatever class you need: #import "Singleton.h", then grab the singleton with Singleton *singletonManager = [Singleton sharedSingleton]; and then you can do whatever you need to as necessary. i.e. to get the description of the NSDictionary you would call [[singletonManager randomDictionary] description];
Now this is using ARC, so if you are not you'd just have to make sure you manage your memory correctly. Enjoy.
You need to get the singleton object before you can call a method on it.
if ( [[User currentUser] isLoggedIn] ) {
// Magic happens here
}
You aren't coding your singleton properly.
+ (User *) currentUser {
#synchronized (self) {
if (currentUser == nil) {
currentUser = [[self alloc] init];
}
return currentUser;
}
}
The answer is really a combo of the two answers from XCodeDev and Matthieu Cormier. You need to "protect" your init the way the code sample says so new versions of the object are not created. Otherwise, its not a real singleton. More info on Singleton pattern.
Also, just because its a singleton doesn't mean you can access it with just class methods after you initialize it. You still need to get the instance you initialized, otherwise you cannot do operations that require certain values only in the instance.
Related
i am facing problem when i call method within method of another class like this i have method for button when someone click on button
//within ViewController.m button method
-(IBAction)login:(id)sender
{
DBHelper *objdb = [[DBHelper alloc] init];
[objdb loginnow:textname.text andpassword:textpassword.text];
}
and this button method calling this method in DBhelper.m file and it succesfully calling this method
-(void) loginnow:(NSString *) username andpassword:(NSString *) password
{
[self createEditableCopyOfDatabaseIfNeeded];
[self initializeDatabase];
const char *sql;
NSString *querySQL = [NSString stringWithFormat: #"SELECT username, password FROM CONTACT WHERE username='%#' AND password='%#'",username,password];
sql = [querySQL UTF8String];
if (sqlite3_prepare_v2(database, sql, -1, &init_statement, NULL) != SQLITE_OK) {
NSAssert1(0, #"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database));
}
while (sqlite3_step(init_statement) == SQLITE_ROW)
{
NSLog(#"char sql = %s" ,sql);
dbusername = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement,0)];
dbpassword = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement,1)];
}
if ([dbusername isEqualToString:username] && [dbpassword isEqualToString:password])
{
//DBHelper.callingViewController = self;
[self.callingViewController addview];
}
else if (dbusername != username || dbpassword != password)
{
NSLog(#"dbusername is = %#" ,dbusername);
NSLog(#"dbpassword is = %#" ,dbpassword);
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Login Failed"
message:#"Username Or Password is not Correct"
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:#"OK ", nil];
[alert show];
[alert release];
}
sqlite3_reset(init_statement);
[self closeDatabase];
}
and also in DBhelper.h i define property for this
#property (strong) ViewController * callingViewController;
and within if condidtion in lognow method if password and username is succesully match i am calling this mathod in Viewcontroller.com file but am fail to call that
//ViewController.m
-(void) addview
{
DBHelper *f = [[DBHelper alloc] init];
f.callingViewController = self;
newview.center = CGPointMake(1000, 1000);
}
Though it's not wise to hold the viewController in the DBhelper(it breaks MVC), you could call your ViewController's method as your code but remember to set to pass your ViewController to the DBhelper. Maybe like this:
//ViewController.m
-(IBAction)login:(id)sender
{
DBHelper *objdb = [[DBHelper alloc] init];
[objdb loginnow:textname.text andpassword:textpassword.text viewController:self];
}
//DBHelper.m
-(void) loginnow:(NSString *) username andpassword:(NSString *)password viewController:(ViewController *)vc
{ ...
if ([dbusername isEqualToString:username] && [dbpassword isEqualToString:password])
{
[vc addview];
}
...
}
But in fact you should use a delegate (or block or notification, but delegate is the most case) here. Like this:
In DBHelper.h, before #interface, add
#class DBHelper;
#protocol DBHelperDelegate <NSObject>
-(void) DBHelp:(DBHelper *)helper didFinishedLoginSuc:(BOOL)suc;
#end
and between the #interface and #end tag, add(suppose you are not using ARC)
#property (nonatomic, assign) id delegate;
in the DBHelper.m, in the #implementation, add(suppose you are not using auto synthesize)
#synthesize delegate = _delegate;
Now, you can change the [self.callingViewController addview]; to
if (self.delegate && [self.delegate responseToSelector:#selector(DBHelp:didFinishedLoginSuc:)]) {
[self.delegate DBHelp:self didFinishedLoginSuc:YES];
}
Now you get a delegate prepared for every view controller which obey the DBHelperDelegate.
In your ViewController.h, tell the compiler that it obey the DBHelperDelegate by add behind the class declare:
#interface ViewController:UIViewController<DBHelperDelegate>
and change the addView method name to
-(void) DBHelp:(DBHelper *)helper didFinishedLoginSuc:(BOOL)suc
At last, when you click the button, set self as the objdb's delegate
-(IBAction)login:(id)sender
{
DBHelper *objdb = [[DBHelper alloc] init];
objdb.delegate = self;
[objdb loginnow:textname.text andpassword:textpassword.text];
}
Now, when you login successfully, -(void) DBHelp:(DBHelper *)helper didFinishedLoginSuc:(BOOL)suc in ViewController.m will be called and you can deal with your view.
Remember to set the delegate to nil when your viewController gets dealloc, or you will expect an memory error. Be careful.
i have been using mostly the appDelegate class for global variables but recently i came to know that its not a gud way for keeping global variables so i am trying to make a singleton class as following
#implementation globalVar
static globalVar *_sharedInstance =nil;
#synthesize totalTime;
- (id) init
{
if (self = [super init])
{
}
return self;
}
+ (globalVar *) sharedInstance
{
#synchronized (self) {
if (_sharedInstance == nil) {
[[self alloc] init];
}
}
return _sharedInstance;
}
+ (id)allocWithZone:(NSZone *)zone {
#synchronized(self) {
if (_sharedInstance == nil) {
_sharedInstance = [super allocWithZone:zone];
return _sharedInstance;
}
}
return nil;
}
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
-(NSUInteger)retainCount
{
return NSUIntegerMax; //denotes an object that cannot be released
}
- (void)release
{
// do nothing
}
- (id)autorelease
{
return self;
}
- (void) setTotalTime:(NSString *)time
{
#synchronized(self) {
if (totalTime != time) {
[totalTime release];
totalTime = [NSString stringWithFormat:#"%#",time];
}
}
//NSLog(#"time %#",totalTime);
}
-(NSString *)getTotalTime
{
#synchronized(self) {
//NSLog(#"total %#",totalTime);
return totalTime;
}
}
when i set the value for totaltime in my appDelegate class and retrieve it in that class only i get the correct value. but when i only retrieve the value in some other class i get BAD EXCESS. i first create the sharedinstance and then only call this method then why am i getting this error??
globalVar *myEngine = [globalVar sharedInstance];
NSLog(#"about %#",[myEngine totalTime]);
in my app delegate
globalVar *myEngine = [globalVar sharedInstance];
[myEngine setTotalTime:totalTime];
NSLog(#"in app delegate%#",[myEngine getTotalTime]);
You're releasing totalTime but not retaining the new value, which means that when you access it it's already been released, causing a bad access exception.
You can correct this by changing the line where you set the value to include a call to retain:
totalTime = [[NSString stringWithFormat:#"%#",time] retain];
Have a look at the discussion here:
Is it good practice to use AppDelegate for data manipulation and Handling?
I made this class that turns any object into a singleton, but I know that it's not "concurrent queue safe." Could someone please explain to me how to do this, or better yet, show me the code. To be clear I want to know how to use this with operation queues and dispatch queues (NSOperationQueue and Grand Central Dispatch) on iOS.
Thanks in advance,
Rich
EDIT: I had an idea for how to do it. If someone could confirm it for me I'll do it and post the code. The idea is that proxies make queues all on their own. So if I make a mutable proxy (like Apple does in key-value coding/observing) for any object that it's supposed to return, and always return the same proxy for the same object/identifier pair (using the same kind of lazy loading technique as I used to create the singletons), the proxies would automatically queue up the any messages to the singletons, and make it totally thread safe.
IMHO this seems like a lot of work to do, so I don't want to do it if it's not gonna work, or if it's gonna slow my apps down to a crawl.
Here's my non-thread safe code:
RMSingletonCollector.h
//
// RMSingletonCollector.h
// RMSingletonCollector
//
// Created by Rich Meade-Miller on 2/11/11.
// Copyright 2011 Rich Meade-Miller. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "RMWeakObjectRef.h"
struct RMInitializerData {
// The method may take one argument.
// required
SEL designatedInitializer;
// data to pass to the initializer or nil.
id data;
};
typedef struct RMInitializerData RMInitializerData;
RMInitializerData RMInitializerDataMake(SEL initializer, id data);
#interface NSObject (SingletonCollector)
// Returns the selector and data to pass to it (if the selector takes an argument) for use when initializing the singleton.
// If you override this DO NOT call super.
+ (RMInitializerData)designatedInitializerForIdentifier:(NSString *)identifier;
#end
#interface RMSingletonCollector : NSObject {
}
+ (id)collectionObjectForType:(NSString *)className identifier:(NSString *)identifier;
+ (id<RMWeakObjectReference>)referenceForObjectOfType:(NSString *)className identifier:(NSString *)identifier;
+ (void)destroyCollection;
+ (void)destroyCollectionObjectForType:(NSString *)className identifier:(NSString *)identifier;
#end
// ==--==--==--==--==Notifications==--==--==--==--==
extern NSString *const willDestroySingletonCollection;
extern NSString *const willDestroySingletonCollectionObject;
RMSingletonCollector.m
//
// RMSingletonCollector.m
// RMSingletonCollector
//
// Created by Rich Meade-Miller on 2/11/11.
// Copyright 2011 Rich Meade-Miller. All rights reserved.
//
#import "RMSingletonCollector.h"
#import <objc/objc-runtime.h>
NSString *const willDestroySingletonCollection = #"willDestroySingletonCollection";
NSString *const willDestroySingletonCollectionObject = #"willDestroySingletonCollectionObject";
RMInitializerData RMInitializerDataMake(SEL initializer, id data) {
RMInitializerData newData;
newData.designatedInitializer = initializer;
newData.data = data;
return newData;
}
#implementation NSObject (SingletonCollector)
+ (RMInitializerData)designatedInitializerForIdentifier:(NSString *)identifier {
return RMInitializerDataMake(#selector(init), nil);
}
#end
#interface RMSingletonCollector ()
+ (NSMutableDictionary *)singletonCollection;
+ (void)setSingletonCollection:(NSMutableDictionary *)newSingletonCollection;
#end
#implementation RMSingletonCollector
static NSMutableDictionary *singletonCollection = nil;
+ (NSMutableDictionary *)singletonCollection {
if (singletonCollection != nil) {
return singletonCollection;
}
NSMutableDictionary *collection = [[NSMutableDictionary alloc] initWithCapacity:1];
[self setSingletonCollection:collection];
[collection release];
return singletonCollection;
}
+ (void)setSingletonCollection:(NSMutableDictionary *)newSingletonCollection {
if (newSingletonCollection != singletonCollection) {
[singletonCollection release];
singletonCollection = [newSingletonCollection retain];
}
}
+ (id)collectionObjectForType:(NSString *)className identifier:(NSString *)identifier {
id obj;
NSString *key;
if (identifier) {
key = [className stringByAppendingFormat:#".%#", identifier];
}
else {
key = className;
}
if (obj = [[self singletonCollection] objectForKey:key]) {
return obj;
}
// dynamic creation.
// get a class for
Class classForName = NSClassFromString(className);
if (classForName) {
obj = objc_msgSend(classForName, #selector(alloc));
// if the initializer takes an argument...
RMInitializerData initializerData = [classForName designatedInitializerForIdentifier:identifier];
if (initializerData.data) {
// pass it.
obj = objc_msgSend(obj, initializerData.designatedInitializer, initializerData.data);
}
else {
obj = objc_msgSend(obj, initializerData.designatedInitializer);
}
[singletonCollection setObject:obj forKey:key];
[obj release];
}
else {
// raise an exception if there is no class for the specified name.
NSException *exception = [NSException exceptionWithName:#"com.RMDev.RMSingletonCollector.failed_to_find_class" reason:[NSString stringWithFormat:#"SingletonCollector couldn't find class for name: %#", [className description]] userInfo:nil];
[exception raise];
[exception release];
}
return obj;
}
+ (id<RMWeakObjectReference>)referenceForObjectOfType:(NSString *)className identifier:(NSString *)identifier {
id obj = [self collectionObjectForType:className identifier:identifier];
RMWeakObjectRef *objectRef = [[RMWeakObjectRef alloc] initWithObject:obj identifier:identifier];
return [objectRef autorelease];
}
+ (void)destroyCollection {
NSDictionary *userInfo = [singletonCollection copy];
[[NSNotificationCenter defaultCenter] postNotificationName:willDestroySingletonCollection object:self userInfo:userInfo];
[userInfo release];
// release the collection and set it to nil.
[self setSingletonCollection:nil];
}
+ (void)destroyCollectionObjectForType:(NSString *)className identifier:(NSString *)identifier {
NSString *key;
if (identifier) {
key = [className stringByAppendingFormat:#".%#", identifier];
}
else {
key = className;
}
[[NSNotificationCenter defaultCenter] postNotificationName:willDestroySingletonCollectionObject object:[singletonCollection objectForKey:key] userInfo:nil];
[singletonCollection removeObjectForKey:key];
}
#end
RMWeakObjectRef.h
//
// RMWeakObjectRef.h
// RMSingletonCollector
//
// Created by Rich Meade-Miller on 2/12/11.
// Copyright 2011 Rich Meade-Miller. All rights reserved.
//
// In order to offset the performance loss from always having to search the dictionary, I made a retainable, weak object reference class.
#import <Foundation/Foundation.h>
#protocol RMWeakObjectReference <NSObject>
#property (nonatomic, assign, readonly) id objectRef;
#property (nonatomic, retain, readonly) NSString *className;
#property (nonatomic, retain, readonly) NSString *objectIdentifier;
#end
#interface RMWeakObjectRef : NSObject <RMWeakObjectReference>
{
id objectRef;
NSString *className;
NSString *objectIdentifier;
}
- (RMWeakObjectRef *)initWithObject:(id)object identifier:(NSString *)identifier;
- (void)objectWillBeDestroyed:(NSNotification *)notification;
#end
RMWeakObjectRef.m
//
// RMWeakObjectRef.m
// RMSingletonCollector
//
// Created by Rich Meade-Miller on 2/12/11.
// Copyright 2011 Rich Meade-Miller. All rights reserved.
//
#import "RMWeakObjectRef.h"
#import "RMSingletonCollector.h"
#implementation RMWeakObjectRef
#dynamic objectRef;
#synthesize className, objectIdentifier;
- (RMWeakObjectRef *)initWithObject:(id)object identifier:(NSString *)identifier {
if (self = [super init]) {
NSString *classNameForObject = NSStringFromClass([object class]);
className = classNameForObject;
objectIdentifier = identifier;
objectRef = object;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(objectWillBeDestroyed:) name:willDestroySingletonCollectionObject object:object];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(objectWillBeDestroyed:) name:willDestroySingletonCollection object:[RMSingletonCollector class]];
}
return self;
}
- (id)objectRef {
if (objectRef) {
return objectRef;
}
objectRef = [RMSingletonCollector collectionObjectForType:className identifier:objectIdentifier];
return objectRef;
}
- (void)objectWillBeDestroyed:(NSNotification *)notification {
objectRef = nil;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[className release];
[super dealloc];
}
#end
The easiest (and thread safe) acces to singleton:
static SomeClass* shared_instance;
+(SomeClass*) sharedInstance {
#synchronized( shared_instance ) {
if( !shared_instance ) {
shared_instance = [[SomeClass alloc] init];
//some additional initialization here
}
}
return shared_instance;
}
Edit:
I think particular singleton class would be your best choice. Even for lazy loading. E.g. you need to access some array of users all over your app. Then you can create singleton class:
UserManager:
+(UserManager*) sharedManager;
-(NSArray*) allUsers;
-(NSArray*) recentUsers;
-(NSArray*) featuredUsers;
-(void) addUser:(User*) user;
-(void) removeUser:(User*) user;
etc...
Then you will be able to access that arrays in every view controller.
You should create singleton for other types.
hi i am working with Twitter, in this there will be two class, for showing tweet in table
view and retrieving string for table view like user photos, screen name, username and date
one class is MyTweetViewController.h //I am importing the Tweet class also
and next one is Tweet.h class
//this line i am getting warning i.e NO initWithTweetDictionary method not found,
Tweet *tweet =[[Tweet alloc] initWithTweetDictionary:tweetDict];
- -(void)statusesReceived:(NSArray *)statuses forRequest:(NSString *)connectionIdentifier
// this is delegate for MGTwitter for retrieving information of user
{
NSLog(#"Statuses Receives: %#",statuses);
{
tweetArray = [[NSMutableArray alloc] init];
for(NSDictionary *tweetDict in statuses)
{
Tweet *tweet =[[Tweet alloc] initWithTweetDictionary:tweetDict];// here i am getting
warring i.e NO initWithTweetDictionary method not found,
[tweetArray addObject:tweet];
[tweet release];
}
}
[self.tweetsTableView reloadData];
}
////Tweet.h
#import <UIKit/UIKit.h>
#interface Tweet : NSObject {
NSDictionary *contentsTweet;
}
-(NSString*)userName;
-(NSString*)tweet;
-(NSString*)image_url;
-(NSString*)created_at;
#end
///Tweet.m
#import "Tweet.h"
#implementation Tweet
-(id)initWithTweetDictionary:(NSDictionary*)_contentsTweet {
if(self = [super init]) {
contentsTweet = _contentsTweet;
[contentsTweet retain];
}
return self;
}
-(NSString*)userName {
NSDictionary * dic = (NSDictionary*)[contentsTweet objectForKey:#"user"];
return [dic objectForKey:#"name"];
}
-(NSString*)tweet {
return [contentsTweet objectForKey:#"text"];
}
-(NSString*)image_url {
NSDictionary * dic = (NSDictionary*)[contentsTweet objectForKey:#"user"];
return [dic objectForKey:#"profile_image_url"];
}
- (void)dealloc {
[contentsTweet release];
[super dealloc];
}
#end
Please suggest me
Thank you
in Tweet.h you have to declare -(id)initWithTweetDictionary:(NSDictionary*)_contentsTweet;
in order to make the warning dissappear.
#interface Tweet : NSObject {
NSDictionary *contentsTweet;
}
-(id)initWithTweetDictionary:(NSDictionary*)_contentsTweet;
-(NSString*)userName;
-(NSString*)tweet;
-(NSString*)image_url;
-(NSString*)created_at;
#end
I've implemented google analytics in my ios app but nothing ever gets logged to analytics. I've been running it for a month, so I know there's something that's not working.
I added a GANTrackerDelegate to trace why my dispatch calls are failing, but it never gets called. I know dispatch is getting called. Does anyone know? Here's my class.
#import <Foundation/Foundation.h>
#import "GANTracker.h"
#interface trackerDelegate : NSObject<GANTrackerDelegate> {
}
#end
#import "trackerDelegate.h"
#implementation trackerDelegate
#pragma mark GANTrackerDelegate
- (void)trackerDispatchDidComplete:(GANTracker *)tracker
eventsDispatched:(NSUInteger)eventsDispatched
eventsFailedDispatch:(NSUInteger)eventsFailedDispatch {
NSLog(#"events dispatched: %d, events failed: %d", eventsDispatched, eventsFailedDispatch);
}
#end
Here's my tracker class. Note, I can trace all of these things getting called, and I know that dispatch is called repeatedly and returns 'YES', but the delegate routine never gets called. It might be a coding thing, but I'm trying to see if the dispatch calls are failing or not. The id I'm using is valid because it works for Android.
tracker.h
#interface Tracker : NSObject {
}
+ (void) startTracking;
+ (void) endTracking;
+ (void) dispatch;
+ (void) trackPageView : (NSString *) pageId;
+ (void) trackEvent : (NSString *) categoryId
: (NSString *) actionID
: (NSString *) labelID
: (int) tvalue;
#end
tracker.m
#import "Tracker.h"
#import "trackerDelegate.h"
#implementation Tracker
static BOOL trackingOn = false;
static BOOL dirty = false;
trackerDelegate *tg = nil;
+ (void) startTracking
{
if (trackingOn){
return;
}
#try{
if (!tg)
{
tg = [[trackerDelegate alloc] init];
}
[[GANTracker sharedTracker] startTrackerWithAccountID:#"UA-VALID-GOOGLEID"
dispatchPeriod:-1
delegate:tg];
trackingOn = true;
}
#catch (NSException*)
{
trackingOn = false;
}
}
+ (void) endTracking
{
#try{
if (trackingOn)
{
[[GANTracker sharedTracker] stopTracker];
}
trackingOn = false;
}
#catch (NSException *){
trackingOn = false;
}
}
+ (void) dispatch
{
if (!dirty){
return;
}
[self startTracking];
#try
{
if (![[GANTracker sharedTracker] dispatch]) {
trackingOn = false;
NSLog(#"Google anaytics dispatch failed");
return;
}
dirty = false;
}
#catch (NSException *){
trackingOn = false;
}
}
+ (void) trackPageView : (NSString *) pageId
{
[self startTracking];
#try{
if (trackingOn){
NSError *error;
if (![[GANTracker sharedTracker] trackPageview:pageId
withError:&error]) {
trackingOn = false;
NSLog(#"Google anaytics track pageview failed");
return;
}
dirty = true;
}
}
#catch (NSException *){
trackingOn = false;
}
}
+ (void) trackEvent : (NSString *) categoryId
: (NSString *) actionID
: (NSString *) labelID
: (int) tvalue
{
[self startTracking];
#try
{
if (trackingOn){
NSError *error;
if (![[GANTracker sharedTracker] trackEvent:categoryId
action:actionID
label:labelID
value:tvalue
withError:&error])
{
trackingOn = false;
NSLog(#"Google anaytics track event failed");
return;
}
dirty = true;
}
}
#catch (NSException *){
trackingOn = false;
}
}
#end
Muchas Gracias!!
First of all you should initiate the Google Analytics account using the Web-property ID given to you at the time of creating an account in Google Analytics site.
Then you must add the following code to start the tracking and set the dispatch time. Then only you could view the analytics count in your account in Google Analytics online account. Here is the initiating code for that.
[[GANTracker sharedTracker]startTrackerWithAccountID:#"UA-xxxxxx-yy"
dispatchPeriod:10
delegate:nil];
You should also make sure, you have included the following code in the dealloc in the AppDelegate.
[GANTracker sharedTracker] stopTracker];
Hope this will work fine. Thanks.