I have a custom class/object that handles gestures and conducts animations for a given view using a CADisplayLink. In its simplest form my class looks something like follows:
#interface SomeClass : NSObject
#property (strong) UIView *someView;
#end
When I add the following code to my view controller....
- (void)viewDidLoad
{
[super viewDidLoad];
SomeClass *someClass = [[SomeClass alloc] init];
someClass.someView = someView;
}
... I was anticipating my someClass object would be retained for the life of the view controller, since I am using a strong reference to someView.
However someClass is immediately deallocated.
I am already aware that I can overcome the deallocation simply by adding someClass as a property (or indeed iVar) of the view controller however I would ideally like to avoid this extra work...
so is there anyway I can have my class retained until either the view or view controller its associated with are deallocated?
EDIT
UIGestureRecognizer objects are an exmaple of a class that doesn't get deallocated when I associate them with a view...
- (void)viewDidLoad
{
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] init];
[someView addGestureRecognizer:gestureRecognizer];
}
// tapGestureRecognizer still lives
Presumably this is because the UIView takes owner ship of the UIGestureRecognizer object. Is there anyway to achieve this with my class and a UIView category? I.e....
- (void)viewDidLoad
{
[super viewDidLoad];
SomeClass *someClass = [[SomeClass alloc] init];
[someView addSomeClass:someClass];
}
If you want to associate the object with a UIView in the same way a UIGestureRecognizer does then this is technically possible using associatedObjects as follows (but I'm not sure I'd advocate this approach since associatedObjects are often frowned upon)...
SomeClass.h
#class SomeClass;
#interface UIView (SomeClass)
- (void)addSomeClass:(SomeClass *)someClass;
- (void)removeSomeClass:(SomeClass *)someClass;
#end
#interface SomeClass : NSObject
#property (strong) UIView *someView;
#end
SomeClass.m
#import "SomeClass.h"
#import <objc/runtime.h>
#implementation UIView (AssociatedObject)
- (void)addSomeClass:(SomeClass *)someClass
{
NSMutableArray *someClasses = [self someClasses];
if (someClasses == nil) {
someClasses = [[NSMutableArray alloc] init];
[self setSomeClasses:someClasses];
}
[someClasses addObject:someClass];
}
- (void)removeSomeClass:(SomeClass *)someClass
{
NSMutableArray *someClasses = [self someClasses];
if (someClasses != nil) {
[someClasses removeObject:someClass];
if (someClasses.count == 0) {
[self setSomeClasses:nil];
}
}
}
#pragma mark - Private Methods
- (NSMutableArray *)someClasses
{
return (NSMutableArray *)objc_getAssociatedObject(self, #selector(someClasses));
}
- (void)setSomeClasses:(NSMutableArray *)someClasses
{
objc_setAssociatedObject(self, #selector(someClasses), someClasses, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#end
#implementation SomeClass
#end
Implementation
- (void)viewDidLoad
{
[super viewDidLoad];
SomeClass *someClass = [[SomeClass alloc] init];
someClass.someView = someView;
[someView addSomeClass:someClass];
}
Some further reading on associatedObjects from NSHipster...
http://nshipster.com/associated-objects/
But you can declare SomeClass instance instead of property like this:
#implementation ViewController
{
SomeClass* _someClass;
}
...
- (void)viewDidLoad
{
[super viewDidLoad];
_someClass = [[SomeClass alloc] init];
_someClass.someView = someView;
}
Your SomeClass instance is holding a strong reference to the someView, but nothing is holding a reference to the SomeClass instance except the local variable inside your viewDidLoad message, so as soon as the method exits, that instance can be deallocated. As that was the object holding the only reference to your UIView the view can also be deallocated.
Your only options are to store the reference to the SomeClass object in an instance variable (or iVar) as stosha suggested or in a property. Properties are the preferred method and with automatic synthesis they don't take much more effort than a local variable declaration.
You can declare the property inside the .m file so that it isn't visible to other classes that reference your ViewController class -
In your ViewController.m file -
#interface ViewController ()
#property (strong, nonatomic) SomeClass *someClass;
#end
#implementation ViewController
...
(void)viewDidLoad
{
[super viewDidLoad];
self.someClass = [[SomeClass alloc] init];
self.someClass.someView = someView;
}
Related
I have been creating a cocoa static library in which I have a public nsobject file where I created a custom delegate. In the app I imported the nsobject file and implemented the delegate but the delegate is not getting called... the static library name is glamApi.
the SKUIDPasser.h file of the NSObject in the library
#import <Foundation/Foundation.h>
#protocol SubClassDelegate <NSObject>
#required
- (void)MethodNameToCallBack:(NSString *)s;
#end
#interface SKUIDPasser : NSObject
-(void)getSKUIDsFromCart:(NSString *)SKUIDs;
#property (nonatomic, weak) id <SubClassDelegate> delegatePasser;
#end
and the SKUIDPasser.m file
#import "SKUIDPasser.h"
#implementation SKUIDPasser
#synthesize delegatePasser;
-(void)getSKUIDsFromCart:(NSString *)SKUIDs{
NSLog(#"getSKUIDsFromCart %#",SKUIDs);
[delegatePasser MethodNameToCallBack:SKUIDs];
}
#end
And the method is called from a Viewcontroller in static library
- (IBAction)CartShowEvent:(id)sender {
if (![cartBadge isHidden]) {
buyClicked = TRUE;
[self loadCart];
[self showCartItemsAll];
self.cartView.frame = self.view.bounds;
[self.view addSubview:self.cartView];
SKUIDPasser *pass = [[SKUIDPasser alloc] init];
[pass getSKUIDsFromCart:#"sssss"];
} else {
[Utilities alert:#"No products to display !!!"];
}
}
The Viewcontroller which the custom delegate has to be implemented Viewcontroller.h
#import <glamAPI/SKUIDPasser.h>
#interface ViewController : UIViewController<SubClassDelegate>{
SKUIDPasser *sk;
}
Viewcontroller.m
- (void)viewDidLoad {
[super viewDidLoad];
sk = [[SKUIDPasser alloc] init];
sk.delegatePasser = self;
NSLog(#"sk.delegatePasser %#",sk.delegatePasser);
}
- (void)MethodNameToCallBack:(NSString *)s
{
NSLog(#"MethodNameToCallBack %#",s);
}
I didn't get any error but the method is not calling..Please help me to resolve this
The very first thing you need to understand is that each instance object of a class is entirely different entity and maintains it's state separately.
In you case your have created an object of your static library in viewDidLoad: and set the delegate accordingly, but when you are making the call to method getSKUIDsFromCart, you are using a different instance for which you never set the delegate property. That's why there was no callback.
To solve this, you can set the delegate in method CartShowEvent: before making the call, something like this
SKUIDPasser *pass = [[SKUIDPasser alloc] init];
pass.delegatePasser = self;
[pass getSKUIDsFromCart:#"sssss"];
However i would suggest that you should use the instance variable of library which you already created in viewDidLoad:
- (IBAction)CartShowEvent:(id)sender {
if (![cartBadge isHidden]) {
buyClicked = TRUE;
[self loadCart];
[self showCartItemsAll];
self.cartView.frame = self.view.bounds;
[self.view addSubview:self.cartView];
//No need to create another object.
//SKUIDPasser *pass = [[SKUIDPasser alloc] init];
//Use the previously created instance object
[sk getSKUIDsFromCart:#"sssss"];
}
else {
[Utilities alert:#"No products to display !!!"];
}
}
The SKUIDPasser object that you are calling within (IBAction)CartShowEvent:(id)sender and the SKUIDPasser object that you are setting the delegate are NOT the same.
Just for a test, try calling the method [sk getSKUIDsFromCart:#"sssss"]; just after you set the delegate and you will see that it will be called because this instance has the delegate set correctly:
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
sk = [[SKUIDPasser alloc] init];
sk.delegatePasser = self;
[sk getSKUIDsFromCart:#"sssss"];
NSLog(#"sk.delegatePasser %#",sk.delegatePasser);
}
- (void)MethodNameToCallBack:(NSString *)s
{
NSLog(#"MethodNameToCallBack %#",s);
}
Update
I updated my answer to help you call the trigger from the static library
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
sk = [[SKUIDPasser alloc] init];
sk.delegatePasser = self;
/*
You now can pass this variable to the static library to get called
from there ...
example:
viewControllerOnStaticLibrary.passer = sk;
*/
NSLog(#"sk.delegatePasser %#",sk.delegatePasser);
}
- (void)MethodNameToCallBack:(NSString *)s
{
NSLog(#"MethodNameToCallBack %#",s);
}
Viewcontroller_in_static_library.h
#property (nonatomic, strong) SKUIDPasser *passer;
Viewcontroller_in_static_library.m
- (IBAction)CartShowEvent:(id)sender {
if (![cartBadge isHidden]) {
buyClicked = TRUE;
[self loadCart];
[self showCartItemsAll];
self.cartView.frame = self.view.bounds;
[self.view addSubview:self.cartView];
//now you are calling the same instance
[self.passer getSKUIDsFromCart:#"sssss"];
} else {
[Utilities alert:#"No products to display !!!"];
}
}
I'm having the following problem.
I've created a custom view controller that has a some useful methods that I need.
This is the code in the .h
#interface MYViewController : UIViewController
- (void)method;
- (void)otherMethod;
#end
This is my init method of MYViewController class:
- (instancetype)init
{
self = [super init];
return self;
}
Then when I try to extend that class I can't set the title of the child controller. For example, in "MYOtherController.h"
#interface MYOtherViewController : MYViewController
- (void)childControllerMethod;
#end
And this is the init of MYOtherViewController:
- (instancetype)init
{
self = [super init];
return self;
}
And then, if I instantiate a MYOtherViewController object and try to set the title, it happens nothing. For example:
MYOtherViewController *controller = [[MYOtherViewController] alloc] init];
controller.title = #"Hello";
If I put this in the viewDidLoad of the MYOtherViewController class it logs that title is nil:
- (void)viewDidLoad {
NSLog(#"title: %#", self.title);
[super viewDidLoad];
}
Why can't set the title in this child class?
the title for the viewcontroller hasnt been made yet after the alloc init, you would need to set it after the viewDidLoad (which is when all the UI elements have been initialized), so what you can do is make an #property on the viewcontroller which you set after the alloc init, then in the viewDidLoad, set the title to the value of the #property
I have two Classes - say Class1 and Class2
Class1 has button which when pressed does a push segue to Class2.
Also, when the button is pressed a network call is being done on another class.
Class1 code :
- (IBAction)sellPressed:(id)sender
{
SPNNetworkEngine *networkEngine = [[SPNNetworkEngine alloc] init];
networkEngine.delegate = self;
[networkEngine getFarePrice];
}
In Network class, I have :
In .h file
#protocol SPNNetworkEngineDelegate <NSObject>
- (void) farePriceReceivedWithDetails:(id) farePrice;
#end
#property (nonatomic, weak) id delegate;
- (void) getFarePrice;
In .m file
- (void) getFarePrice
{
...........
.......
//After all the operations, I delegate
if ([delegate respondsToSelector:#selector(farePriceReceivedWithDetails:)])
[delegate farePriceReceivedWithDetails:cost];
}
}
And in Class2 I have,
- (void)viewDidLoad
{
[super viewDidLoad];
SPNNetworkEngine *networkEngine = [[SPNNetworkEngine alloc] init];
networkEngine.delegate = self;
}
// Delegate method
- (void) farePriceReceivedWithDetails:(id)farePrice
{
NSLog(#"FarePrice %#", farePrice);
}
The delegate method in Class2 is never called. What have I done wrong here?
But when I write the delegate method -(void)farePriceReceivedWithDetails:(id)farePrice in Class1, its working fine.
Your Coding approch is wrong You alloc SPNNetworkEngine class in Your Class1 and set delegate of SPNNetworkEngine is self. so The delegate is fire in class1. But you want this method in class2 so you have to assign this delegate with class2 object.
Second point is you have to Change this line with this
#protocol SPNNetworkEngineDelegate <NSObject>
- (void) farePriceReceivedWithDetails:(id) farePrice;
#end
#property (nonatomic, strong) id <SPNNetworkEngineDelegate> delegate;
- (void) getFarePrice;
You should retain this object in Class1.h as instance variable. So that it will be alive till that class object exist. What you are doing currently is simply allocating it in a method. once the method executed & finished, it will be deallocated. So your delegate call back will not work.
So avoid this line
SPNNetworkEngine *networkEngine = [[SPNNetworkEngine alloc] init];
change the above code to like below
Declare in Class1
#property (nonatomic, strong) SPNNetworkEngine *networkEngine;
Then do
self.networkEngine = [[SPNNetworkEngine alloc] init];
In the Class1 you are calling to [networkEngine getTestPrice] and I think that mean the [networkEngine getFarePrice] function (because is where you call the delegate)
In Class2 you must call the same function: [networkEngine getTestPrice]
Have you added the SPNNetworkEngineDelegate to the list of delegates in the Class2.h?
i.e.
#interface Class2 : UIViewController <SPNNetworkEngineDelegate>
Are there any dangers in storing a casted reference to MyView in my subclass of UIViewController? I am trying to avoid casting self.view every time I want to use an instance variable/method because it looks ugly. For example, let's avoid:
[((MyView *)self.view) instancedView]
castedView is defined at the same time as self.view
castedView.instancedView is a subview of castedView stored as an instance variable (a UIButton that I want to attach a tap event listener to, for example)
MyViewController.h
#interface MyViewController : UIViewController
#property MyView *castedView;
#end
MyViewController.m
#implementation MyViewController
#synthesize castedView;
- (void)viewDidLoad {
[super viewDidLoad];
castedView = [[MyView alloc] init];
self.view = castedView;
// ... create gestureRecognizer named "onTap" that calls "whenTapped" ...
[castedView.instancedView addGestureRecognizer:onTap];
}
- (IBAction)whenTapped {
castedView.instancedView.backgroundColor = [UIColor whiteColor];
}
#end
Any help is appreciated.
So I have two classes. When press the save button, it will pass down the value from self.screen.text by addItem method to the totalArray in class 2. If I try to NSLog in the #implementation of addItem method, then it will give out the correct output but If I do it in viewDidLoad, the output is null. How can I save the value passing from class1 to property of class2 permanently? Thank you. The class2 in a subclass of UITableViewController
Class 1 #interface
//class1.h
#import class2.h
#interface class1 : superclass {
}
- (IBAction)buttonSave:(id)sender;
Class1 #implementation
//class1.m
#interface class1 ()
#end
#implementation class1 {
}
- (IBAction)buttonSave:(id)sender {
class2 *Obj = [[class2 alloc] init];
[Obj addItem:self.screen.text];
}
And class2 #interface
//class2.h
#import class2.h
#interface {
}
#property (strong, nonatomic) NSMutableArray *totalArray;
class2 #implementation
#interface class2 ()
#end
#implementation {
}
- (void) addItem:(id)item {
self.totalArray = [[NSMutableArray alloc] init]; //alloc & init
[self.totalArray addObject:item]; //add object to the total array
// NSLog(#"%#", self.totalArray); If I NSLog in within this method then everything works as expected.
}
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"%#", self.totalArray); //But in here the output is null. ???
}
I think that your problem is that you have use a different class2 object. The one that you had init in buttonSave, is not the one that you are displaying
add a property in class1.h
#property (nonatomic, strong) NSMutableArray *savedArray;
and modify buttonSave :
- (IBAction)buttonSave:(id)sender {
self.savedArray = [[NSMutableArray alloc] init];
[self.savedArray addObject:self.screen.text];
}
You are using a storyboard, then please try to add this in class1.h and add an identifier class2Segue to this segue in your storyboard :
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"class2Segue"])
{
Class2 *tableController = (Class2 *)[segue destinationViewController];
tableController.totalArray = self.savedArray;
}
}
viewDidLoad is called after init so you array is nil here. Change your class2 init method to accept the item.
// In class2
-(id) initWithStyle:(UITableViewStyle)style andItem:(id)item {
self = [super initWithStyle:style];
if(self) {
self.totalArray = [[NSMutableArray alloc] init];
[self.totalArray addObject:item];
}
return self;
}
Your addItem will then look like,
- (void) addItem:(id)item {
//Just add, do not initialize again
[self.totalArray addObject:item];
}
The button action in class1 will now look like,
- (IBAction)buttonSave:(id)sender {
class2 *Obj = [[class2 alloc] initWithItem:self.screen.text];
//OR
//class2 *Obj = [[class2 alloc] initWithItem:UITableViewStylePlain andItem:self.screen.text];
}
Hope that helps!
Try to use like this...
- (IBAction)buttonSave:(id)sender
{
class2 *Obj = [[class2 alloc] init];
Obj.totalArray = [[NSMutableArray alloc] init]; //alloc & init
[Obj.totalArray addObject:self.screen.text];
NSLog(#"screen.text %#", self.screen.text); // -- check here it may be null----
NSLog(#"Obj.totalArray %#", Obj.totalArray);
}
#interface class2 ()
#end
#implementation {
}
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%#", self.totalArray); //But in here the output is null. ???
}
You can not ensure when your viewDidLoad method will call... so better pass the value to the init method and set there initWithText:(NSString*)text{}. Other wise try to call NSLog in viewWillAppear or viewDidAppear just for testing purpose. In iOS 7 now presentation of view-controllers is bit changed now.