I've been working on a project for several weeks, and recently implemented a singleton object to assist with saving data. After this was implemented, I've been having issues updating labels inside my main view controller.
For example, I'm trying to update the following labels:
#property (nonatomic, retain) IBOutlet UILabel *numDrinksLabel;
#property (nonatomic, retain) IBOutlet UILabel *BACLabel;
with the following code, which is inside a function that gets called on a button press:
BACLabel.text = [NSString stringWithFormat:#"%.2f", user.BAC];
numDrinksLabel.text = [NSString stringWithFormat:#"(%i)", user.numDrinks];
this code block gives me the runtime error:
-[__NSCFString setText:]: unrecognized selector sent to instance 0x1197ef40
However, the same code block called inside viewDidLoad or viewDidAppear is executed with no problems. Initially this suggested to me that there was a problem with my #property declaration, but I get the same error when I change retain to strong, and when I change to weak, the uilabel object is simply null, which is to be expected but nonetheless very frustrating.
So the question is, why would the label objects become dealloced after the viewDidAppear function?
Any suggestions on how to fix this or further test for the root cause would be greatly appreciated!
It seems that your object which contains the iVars numDrinksLabel and BACLabel does no longer exist when you assign something to the text property of the UILabel objects.
Since this happens after you press a button, you have been in the main event loop before. In this loop, any autorelease object will be released if it is not retained by some object.
Thus it seems to me that the object that has your UILabels as iVars is an autorelease object, and it is not retained because you don't use setter methods like self.BACLabel.text = but simply assign methods as BACLabel.text =.
So try replacing your assignments like BACLabel.text = by setters like self.BACLabel.text =, as sixthcent said.
Please check if the superview of these labels is also declared strong
Related
I have an Manual Reference Count project, where few classes Im converting to ARC by removing retain,release & etc and by setting compiler flag “-fobjc-arc”
Their are 2 ARC(-fobjc-arc) enabled view controller classes, ClassA and ClassB.
I am allocating and initialising objects of ClassB inside ClassA within a for loop to achieve some functionality, Code snippet is as below,
#interface ClassA ()
#property (strong, nonatomic) ClassB *classBObj;
#end
#implementation ClassA
- (void)createClassBView {
for (int count = 0; count <= [dataObject count]; count++) //if count is more than 1 it is not retaining the previous classBObj
{
classBObj = [[ClassB alloc] init]; //ARC is keeping only 1 object reference of this class but I need to retain all the iterated objects
[self.scrollView addSubView:classBObj withFrame:myFrame];//only 1 view is getting added as subview even if control comes here more than once
}
}
#end
The above code works fine for me in non-ARC(MRC) but fails to work properly when ARC is enabled. It is not retaining ClassB objects even if it is strong,
Only 1 object i.e; last iterated ClassB object reference is alive, rest are getting destroyed and it is throwing exception "ClassB reference to an deallocated instance"
I tried by using if(!classBObj){classBObj = [[ClassB alloc] init];} inside loop, that time I'm not getting ClassB reference to an deallocated instance exception but only 1 subview of ClassB is getting added to my scrollview(i.e; last iterated).
Please guide me on this.
Any help is appreciated in advance.
Your code is doing exactly what you are telling it to do. You are setting the very same reference, self.classBObj, to a ClassB instance - over and over, in your loop. Each time through the loop, the existing ClassB instance that was previously assigned self.classBObj needs to "get out of the way" so that a new one can be assigned to self.classBObj. So it is rightly released when it is replaced by the new one - rightly, because there is now no existing reference to it.
The truth is that you were totally mismanaging the memory here before ARC, and adopting ARC has revealed this fact. You're just lucky your code ever worked (or seemed to). If you want to maintain multiple ClassB instances, you need your instance variable to be an array of them, not a single one.
(On the other hand, if classBObj is a UIView and is to be added immediately to the interface as a subview, that is still happening, so it's hard to see what your complaint is. Indeed, the weird part is why you ever needed classBObj to be a property in the first place; why isn't it just a local variable? It's not like you need these references to be retained elsewhere, since you have those references — as subviews of your self.scrollView. But if you need those references for some later purpose, and if you don't want to obtain them by using the fact that they are subviews of your scroll view, then clearly you need an array of them, as I just said.)
I started Objective-C programming a couple weeks ago, so my understanding of how all these pieces fit together & the order they're all happening is still confusing to me. I'm trying to make a JSON API call to one of my apps using NSURLSession. That all works flawlessly, but I want to update a label with a piece of the data that's returned & anytime I look at/try to update the label, I get null.
Some of the SO posts I've found that are similar to my problem include: this, this, this, and this. Coming from the world of Ruby on Rails, I haven't had to deal with async concepts at all, but I know I'm close.
Here's the relevant snippet of code in question:
if (!jsonError) {
NSDictionary *skillBuildData = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%#:", skillBuildNameLabel.text); // should say "Build Name" but returns null
NSLog(#"%#", skillBuildData[#"name"]); // correctly prints the result
NSLog(#"%#:", skillBuildNameLabel.text); // should have contents of skillBuildData[#"name"] but returns null
skillBuildNameLabel.text = skillBuildData[#"name"]; // obviously results in null, but I don't know why.
});
}
EDIT:
Not sure if it's relevant, but here's the bulk of my ViewController.h to give you an idea of the outlets & actions in this very simple app. One button, one method, the IBOutlet that links the button & JSON call method, and a label:
#interface ViewController : UIViewController {
IBOutlet UILabel *skillBuildNameLabel;
IBOutlet UIButton *getSkillBuildDataButton;
}
- (void)getSkillBuildDataById:(int) skillBuildId;
- (IBAction)buttonPressed;
It seems like I'm very close, I just can't see the link I'm missing. Thank you so much in advance!
EDIT 2:
Check out Ben Kreeger's comment to the response I marked as the answer. I didn't connect the actual label in my storyboard to the outlet I created in my ViewController.h. I had no idea you could drag the line from the element in a storyboard to an actual line of code. That was the missing piece. Looks like I have a lot more to learn about Xcode & Objective-C. Thanks to all who helped me out!
You may have more luck declaring your IBOutlets as properties (#property) instead of as instance variables (see this answer for why weak instead of strong).
#interface ViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *skillBuildNameLabel;
#property (weak, nonatomic) IBOutlet UIButton *getSkillBuildDataButton;
...
#end
Then you'll be able to reference them as self.skillBuildNameLabel and self.getSkillBuildDataButton in your implementation.
Beware that this self.x notation inside of a callback like that may lead to what's called a retain cycle. If this is the case, Xcode will warn you about this. Here's a bit on retain cycles for you.
Footnote: I rarely ever see (and never write) this syntax anymore for declaring instance variables.
#interface ViewController : UIViewController {
IBOutlet UILabel *skillBuildNameLabel;
IBOutlet UIButton *getSkillBuildDataButton;
}
Use properties instead.
You are doing the logging before setting the text. move
skillBuildNameLabel.text = skillBuildData[#"name"];
to the top of the async block, above the NSLog statements.
I must be missing something basic here. When I use [NSString stringWithFormat] to set a property of an object that displays in a UITableViewCell, it displays as empty. If I just set the property normally, i.e. property = #"Item One", it displays fine. The code, and result below.
Using this:
Results in:
Yet the log shows:
Which would indicate the property is set. Will someone please take me to school here.
EDIT
Underretaining was the issue.
My prop looked as such: #property (weak, nonatomic) NSString *name
Changing that to: #property (strong, nonatomic) NSString *name
Fixed the issue. I understand that this means I retain a strong reference to this property, but in this particular, is there a "I just graduated college and taught himself Obj-C" explanation as to what was happening in memory to cause this issue?
My first thought, since a constant string works, is that you're under-retaining the name property, although if that's true I'm surprised you aren't crashing.
Edit:
After seeing your code, here's the problem. A weak property only holds a value as long as the value is kept alive elsewhere. A constant string lives forever, so assigning a constant string here worked. But if you assign a calculated value, then nothing owns it, and therefore nothing keeps it alive, and therefore your weak property will end up with nil.
I'm assuming you're assigning name to the text property of a UILabel.
Either the label is:
nil
Hidden (check it's hidden property)
not in the view hierarchy (check it's superview property)
Not big enough to show the text (calling sizeToFit will fix this)
The same color as your background (not likely)
The hardest problem to solve will be if it is nil. You haven't told us where the cell is coming from but here are a few possibilities.
The IBOutlet isn't set in the xib/storyboard
You aren't instantiating it in the correct init method. If you are instantiating it, make sure that specific init variation is being called.
I sounds like a stupid question, but it seems I cannot release an adMob GADBannerView.
Admob documentation says don't call "release" when using ARC. Needless to say you cannot call release because it's not allowed and generates an error.
I tried this:
#property (nonatomic, strong) GADBannerView *adMobView;
…
[adMobView removeFromSuperview];
adMobView.delegate = nil;
adMobView = nil;
But nothing happens. It becomes nil but still stays on the screen. It supposed to be a subclassed UIView. At the best I can hide it but it still received ads and obviously stays in the memory.
Any Ideas?
Try weak reference
#property (nonatomic, weak) GADBannerView *adMobView;
Weak
weak is similar to strong except that it won't increase the reference count by 1. It does not become an owner of that object but just holds a reference to it. If the object's reference count drops to 0, even though you may still be pointing to it here, it will be deallocated from memory.
Refer more here
Have an interesting issue where there is a class that is referenced in an XIB layout (subclass of UIScrollView) and is not being de-allocated according to Instruments / Allocations and does not break in it's dealloc routine. Let's call it Sclass1.
There is a using class (let's call it Uclass) that has the XIB file and the outlet.
#property (nonatomic, weak) IBOutlet Sclass1* sclass1;
This is hooked properly to the XIB file layout.
Sclass1 is property allocated when the XIB for Uclass is loaded. Uclass does get deallocated and then recreated from time to time and thus we have another instance of Sclass1, but Sclass1 never goes away and can't find another reference to it.
Drill down in Instruments shows the one Malloc and that is it.
fyi, the class gets started with
[UIClassSwapper initWithCoder:]
If an object doesn't get deallocated under ARC, it means a strong reference to it exists. Since your property is weak the object must be owned strongly by something other than the Uclass object (Otherwise it would get deallocated immediately after the XIB has loaded). In the code you've provided it isn't clear what the actual strong owner of this object is, but I assume it could be one (or more) of the following:
Since the object's class is a UIView subclass, it may be (strongly) referenced by its superview if added as one of subviews. This happens automatically when a XIB file is loaded. If the superview doesn't get deallocated neither will the SClass object. You can remove this ownership by calling removeFromSuperview
A strong ownership cycle (retain-cycle) exists somewhere among ivars of the SClass1 object (i.e. one of the strongly-owned instance variables have a strong reference back to its owner - the SClass1). Beware that any block using self directly also keeps a strong reference. Having a strong reference to the block then often leads to a retain-cycle. Save self to a __weak var and pass that to the block instead unless you have a good reason not to.
A manually created strong reference exists by e.g. adding the object to a container or saving the pointer to a non-__weak variable.
Try finding and removing these strong ownerships. Only after all of them are removed the object can be deallocated.
Since your property is weak and it's still not deallocated, look for strong references to Sclass or it's owner, Uclass. Maybe you are using Uclass(or Sclass) in block directly, without __weak typeof(self) weakSelf dancing and this block creates retain cycle. Also watch for parent-child relations and delegates. Maybe there is delegate which is strong instead of weak or two controllers hold strong references to eachother.
Also, if you want to have more detailed answers, please post more relevant code.
I think your #property should be strong for a class :
#property (nonatomic, strong) IBOutlet Sclass1* sclass1;
Because strong is the equivalent to retain and ARC will manage the release for you.
You will have more information with the Apple Documentation about Transitioning to ARC Release Notes in the section on property attributes.
I recently had the same symptoms - To solve it in my case, my object was acting as delegate for a number of other objects, so had to release the object from all its delegate responsibilities before it would call dealloc