My URL map is as follows:
[map from:#"tt://webPage/(initWithPage:)" toSharedViewController:[WebPageController class]];
and in the WebPageController
- (id) initWithPage:(WebPage)page
{
if (self = [super init])
{
...
Then I called the url several times in my code
tt://webPage/1
tt://webPage/2
tt://webPage/1 (still called the initWithPage: everytime, not cached)
Why it is not cached as it is a SharedViewController?
I believe this is happening to you because TTNaviagtor is broken on iOS 5. see https://github.com/facebook/three20/pull/719/files. Have you tried running the same code on a iOS 4 with the same result?
My recommendation to you is to stop using TTNaviagtor. You can still use the three20 library by pushing and poping TTViewController in the native ios method.
Here's an example on replacing the TTNaviagtor in your app delegate:
#interface AppDelegate : NSObject <UIApplicationDelegate> {
UIWindow* _window;
TTBaseNavigationController* _masterNavController;
WebPageController* _web1Controller;
WebPageController* _web2Controller;
}
#property(nonatomic, retain) UIWindow* window;
#property(nonatomic, retain) TTBaseNavigationController* masterNavController;
#property(nonatomic, retain) WebPageController* web1Controller;
#property(nonatomic, retain) WebPageController* web2Controller;
And
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
#implementation AppDelegate
#synthesize window = _window;
#synthesize masterNavController = _masterNavController;
#synthesize web1Controller = _web1Controller;
#synthesize web2Controller = web2Controller;
///////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
_window = [[UIWindow alloc] initWithFrame:TTScreenBounds()];
TTViewController* controller = [[[MasterViewController alloc] init] autorelease];
_masterNavController = [[TTBaseNavigationController alloc] initWithRootViewController:controller];
[_window addSubview:_masterNavController.view];
}
[_window makeKeyAndVisible];
return YES;
}
then you can push and pop any TTViewController (or your own subclasses of TTViewController) into the _masterNavController. Personally, i think TTNavigator is a bad design pattern, and apple designed their navigation system in different mindset.
why not step into the code and check what happen?
I believe the objects are created in TTURLMap's objectForURL:query:pattern: you can set a break point and see why a new one is created instead re-use old one.
this the implementation of objectForURL:query:pattern: with my comment
- (id)objectForURL: (NSString*)URL
query: (NSDictionary*)query
pattern: (TTURLNavigatorPattern**)outPattern {
id object = nil;
if (_objectMappings) {
// _objectMappings is a NSMutableDictionary and use to cache shared object
object = [_objectMappings objectForKey:URL];
// if object not found than check does _objectMappings contains it with right key
if (object && !outPattern) {
return object;
}
}
NSURL* theURL = [NSURL URLWithString:URL];
TTURLNavigatorPattern* pattern = [self matchObjectPattern:theURL];
if (pattern) {
if (!object) {
// object is not found in the mapping dictionary so create new one, this should only happen once for shared object
object = [pattern createObjectFromURL:theURL query:query];
}
// make sure navigationMode is TTNavigationModeShare
if (pattern.navigationMode == TTNavigationModeShare && object) {
// cache shared object in the mapping dictionary so next time it will re-use the cached one
[self setObject:object forURL:URL];
// if setObject:forURL: is not working than the shared object will not be cached
}
if (outPattern) {
*outPattern = pattern;
}
return object;
} else {
return nil;
}
}
Related
I asked a question yesterday regarding my table view and linking unique detail views to each cell in the table view. I believe I got a good answer to my question here. (Hopefully you can read that post and see what I need). Basically I wanted to know if I am making my singleton correctly. Here is my code:
timerStore.h
#import "Tasks.h"
#interface timerStore : NSObject
{
NSMutableDictionary *allItems;
}
+(timerStore *)sharedStore;
-(NSDictionary *)allItems;
-(NSTimer *)createTimerFor:(Tasks *)t inLocation: (NSIndexPath *)indexPath;
-(void)timerAction;
#end
timerStore.m
#implementation timerStore
+(timerStore *)sharedStore{
static timerStore *sharedStore = nil;
if (!sharedStore)
sharedStore = [[super allocWithZone:nil]init];
return sharedStore;
}
+(id)allocWithZone:(NSZone *)zone{
return [self sharedStore];
}
-(id)init {
self = [super init];
if (self) {
allItems = [[NSMutableDictionary alloc]init];
}
return self;
}
-(NSDictionary *)allItems{
return allItems;
}
-(NSTimer *)createTimerFor:(Tasks *)t inLocation: (NSIndexPath *)indexPath {
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:t.timeInterval target:self selector:#selector(timerAction) userInfo:nil repeats:1.0];
[allItems setObject:timer forKey:indexPath];
return timer;
}
-(void)timerAction{
//custom properties here
}
#end
I'm kind of confused because I was under the impression that a cell's index path gets recycled as you scroll down (dequeue). I may be wrong though. Anyway, am I on the right path to making a singleton as the guy in the link suggested?
The best way to implement App Singleton is as follows
Header file
#import <Foundation/Foundation.h>
#interface AppSingleton : NSObject
#property (nonatomic, retain) NSString *username;
+ (AppSingleton *)sharedInstance;
#end
Implementation File
#import "AppSingleton.h"
#implementation AppSingleton
#synthesize username;
+ (AppSingleton *)sharedInstance {
static AppSingleton *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
// Initializing
- (id)init {
if (self = [super init]) {
username = [[NSString alloc] init];
}
return self;
}
#end
Note:
What this does is it defines a static variable (but only global to this translation unit) called sharedInstance which is then initialised once and only once in sharedInstance Method. The way we ensure that it’s only created once is by using the dispatch_once method from Grand Central Dispatch (GCD). This is thread safe and handled entirely by the OS for you so that you don’t have to worry about it at all.
Using Singleton To set value
[[AppSingleton sharedInstance] setUsername:#"codebuster"];
Using Singleton to get value.
NSString *username = [[AppSingleton sharedInstance] username];
Further Reference and Reading
I've reviewed (and tried) a bunch of the threads here regarding Singletons and NSMutableArrays. I'm new to Objective-C so please bear with me.
I simply want to create a few arrays that can be accessed from any view/.m file.
What is the best (or most concise) coding for a Singleton?
Below is what I have now and I get
1 warning at .m '#implementation' - "Incomplete implementation"
1 error at usage in a view .m file - "initializer element is not a compile-time constant"
This is the code I have now - my GlobalData.h file:
#import <Foundation/Foundation.h>
#interface GlobalData : NSObject {
NSMutableArray *listOfHeadings;
NSMutableArray *listOfItems1;
NSMutableArray *listOfItems2;
}
#property(nonatomic,retain)NSMutableArray *listOfHeadings;
#property(nonatomic,retain)NSMutableArray *listOfItems1;
#property(nonatomic,retain)NSMutableArray *listOfItems2;
+(GlobalData*)getInstance;
#end
My GlobalData.m file:
#import "GlobalData.h"
#implementation GlobalData
#synthesize listOfHeadings;
#synthesize listOfItems1;
#synthesize listOfItems2;
static GlobalData *instance=nil;
+(GlobalData *)getInstance
{
#synchronized(self)
{
if(instance==nil)
{
instance= [GlobalData new];
}
}
return instance;
}
#end
And in a view .m file (simplified):
#import GlobalData.h
GlobalData *globDat=[GlobalData getInstance]; //error occurs here
Can someone point out the trouble and if there's better coding, please enlighten me - thanks!
EDIT
Here's a few links I've tried to use:
Can i have a single NSMutableArray in my multiple views application?
iPhone help with singleton class
In this case, you might be doing more than you have to. Granted this certainly isn't always the best solution - but you can put your NSMutableArray as a property in your App Delegate and then easily refer to it from any view. By doing it this way - you aren't locking it in as a 'singleton' but there is a 'singleton instance' of it (this helps a great deal for testability).
I have simplified this process here:
YourAppDelegate.h
#property (nonatomic,retain) NSMutableArray *myArray;
YourAppDelegate.m
#synthesize myArray;
YourViewController.m
YourAppDelegate *appDelegate = (YourAppDelegate *)[[UIApplication sharedApplication] delegate];
NSMutableArray *myArrayFromAppDelegate = appDelegate.myArray;
From this point - you can do any manipulation on this value.
Here's the "modern" version of a single method to turn any class into a Singleton (in this case formatted as a code snippet). It works in iOS4.x or higher:
+(<#SingletonClassName#> *) sharedInstance
{
static <#SingletonClassName#> *_sharedClient = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedClient = [[self alloc] init];
});
return _sharedClient;
}
But, do you really need a singleton of a single NSMutableArray? You could use the built-on singleton - your application delegate, which is got to by calling:
MyAppDelegate * appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.myMutableArray addObject:...];
The error initializer element is not a compile-time constant is not related to how you create your singleton. The error is how you are accessing your singleton. You are doing this outside of a function:
GlobalData *globDat=[GlobalData getInstance];
This means that you are trying to initialize a global variable (globDat) as the value of the expression [GlobalData getInstance]. You can only initialize global variables to expressions that are "compile-time constants". That means things like 0 or "fred" or 8/2. The value of [GlobalData getInstance] cannot be computed at compile-time, so it cannot be used to initialize the global variable.
Instead, you need to just use [GlobalData getInstance] inside your function bodies wherever you are currently trying to use the globDat variable.
As for the warning, Incomplete implementation, I don't see what's missing. Perhaps you didn't post all of the code from GlobalData.h. Anyway, you should be able to click the warning (where it appears on the right side of the editor window) and have Xcode show you what's missing.
This is the way I create my Singleton:
Singleton.h
#import <Foundation/Foundation.h>
#interface Singleton : NSObject {
NSMutableArray *firstMutableArray;
NSMutableArray *secondMutableArray;
}
#property (nonatomic, retain) NSMutableArray *firstMutableArray;
#property (nonatomic, retain) NSMutableArray *secondMutableArray;
+ (id)sharedSingleton;
#end
Sigleton.m
#import "Singleton.h"
static Singleton *sharedMySingleton = nil;
#implementation Singleton
#synthesize firstMutableArray;
#synthesize secondMutableArray;
#pragma mark Singleton Methods
+ (id)sharedSingleton {
#synchronized(self) {
if (sharedMySingleton == nil) {
sharedMySingleton = [[super allocWithZone:NULL] init];
}
return sharedMySingleton;
}
+ (id)allocWithZone:(NSZone *)zone {
return [[self sharedSingleton] retain];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX;
}
- (oneway void)release {
// Never release
}
- (id)autorelease {
return self;
}
- (id)init {
if (self = [super init]) {
firstMutableArray = [[NSMutableArray alloc] initWithObjects:nil];
secondMutableArray = [[NSMutableArray alloc] initWithObjects:nil];
}
return self;
}
- (void)dealloc {
[firstMutableArray release];
[secondMutableArray release];
[super dealloc];
}
#end
Then, when you want to call your Singleton:
#import "Singleton.h"
Singleton *singleton = [Singleton sharedSingleton];
singleton.firstMutableArray = ...
singleton.secondMutableArray = ...
This issue has been bugging me for a while now and somehow I cannot find what I'm doing wrong. I must say I am new to Objective-C and Xcode.
So the issue is that I try to declare an instance variable (NSMutableArray) but for some reason the init function is NEVER reached. The variable is always NULL.
So I have a class named PropertyProvider which contains a NSMutableArray named "properties".
#interface PropertyProvider : NSObject {
NSMutableArray *properties;
}
#property (nonatomic, retain) NSMutableArray *properties;
..
#end
I then instantiate this NSMutableArray in the init method of this PropertyProvider class as the following:
#implementation PropertyProvider
#synthesize properties;
- (id) init {
self = [super init];
NSLog(#"Instantiating PropertyProvider");
if (self) {
properties = [[NSMutableArray alloc] init];
}
return self;
}
.. more code ..
#end
In my Application delegate I try to instantiate the PropertyProvider as "prp":
#implementation MyAppDelegate
#synthesize prp = _prp;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
[[_prp init] alloc];
[self.window makeKeyAndVisible];
return YES;
}
.. more code ..
- (void)dealloc
{
[_prp release];
[super dealloc];
}
But thus, for some reason it never reaches the init method of my PropertyProvider. Why o why?!
Thanks for your help!
The correct way of initializing _prp as an instance of PropertyProvider would be,
_prp = [[PropertyProvider alloc] init];
Since _prp is an instance variable, it is nil by default and hence messages to it don't do anything which is the reason why you're not getting any errors.
I want to have one object that is initialized in the delegate and I want to be able to use this object anywhere across view controllers (doesn't depend on what view I am currently at). I am guessing the solution to this would be to have a singleton class, so far I have the following:
#interface LocationManager : NSObject <CLLocationManagerDelegate>{
NSDate *enter;
NSDate *exit;
CLLocationManager * manager;
}
#property (nonatomic, retain) NSDate * enter;
#property (nonatomic, retain) NSDate * exit;
- (BOOL)registerRegionWithLatitude:(double)latitude andLongitude:(double)longitude;
+ (LocationManager *)instance;
#end
#import "LocationManager.h"
#implementation LocationManager
#synthesize enter;
#synthesize exit;
#pragma mark - CLLocationManager delegate
static LocationManager *gInstance = NULL;
+ (LocationManager *)instance
{
#synchronized(self)
{
if (gInstance == NULL)
gInstance = [[self alloc] init];
}
return(gInstance);
}
#end
Is this correct? So all I need to do to access this is just to call instance? Inside LocationManager I also want to have only one CLLocationManager, called manager.. however, where do I initialize it so I only have one? Can I do the following? Most other singleton examples doesn't have any variables in the class, so that's where I got confused
+ (LocationManager *)sharedLocationManager
{
#synchronized(self)
{
if (lm == NULL){
lm = [[self alloc] init];
lm.manager = [[CLLocationManager alloc] init];
lm.manager.delegate = lm;
}
}
return(lm);
}
Basically -- yes.
Just a couple of small things:
static LocationManager *gInstance = NULL;
instead of NULL, you should use nil, it's a convention in Objective-C.
You should also overwrite alloc, new, copyWithZone:, and mutableCopyWithZone:. From Buck/Yacktman: "Cocoa Design Patterns", p. 153:
+ (id)hiddenAlloc
{
return [super alloc];
}
+ (id)new
{
return [self alloc];
}
+ (id)allocWithZone:(NSZone *)zone
{
return [[self sharedInstance] retain];
}
- (id)copyWithZone:(NSZone *)zone
{
[self retain];
return self;
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
return [self copyWithZone:zone];
}
This way, your singleton object cannot be copied. You need to call hiddenAlloc from your instance method (by the way, the method to access a Singleton object is often called sharedInstance in Objective-C).
For other singleton styles with their pros and cons, check out this question.
Personally, I prefer this style (copied from one of the answers on that link):
static MySingleton *sharedSingleton;
+ (void)initialize
{
static BOOL initialized = NO;
if(!initialized)
{
initialized = YES;
sharedSingleton = [[MySingleton alloc] init];
}
}
In fact, there's a tried-and-true method to create singletons already. Download the SynthesizeSingleton.h file (from a Cocoa with Love article). It contains a massive amount of pre-processor code which will generate any singleton for you. Hit the article for more details.
Since the factory method "instance" is a class-level method, the #synchronized block should be
#synchronized([LocationManager class]) {
//}
I create a new "View-based Application" project and modify the didFinishLaunchingWithOptions: method as follow.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// Add the view controller's view to the window and display.
[self.window addSubview:viewController.view];
[self.window makeKeyAndVisible];
downloader = [[InternetIOProcess alloc] init];
[downloader initWithServer:[NSURL URLWithString:#"http://www.test.com"] ];
return YES;
}
InternetIOProcess is a NSObject with two variables, and a method:
#interface InternetIOProcess : NSObject {
NSMutableArray* downloadingFile;
NSURL* serverAddress;}
#property (nonatomic,retain) NSMutableArray* downloadingFile;
#property (nonatomic,retain) NSURL* serverAddress;
-(void) initWithServer:(NSURL*) server;
the implementation of InternetIOProcess is:
#implementation InternetIOProcess
#synthesize downloadingFile,serverAddress; //,serviceuploadingQueue,;
-(void) initWithServer:(NSURL*) server
{
downloadingFile = [NSMutableArray array];
serverAddress = server;
}
And then, I write a IBAction in UIViewController response to a button "touch up inside" event:
-(IBAction) test:(id)sender
{
MyAppDelegate* d = (MyAppDelegate*)[UIApplication sharedApplication].delegate;
InternetIOProcess* thedownloader = d.downloader;
//value of "thedownloader" incorrect.
}
Try to access "thedownloader" here, its member "downloadingFile, serverAddress" both give random bad values!
Anybody know why can't I access this object?
The problem lies here with you not retaining the array and the server and bad naming convention of init. It looks like your custom init method is not getting called.
-(void) initWithServer:(NSURL*) server
{
downloadingFile = [NSMutableArray array];
serverAddress = server;
}
Try making the following changes
//InternetIOProcess.h add
-(id) initWithServer:(NSURL*) server;
//InternetIOProcess.m change
-(id) initWithServer:(NSURL*) server
{
self = [super init];
if(self != nil)
{
downloadingFile = [[NSMutableArray array] retain];
serverAddress = [server retain];
}
return self;
}
//MyAppDelegate.m
downloader = [[InternetIOProcess alloc] initWithServer:[NSURL URLWithString:#"http://www.test.com"]];
Just checking, your UIViewController contains
#import "MyAppDelegate.h"
and
#import "InternetIOProcess.h"
And your MyAppDelegate contains
#import "InternetIOProcess.h"
Also, are you getting any compiler warnings?
Is downloader a property with the retain attribute on the delegate class? I don't see you specifying retain when you allocate the instance.
In your initWithServer: method, use self.downloadingFile and self.serverAddress so that the objects are retained.