How do I keep an object in scope, which performs a task in background, that was instantiated from a method of another object?
See the following example. The display method of object A instantiates object B and calls its display method. When the B object's display method, starts an asynchronous task, control goes back to A::display method, which returns, and bObject gets deallocated. When bObject's data/method are accessed in the background thread, this will throw exception.
What is the best way of maintaining the scope for object B? I can declare the object B as a member variable for A, but this will increase the scope for the entire lifespan of object A.
#implementation A
-(void) display
{
B* bObject = [[B alloc] init];
[bObject display];
}
#end
#implementation B
-(void) display
{
dispatch_async((dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0),{
self.data = 5;
---------
});
#end
Previously in iOS you would be able to do this and then assuming your object created returned with some delegate callback you could release it. Now you will have to make "bObject" a member variable for your class and either set it to nil when you are done with it or wait for A to be destroyed which will put bObject out of scope. I'm assuming this is with ARC and as soon as "display" finishes your local variable is out of scope and is deallocated because of ARC.
you can add this to A:
#property (nonatomic, strong)B *bObject;
You can create an iVar B *bObject in A's interface. Just make sure you are not releasing bObject anywhere. Also, you may want to not reference self from the block in bObject.
#interface A : NSObject
#property (strong, nonatomic) B *bObject;
#end
#implementation A
- (void)display {
self.bObject = [[B alloc] init];
[self.bObject display];
}
#end
#implementation A
- (void)display {
B *safeSelf = __block self;
dispatch_async((dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0),{
safeSelf.data = 5;
----
});
}
#end
Related
Say I have the following singleton:
#interface ABCSingleton: NSObject
#property (nonatomic, copy) NSString *name;
#property (nonatomic, strong) ABCViewController *mainViewController;
#end
#implementation ABCSingleton
+ (ABCSingleton *)sharedInstance {
static ABCSingleton *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [ABCSingleton new];
});
return instance;
}
- (void)doSomething {
}
#end
If doSomething contained this code:
- (void)doSomething {
self.mainViewController.tapBlock = ^() {
self.name = #"abc";
};
}
... it would create a retain cycle since ABCSingleton owns mainViewController, which owns tapBlock, which owns ABCSingleton.
What if instead of using self, I used sharedInstance?
- (void)doSomething {
self.mainViewController.tapBlock = ^() {
[ABCSingleton sharedInstance].name = #"abc";
};
}
Would it still create a retain cycle? (An explanation as to why, or why not, would be appreciated!)
To the specific question, this is a retain loop in the first case, and equivalent to a retain loop in the second case (equivalent in that mainViewController will never be released).
This indicates a deeper design problem. A singleton should never reference a view controller. Singletons are by nature model objects. Model objects should never reference controller objects directly. See Model-View-Controller for an introduction. This is a key design pattern in Cocoa.
Instead, the view controller should know about the model (the singleton in this case). doSomething should modify the model, and the view controller, when it comes on the screen, should read the model to set tapBlock. The view controller can also observe the model (via KVO, notifications, or delegation) while it is onscreen to know when to change its values.
I have a problem understanding the Objective-C and the ARC.
As I understood the strong pointers will be dealloced automatically for you, so you don't have to think about it (dealloced in dealloc method, or after the last time the object was used ?).
So I wrote a little app, with 2 viewControllers and a NavigationController, which enters one view and then goes back.
The dealloc method was called, but the property, which I set at viewDidLoad method, wasn't deallocated, it is still pointing to some object.
Code:
The first viewController has a button, which by pressing it, performs a segue to another viewController. No code there.
SecondViewController.m
#interface SecondViewController ()
#property (nonatomic, strong) NSString *myString;
#end
#implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"%#", _myString);
_myString = #"it works";
}
- (void)dealloc {
NSLog(#"%#", _myString);
// and now it is deallocating the _myString property ???
}
#end
Then, I tried to do another thing.
The idea was to create a weak pointer, which points to the same memory address, as the strong pointer. I though, that the weak pointer should be nil in any case.
Since the dealloc method is called, all weak pointers should be niled
Since the strong pointer was used only in viewDidLoad, it should be deallocated way before the dealloc method.
The problem is, it is not deallocated.
Why ?
Code for secondViewController:
#interface SecondViewController ()
#property (nonatomic, strong) NSString *myString;
#property (nonatomic, weak) NSString *test;
#end
#implementation SecondViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%#", _myString);
_myString = #"it works";
_test = _myString;
}
- (void)dealloc
{
NSLog(#"%#", _test);
}
#end
Deallocation of the properties happens at the end of the dealloc method. If you overwrite the dealloc method, the properties won't yet be deallocated inside that method.
You could test this by creating a weak property in your first view controller, assign the strong property of the second view controller, then log the value of it when the application returns to the first view controller.
The simplest way to illustrate weak references is with the following example...
Given the following two classes:
#interface ObjectWithStrongRef : NSObject
#property (strong) NSString *ref;
#end
#interface ObjectWithWeakRef : NSObject
#property (weak) NSString *ref;
#end
We will create an instance of ObjectWithWeakRef with a scope larger than that of ObjectWithStrongRef, assign the latter's ref property a value, then have the former's ref point to this same object, then we will check ref in both scopes.
int main(int argc, const char * argv[]) {
ObjectWithWeakRef *weak = [[ObjectWithWeakRef alloc] init];
#autoreleasepool {
ObjectWithStrongRef *strong = [[ObjectWithStrongRef alloc] init];
strong.ref = [NSString stringWithFormat:#"Hello %#", #"World"];
weak.ref = strong.ref;
NSLog(#"Weak.Ref = %#", weak.ref);
}
NSLog(#"Weak.Ref = %#", weak.ref);
}
Note, that we can't simply assign ref to a literal string. Objective-C tends to keep these around in memory so it can do some memory optimizations, but when we use stringWithFormat:, it'll create an autoreleasing string.
At the first NSLog statement, strong.ref maintains a strong reference to the string object, so when we log weak.ref, the object is not yet deallocated, so it correctly logs "Hello World".
Between the first and second NSLog call, we've exited the #autoreleasepool, within which the strong object was scoped (if we put an NSLog message in ObjectWithStrongRef's dealloc, we'd see it called here). Because strong has deallocated as we exit the #autoreleasepool, there are no longer any strong references to the string object we have references to--we only have weak's weak reference to the memory, so the string object also deallocates (just after strong has deallocated).
So in the second NSLog call, we'll see Weak.Ref = (null) printed.
Below is my code to verify the question.
0. Mock a retain-cycle
I create two classes, A & B:
#interface A : NSObject
#property (strong, nonatomic) B *b;
#end
#implementation A
- (void)dealloc
{
[_b release];
[super dealloc];
}
#end
//
#interface B : NSObject
#property (nonatomic, strong) A *a;
#end
#implementation B
- (void)dealloc
{
[_a release];
[super dealloc];
}
#end
// And in StrongViewController, there are tempA and tempB, which are both assign proprety:
#interface StrongViewController ()
#property (nonatomic, assign) A *tempA;
#property (nonatomic, assign) B *tempB;
#end
// In viewDidLoad, I create the retain-cycle, a & b retain each other:
A *a = [[[A alloc] init] autorelease];
B *b = [[[B alloc] init] autorelease];
a.b = b;
b.a = a;
self.tempA = a;
self.tempB = b;
// Then in viewWillDisappear, I try to break the retain-cycle:
[super viewWillDisappear:animated];
self.tempA.b = nil; // Key line
// Now, dive into the cycle step by step.
1. Using default setter to break retain-cycle
I set two breakpoints in A's dealloc & B's dealloc:
// Now when the key line self.tempA.b = nil; was executed:
// B's dealloc was called because release message was sent to b in A's setB:
// In B's dealloc, release message was sent to a, so A's dealloc was called then:
// Now, _b is nil because we use the default setB:, so it's okay to go through.
2. Using custom setter to break retain-cycle
// But what if using a custom setB: like below:
// When the key line self.tempA.b = nil; was executed, it runs into the custom setB::
// Now release was sent to b, so it goes to B's dealloc:
// Notice that in B's dealloc, [self retainCount] is still 1, not 0, why?
// Here in B's dealloc, release was sent to _a, so it will go to A's dealloc in turn, and in there release will be sent to _b:
// Here comes the difference, in A's dealloc, _b is not nil, and _b's retainCount is 1.
// According to my knowledge, if _b receives a release message now, it will call its dealloc then, because its retainCount is 1 for the moment.
// But, out of my imagination, it doesn't step into B's dealloc recursively, but just step over to the next line, leaving the retainCount with a big value 2147483648:
3. My question
// My question is on the above, why it step over when _b receives a release message, instead of stepping into its dealloc, while its retainCount is still 1 ?
// And how the retainCount 2147483648 comes out?
// To the depth, how the NSObject works when it receives a 'release' message indeed, anyone knows the source code? Even in A's dealloc, a's retainCount` is still 1, so is b.
When you release an object with a retain count of 1, the following happens:
The object is marked as "dead".
The dealloc method is called.
The memory of the object is returned to the OS.
retain and release have no effect when the object is marked as dead. The runtime doesn't even bother setting the retain count to zero before calling dealloc. When your second dealloc method gets called and tries to release an object that is already dead, even though retainCount returns 1, nothing happens. dealloc is never called twice for the same object.
http://www.opensource.apple.com/source/objc4/objc4-532/runtime/NSObject.mm is worth checking out.
I think the problem has to do with hasCustomRR() but I find the code tricky to follow so could be wrong.
It is not to keep cyclic references between objects !!!
But it is required some time to use such references to get our task done.
So I will suggest if you have one of the object is dependent on other then you can make on of the reference WEAK.It will assure breaking cyclic looping of references. you can refer http://www.cocoawithlove.com/2009/07/rules-to-avoid-retain-cycles.html tutorial for more explanation.
I'm using a singleton called CSAppData to store data for my iPhone app. I'm storing an object called CSInbox in the singleton. When I logout of my app, I want to clear the data for that object.
Here is my singleton code, including the method for clearing the data:
- (id)init {
self = [super init];
if (self)
{
self.inbox = [[CSInbox alloc] init];
}
return self;
}
+ (CSAppData *)appData {
static CSAppData * appDataInstance;
#synchronized(self) {
if(!appDataInstance) {
appDataInstance = [[CSAppData alloc] init];
}
}
return appDataInstance;
}
+(void) clearData {
CSAppData *appData = [CSAppData appData];
appData.inbox = [[CSInbox alloc] init];
}
However, in one of my view controllers, in the initWithCoder method, I'm storing the inbox variable:
-(id) initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if(self) {
self.inbox = [[CSAppData appData] inbox];
}
return self;
}
So, when the app logs out and the clearData method is called, the view controller is still pointing to the old CSInbox object. And even though I am initializing a new view controller and setting it to the root view controller (in the AppDelegate), like this:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
MainTabControllerViewController *viewController = (MainTabControllerViewController *)[storyboard instantiateViewControllerWithIdentifier:#"mainView"];
[self.window setRootViewController:viewController];
The one child view controller that has the CSInbox is never reinitialized, and is still pointing to that old CSInbox object. (I'm not sure why this is happening.)
So, what is the best way to solve this?
Change the clearData method in the singleton to just reset the properties of the CSInbox object, rather than alloc and init and new one?
Move the self.inbox = [[CSAppData alloc] init]; to the viewDidLoad in the view controller class so it gets set properly upon the second login?
Change the logout function in the AppDelegate so that the root view controller and all other view controllers are released, so they will reinitialize upon the second login?
I'm leaning toward #1 or #3...
As requested, here is CSInbox.h:
#interface CSInbox : NSObject
#property (nonatomic,strong) NSMutableArray *threads;
#property (nonatomic, assign) NSInteger newCount;
#property (nonatomic,strong) NSDate *lastUpdate;
-(void) setThreadsFromJSON:(NSDictionary *)json;
#end
And here is CSInboxViewController.h:
#interface CSInboxViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate, CSThreadViewControllerDelegate>
#property (strong, nonatomic) IBOutlet UITableView *inboxTableView;
#property (strong,nonatomic) CSInbox *inbox;
#end
And CSAppData.h:
#interface CSAppData : NSObject {
CSInbox *inbox;
}
#property(nonatomic,strong) CSInbox *inbox;
+ (CSAppData *)appData;
+ (void)clearData;
#end
I think the answer lies not in destroying the singleton object and recreating it, but to actually clear the instance variables within that singleton object.
You don't show the declaration of [CSAppData inbox], but if it's an NSMutableArray, for example, then you can clear that, and any existing references to the singleton object can remain:
+(void) clearData {
CSAppData *appData = [CSAppData appData];
[appData.inbox removeAllObjects];
}
One way to handle this, complying with the spirit of using a singleton, is having your view controllers access directly your singleton inbox, i.e.: [CSAppData appData].inbox instead of self.inbox. This is a bit wordier, but it would "magically" fix your issue.
If that is not acceptable to you, I would go with option #1 of those you list. Even better, I would make the inbox in the singleton a singleton itself, or make sure it is never replaced by another instance.
EDIT:
Another approach you have, is using KVO in your controller so that it gets notified when the inbox object has changed. Don't know if it is quite worth it, but could be used.
I am developing an ARC enabled project. From a view controller I am pushing MyClass,
- (void)pushMyClass {
MyClass *myClass = [[MyClass alloc] init];
[self.navigationController pushViewController:myClass animated:YES];
}
After doing some operations I am popping MyClass. The problem here is that MyClass is not getting deallocated. Following is how the classes look.
/* MyHelperClassDelegate */
#protocol MyHelperClassDelegate <NSObject>
- (void)helperDidFinishHelping:(MyHelperClass *)helper;
#end
/* MyHelperClass Interface */
#interface MyHelperClass : NSObject {
__weak id <MyHelperDelegate> delegate;
}
#property(nonatomic, weak) id<MyHelperDelegate> delegate;
- (void)startHelping;
#end
/* MyHelperClass Implementation */
#implementation MyHelperClass
#synthesize delegate;
- (void)dealloc {
delegate = nil;
}
/* MyClass */
#interface MyClass : UIViewController <MyHelperClassDelegate> {
MyHelperClass *helper;
}
#implementation MyClass {
- (void)dealloc {
helper.delegate = nil;
}
- (void)getHelp {
helper = [MyHelperClass new];
helper.delegate = self;
[helper startHelping];
}
- (void)helperDidFinishHelping:(MyHelperClass *)helper {
}
}
MyHelperClass calls a web service using NSMutalbleURLRequest & NSURLConnection to fetch some data and saves it to user defaults.
One thing to notice here is, if I comment the line helper.delegate = self;, then MyClass gets deallocated.
What to do to make MyClass get deallocated when it is popped out of navigation controller?
Thanks.
Your delegate code looks correct (except your use of an ivar, you don't show a #synthesize so you may have _delegate and delegate both). Its quite likely that something else is retaining MyClass. What I suggest you do is add a NSLog to your MyClass dealloc. Then push it, and immediately hit the back button and see if its dealloc'd or not. If not, then take a hard look at what you do in viewDidLoad et al and start commenting out sections of that code until you can get the dealloc.
Also, I assume you don't keep a strong reference in the class that pushes the MyClass object.
I agree with Chuck that one cannot say much from the code provided. But one reason why the MyClass object is not deallocated might be that it is retained by your helper object since delegate is declared as strong, and the MyClass object has the property helper also declared as strong. In this case you had a retain cycle, and none of them can be released.
The trick could possibly lie within the fact that you use NSURLConnection. It is not specified how you use this class with the code that you've provided, but please note the special considerations referenced in the NSURLConnection class reference:
Special Considerations: During the download the connection maintains a
strong reference to the delegate. It releases that strong reference
when the connection finishes loading, fails, or is canceled.