I have this myViewController, that instantiates instances of itself.
Currently, I have a UIButton, that triggers the method
-(void)somethingImportant
However, I want that somethingImportant to happen during the ViewDidLoad, so I don't have to push that button.
But if I put somethingImportant in the ViewDidLoad of myViewController, it is recursively called as many times I have a subview of myViewController.
I tried to put somethingImportant in the application didFinishLaunchingWithOptions: of my app delegate, but somehow that does't work either.
EDIT
So here's the code that might be relevant. I have this UIScrollView with a lot of subviews of myViewController:
- (void)configureScrollView
{
for (int i = 0; i < [self.childViewControllers count]; i++) {
...
myViewController * theSubview =[self.childViewControllers objectAtIndex:i];
....
[theScrollView addSubview:[theSubview view]];
}
}
What is the best approach to make sure that somethingImportant is called only once?
I have this class, that instantiates instances of itself.
This inherently sounds like a bad idea and can easily lead to recursion if you're not careful. Therefore I would suggest you rethink your logic. If you need multiple instances of a class, you should be managing those instances from outside that class, not from within.
However, if you're still insistent on doing this - you can do something similar to what sschale suggests and use a variable to keep track of whether you've called your method or not.
The thing is you'll need to define this variable as static in order for it to be stored at class scope, not instance scope.
For example:
static BOOL firstCalled = NO;
- (void)viewDidLoad {
[super viewDidLoad];
if (!firstCalled) {
firstCalled = YES;
[self foo];
}
}
Each subclass should be calling [super viewDidLoad], on up the chain, so that code really should only be called once.
However, if you need to make sure it executes only once, add #property (nonatomic) BOOL runOnce; to that file's interface, and then in -(viewDidLoad) do:
if(!self.runOnce) {
//all code that is only run once
self.runOnce = YES;
}
Related
I am having a singleton class MyController of type UIViewController. I could able to access the view property like [MyController sharedInstance].view and could able to set that to nil like,
[MyController sharedInstance].view = nil;
I wanna restrict someone accessing view property. How could I stop/restrict that?
I found the solution myself. I overridden the method as
-(void)setView:(UIView*)view {
if (view == nil) {
//ignore - make no change
}
else {
//default performance
[super setView:view];
}
}
Class ProjectSingleton {
static let shared = ProjectSingleton()
//here you can make any variable or function and use it anywhere in project
}
I am using the controller in multiple screens. If the controller is created n number of times, it adds weight to the app. So, to overcome that, I am creating it once and reusing it every where.
You can create a base class for your UIViewController that can be used across multiple views. iOS will handle the memory management correctly if you load a new view. The old view will be unloaded and
- (void)viewDidDisappear:(BOOL)animated
- (void)viewWillDisappear:(BOOL)animated
will be called. There you can dispose of any excess data.
Since switching to storyboards, I load a view controller via
[self performSegueWithIdentifier:#"identifier" sender:self]
This works perfectly. Now, if I want to set any properties on the destination view controllers, I implement the method prepareForSegue:sender: and set what properties I need to set. Everything works as expected without any problems.
Ever since I starting using this approach over the old
MyViewController *vc = ....
vc.prop = #"value";
[self.navigationController pushViewController:vc];
I've felt that passing parameters to the destination view controller is a little hacky, in particular if the value you're trying to set is not just a static value.
Lets say for example, I have a button which fetches some data from a server. When the data returns, it creates a new object, and then presents a new view controller to display this object. To do this, I call performSegueWithIdentifier:sender:, but that's the end of it. My object is now deallocated and no longer exists, and I have no way of passing it to the prepareForSegue:sender: method, unless I store it in an instance variable.
This feels pretty horrible, as the object isn't meant to last longer than this action, and has no relation to anything else in my current view controller.
In this situation, I understand that I could quite simply request the data in the new view controller but it's just an example.
My question is, is there another way of doing this without it feeling so hacky? Can I get this data into the destination view controller without storing it in an instance variable?
I know I could still use the old approach, but I'd like to stick with the storyboard methods if I can.
Well the sender parameter of the performSegueWithIdentifier:sender is the same one received by the prepareForSegue:sender. So if you want to send a variable to your prepareForSegue:sender the sender is your friend. In your case:
SomeViewController.m
-(void)aMethodThatDownloadsSomeDataFromServer {
NSString *exampleData = [self someDataThatIDownloaded];
[self performSegueWithIdentifier:#"yourSegueIdentifier" sender:exampleData];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if(segue.identifier isEqualToString:#"yourSegueIdentifier"]) {
if([sender isKindOfClass:[NSString class]]) { //maybe you want to send different objects
segue.destinationViewController.stringProperty = sender;
}
else {
segue.destinationViewController.objectPorperty = sender;
}
}
}
The accepted solutios is correct but I frequently use another approach when data are shared between more than two segue. I frequently create a singleton class (let's call it APPSession) and I use it as a datamodel, creating and maintaining a session-like structure I can write and read from everywhere in the code.
For complex applications this solution maybe requires too much error prone coding but I've used it succesfully in a lot of different occasions.
APPSession.m
//
// APPSession.m
//
// Created by Luca Adamo on 09/07/12.
// Copyright 2012 ELbuild. All rights reserved.
//
#import "APPSession.h"
#implementation APPSession
#synthesize myProperty;
static APPSession *instance = nil;
// Get the shared instance and create it if necessary.
+ (APPSession *)instance {
if (instance == nil) {
instance = [[super allocWithZone:NULL] init];
}
return instance;
}
// Private init, it will be called once the first time the singleton is created
- (id)init
{
self = [super init];
if (self) {
// Standard init code goes here
}
return self;
}
// This will never be called since the singleton will survive until the app is finished. We keep it for coherence.
-(void)dealloc
{
}
// Avoid new allocations
+ (id)allocWithZone:(NSZone*)zone {
return [self sharedInstance];
}
// Avoid to create multiple copies of the singleton.
- (id)copyWithZone:(NSZone *)zone {
return self;
}
APPSession.h
//
// APPSession.h
//
// Created by Luca Adamo on 09/07/12.
// Copyright 2012 ELbuild. All rights reserved.
//
#import <Foundation/Foundation.h>
#interface APPSession : NSObject{
}
#property(nonatomic,retain) NSString* myProperty;
+ (id)sharedInstance;
#end
How to read and write the property myProperty from every part of the app code.
// How to write "MyValue" to myProperty NSString *
[APPSession instance] setMyProperty:#"myValue"]
// How to read myProperty
NSString * myVCNewProperty = [[APPSession instance] myProperty];
With this mechanism I can safely write for instance a value in the APPSession in the first ViewController, perform a segue to a second one, perform another segue to a third one and use the variable written during the first segue.
It's more or less like a SessionScoped JavaBean in Java EE. Please feel free to point out problems in this approach.
All of these answers are correct, but I've found a pretty cool way of doing this. I've tested only in iOS 7 and iOS 8
After declaring and setting the value of the object you wish to pass, in the prepareForSegue method,
[segue.destinationViewController setValue:event forKey:#"property"];
//write your property name instead of "property
I spent much time to get a better understanding in delegation in Objective-C. I got it working for most cases, but there is a problem in a specific case, which I find difficult to understand. Let me explain what I am trying to do:
I have a custom view called GridLayoutView, which is subclass of UIView. I also have a view controller SomeViewController, which is the delegate of GridLayoutView.
I have a custom initWithFrame method, and I am conditionally calling another initialization method baseInit. That method calls a delegate method at some time. Here is some code from GridLayoutView:
//
// Delegator
// GridLayoutView.m
//
#implementation GridLayoutView
- (id)initWithFrame:(CGRect)frame
numberOfRows:(NSUInteger)rows
numberOfCols:(NSUInteger)cols
{
self = [super initWithFrame:frame];
if (self) {
self.numberOfRows = rows;
self.numberOfCols = cols;
self.numberOfCells = rows * cols;
if (self.numberOfCells > 0) [self baseInit];
}
return self;
}
- (void)baseInit
{
// do some more initialization stuff here
// ...
// then call a delegate method
[self.delegate someMethod:someObj];
// However, this method is not called because self.delegate is nil
}
and some code from SomeViewController:
//
// Delegate
// SomeViewController.m
//
#implementation SomeViewController
// ...
// in some method
self.gridLayoutView = [[GridLayoutView alloc] initWithFrame:gridLayoutFrame
numberOfRows:rowsCount
numberOfCols:colsCount];
self.gridLayoutView.delegate = self;
// ...
The delegate method never gets called within baseInit, because the delegate is nil at that time and it gets set after initWithFrame and baseInit methods are done. I have confirmed this.
I sense that there is something wrong in my workflow of delegation. I have a solution but I don't think it is the best way to go. The solution is basically passing the SomeViewController instance to the delegator by modifying the initWithFrame method such as:
- (id)initWithFrame:(CGRect)frame
numberOfRows:(NSUInteger)rows
numberOfCols:(NSUInteger)cols
delegate:(id<GridLayoutViewDelegate>)aDelegate
This approach works, but I am uncomfortable due to passing SomeViewController to GridLayoutView in its initWithRect. I am wondering if this is a good way to go with delegation or is there a better approach? I would be very grateful if someone can clear this for me.
If I'm understanding you correctly, there aren't many options here.
Modifying your initializer (as you suggested) to pass in the delegate. There is nothing wrong with that, don't know why you don't like it.
Remove the dependency on the delegate during initialization and instead, send whatever delegate message is appropriate when the delegate property is set by overriding the setter:
- (void)setDelegate:(id<GridLayoutViewDelegate>)aDelegate
{
_delegate = aDelegate;
// send whatever message makes sense to the delegate
[_delegate someMethod:object];
}
EDIT - noticed your comment
Your initialization method should not take any significant amount of time. It's unclear what you mean by 'loading views'. If you simply mean creating and adding subviews to a view then that is fast and there should be no need to communicate progress to a delegate (which you can't do anyway b/c the initialization is on the main thread and UI won't update until all of init is complete).
If you mean loading data that takes a long time, you should disconnect that from initialization and load the data in a background operation, sending progress messages to a delegate.
i would implement the setDelegate function and then call
[self someMethod:someObj]; from there
I need to perform an action in the dealloc method of a category. I've tried swizzling but that doesn't work (nor is it a great idea).
In case anyone asks, the answer is no, I can't use a subclass, this is specifically for a category.
I want to perform an action on delay using [NSTimer scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:] or [self performSelector:withObject:afterDelay:] and cancel it on dealloc.
The first issue is that NSTimer retains the target, which I don't want. [self performSelector:withObject:afterDelay:] doesn't retain, but I need to be able to call [NSObject cancelPreviousPerformRequestsWithTarget:selector:object:] in the dealloc method or we get a crash.
Any suggestions how to do this on a category?
I still think it would be better to subclass your class and not mess with the runtime, but if you are definitely sure you need to do it in a category, I have an option in mind for you. It still messes with the runtime, but is safer than swizzling I think.
Consider writing a helper class, say calling it DeallocHook which can be attached to any NSObject and perform an action when this NSObject gets deallocated. Then you can do something like this:
// Instead of directly messing with your class -dealloc method, attach
// the hook to your instance and do the cleanup in the callback
[DeallocHook attachTo: yourObject
callback: ^{ [NSObject cancelPrevious... /* your code here */ ]; }];
You can implement the DeallocHook using objc_setAssociatedObject:
#interface DeallocHook : NSObject
#property (copy, nonatomic) dispatch_block_t callback;
+ (id) attachTo: (id) target callback: (dispatch_block_t) block;
#end
Implementation would be something like this:
#import "DeallocHook.h"
#import <objc/runtime.h>
// Address of a static global var can be used as a key
static void *kDeallocHookAssociation = &kDeallocHookAssociation;
#implementation DeallocHook
+ (id) attachTo: (id) target callback: (dispatch_block_t) block
{
DeallocHook *hook = [[DeallocHook alloc] initWithCallback: block];
// The trick is that associations are released when your target
// object gets deallocated, so our DeallocHook object will get
// deallocated right after your object
objc_setAssociatedObject(target, kDeallocHookAssociation, hook, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return hook;
}
- (id) initWithCallback: (dispatch_block_t) block
{
self = [super init];
if (self != nil)
{
// Here we just copy the callback for later
self.callback = block;
}
return self;
}
- (void) dealloc
{
// And we place our callback within the -dealloc method
// of your helper class.
if (self.callback != nil)
dispatch_async(dispatch_get_main_queue(), self.callback);
}
#end
See Apple's documentation on Objective-C runtime for more info about the associative references (although I'd say the docs are not very detailed regarding this subject).
I've not tested this thoroughly, but it seemed to work. Just thought I'd give you another direction to look into.
I just stumbled on a solution to this that I haven't seen before, and seems to work...
I have a category that--as one often does--needs some state variables, so I use objc_setAssociatedObject, like this:
Memento *m = [[[Memento alloc] init] autorelease];
objc_setAssociatedObject(self, kMementoTagKey, m, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
And, I needed to know when the instances my category extending were being dealloced. In my case it's because I set observers on self, and have to remove those observers at some point, otherwise I get the NSKVODeallocateBreak leak warnings, which could lead to bad stuff.
Suddenly it dawned on me, since my associated objects were being retained (because of using OBJC_ASSOCIATION_RETAIN_NONATOMIC), they must be being released also, and therefore being dealloced...in fact I had implemented a dealloc method in the simple storage class I had created for storing my state values. And, I postulated: my associated objects must be dealloced before my category's instances are! So, I can have my associated objects notify their owners when they realize they are being dealloced! Since I already had my retained associated objects, I just had to add an owner property (which is not specified as retain!), set the owner, and then call some method on the owner in the associated object's dealloc method.
Here's a modified part of my category's .m file, with the relevant bits:
#import <objc/runtime.h> // So we can use objc_setAssociatedObject, etc.
#import "TargetClass+Category.h"
#interface TargetClass_CategoryMemento : NSObject
{
GLfloat *_coef;
}
#property (nonatomic) GLfloat *coef;
#property (nonatomic, assign) id owner;
#end
#implementation TargetClass_CategoryMemento
-(id)init {
if (self=[super init]) {
_coef = (GLfloat *)malloc(sizeof(GLfloat) * 15);
}
return self;
};
-(void)dealloc {
free(_coef);
if (_owner != nil
&& [_owner respondsToSelector:#selector(associatedObjectReportsDealloc)]) {
[_owner associatedObjectReportsDealloc];
}
[super dealloc];
}
#end
#implementation TargetClass (Category)
static NSString *kMementoTagKey = #"TargetClass+Category_MementoTagKey";
-(TargetClass_CategoryMemento *)TargetClass_CategoryGetMemento
{
TargetClass_CategoryMemento *m = objc_getAssociatedObject(self, kMementoTagKey);
if (m) {
return m;
}
// else
m = [[[TargetClass_CategoryMemento alloc] init] autorelease];
m.owner = self; // so we can let the owner know when we dealloc!
objc_setAssociatedObject(self, kMementoTagKey, m, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return m;
}
-(void) doStuff
{
CCSprite_BlurableMemento *m = [self CCSprite_BlurableGetMemento];
// do stuff you needed a category for, and store state values in m
}
-(void) associatedObjectReportsDealloc
{
NSLog(#"My associated object is being dealloced!");
// do stuff you need to do when your category instances are dealloced!
}
#end
The pattern here I learned somewhere (probably on S.O.) uses a factory method to get or create a memento object. Now it sets the owner on the memento, and the memento's dealloc method calls back to let the owner know it's being dealloced
CAVEATS:
Obviously, you have to have your associated object set with OBJC_ASSOCIATION_RETAIN_NONATOMIC, or it won't be retained and released for you automatically.
This becomes trickier if your memento/state associated object gets dealloced under other circumstances than the owner being dealloced...but you can probably train one object or the other to ignore that event.
The owner property can't be declared as retain, or you'll truly create a strong reference loop and neither object will ever qualify to be dealloced!
I don't know that it's documented that OBJC_ASSOCIATION_RETAIN_NONATOMIC associated objects are necessarily released before the owner is completely dealloced, but it seems to happen that way and almost must be the case, intuitively at least.
I don't know if associatedObjectReportsDealloc will be called before or after the TargetClass's dealloc method--this could be important! If it runs afterwards, if you try to access member objects of the TargetClass you will crash! And my guess is that it's afterwards.
This is a little messy, because you're double-linking your objects, which requires you to be very careful to keep those references straight. But, it doesn't involve swizzling, or other interference with the runtime--this just relies on a certain behavior of the runtime. Seems like a handy solution if you already have an associated object. In some cases it might be worth creating one just to catch your own deallocs!
Your proposed solution unfortunately won't work: because NSTimer retains its target, the target will never run its dealloc until the timer has been invalidated. The target's retain count will always be hovering at 1 or above, waiting for the timer to release it. You have to get to the timer before dealloc. (Pre-ARC, you could override retain and release and destroy the timer, although that's really not a good solution.)
NSThread also has this problem, and the solution is simple: a bit of redesigning separates the controller of the thread from the "model". The object which creates and owns the thread, or timer in this case, should not also be the target of the timer. Then, instead of the retain cycle you currently have (timer owns object which owns timer), you have a nice straight line: controller owns timer which owns target. Outside objects only need to interact with the controller: when it is deallocated, it can shut down the timer without you having to play games with overriding dealloc or other memory management methods.
That's the best way to handle this. In the case that you can't do that for some reason -- you're talking about category overrides, so apparently you don't have the code for the class which is the target of the timer (but you can still probably make a controller even in that case) -- you can use weak references. Unfortunately I don't know any way to make an NSTimer take a weak reference to its target, but GCD will give you a fair approximation via dispatch_after(). Get a weak reference to the target and use that exclusively in the Block you pass. The Block will not retain the object through the weak reference (the way NSTimer would), and the weak reference will of course be nil if the object has been deallocated before the Block runs, so you can safely write whatever message sends you like.
Is there a method that is always called in Cocoa? Many classes have init or initWith, but even worse they can be loaded from a nib or something. I don't want to have to scrape around and find how it does this in this case. I just want to set some initial variables and other things, and I want a method to subclass that I can depend on no matter if it's a UIView, UIViewController or UITableViewCell etc.
No there is not such a method. init comes from NSObject so every object can use it, and as well subclasses define their own initialization methods. UIView, for example, defines initWithFrame: and furthermore there are init methods from protocols, such as NSCoding which defines initWithCoder:. This is the dynamic nature of objective-C, anything can be extended at any time. That being said, there are some patterns. UIViewController almost always takes initWithNibName:bundle: and UIView almost always takes initWithFrame: or initWithCoder:. What I do is make an internal initialize method, and just have the other inits call it.
- (void)initialize
{
//Do stuff
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
[self initialize];
}
}
- (id)initWithCoder:(NSCoder *)aCoder
{
self = [super initWithCoder:aCoder];
if(self)
{
[self initialize];
}
}
Not 100% sure that it is always called, but I am pretty sure that this is a viable option. To be perfectly honest, I can't recall that I have ever seen this method used in practice and I usually shy away from using this method (I have absolutely no idea why, probably because it's just not the cleanest and most comprehensive method to achieve this...):
-didMoveToSuperview()
From documentation:
Tells the view that its superview changed.
The default implementation of this method does nothing. Subclasses can override it to perform additional actions whenever the superview changes.
There's many ways you can write a custom initializer.
- (id)initWithString:(NSString *)string {
if((self == [super init])) {
self.string = string;
}
return self;
}
That's just how I write my initializers in general. For example, the one above takes a string. (you don't have to pass strings if you don't want).
Btw, init is a method. According to the header for NSObject, init has a method implementation.