How to release the private property in iOS 7 using ARC - ios

I am developing a project on iOS 7 using ARC, I want to release a private property when the viewController is released
Here is the TestViewController that is presented as a modal view controller, setting a value to the private property testAVPlayer in viewDidLoad:
//TestViewController.m
#import "TestAVPlayer.h"
#interface TestViewController () {
TestAVPlayer *testAVPlayer;
}
#end
- (void)viewDidLoad
{
[self setupPlayer];
}
- (void)setupPlayer {
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:[[NSBundle mainBundle] URLForResource:#"music" withExtension:#"mp3"]];
testAVPlayer = [TestAVPlayer playerWithPlayerItem:item];
[testAVPlayer setActionAtItemEnd:AVPlayerActionAtItemEndNone];
[testAVPlayer play];
}
- (void)dealloc {
NSLog(#"dealloc TestViewController: %#", self);
}
TestAVPlayer is a subclass of AVPlayer, I put a NSLog into the dealloc
// TestAVPlayer.h
#import <AVFoundation/AVFoundation.h>
#interface TestAVPlayer : AVPlayer
#end
// TestAVPlayer.m
#import "TestAVPlayer.h"
#implementation TestAVPlayer
- (void)dealloc {
NSLog(#"dealloc testAVPlayer: %#", self);
}
#end
When TestViewController is dismissed, the testAVPlayer seems never be released, I see the "dealloc TestViewController", but there is no "dealloc testAVPlayer" in console log

I tried your code, the problem is that even if you call [TestAVPlayer playerWithPlayerItem:item] the TestAVPlayer class doesn't have such method, so it will call playerWithPlayerItem: function from the AVPlayer base class, which will return an instance of the AVPlayer class instead of the TestAVPlayer class. The compiler won't give you any warning because the playerWithPlayerItem: method returns a type of id. If you check this with the debugger you'll see that the private variable's type is not TestAVPlayer:
The dealloc of the TestAVPlayer will never be called as no such object was created.
The AVPlayer instance gets deallocated when the TestViewController is deallocated. You can check this by using Instruments or simply adding a Symbolic Breakpoint to [AVPlayer dealloc].
Select the Breakpoint Navigator and click on the + button and add a Symbolic Breakpoint.
Write [AVPLayer dealloc] to the Symbol field and press Enter. When you run the application and the TestViewController gets deallocated then you will see that the breakpoint will be hit, hence the AVPlayer really gets deallocated.

You are using a class factory method to initialize your object, which means that you do not own the testAVPlayer object and thus are not responsible for releasing it.
See Class Factory Methods from the Concepts in Objective-C Programming guide for more details.
If you indeed want to own and control the lifetime of this object, use the following initializer:
testAVPlayer = [[TestAVPlayer alloc] initWithPlayerItem:item];
and your dealloc method will be called.

testAVPlayer = [AVPlayer playerWithPlayerItem:playerItem];
You are using AVPlayer, not your TestAVPlayer.

Try implementing - viewDidUnload then nil the testAVPlayer:
- (void) viewDidUnload
{
[super viewDidUnload];
testAVPlayer = nil;
}

Although you are calling it as [TestAVPlayer playerWithPlayerItem:item], you are really getting back an instance of AVPlayer, not TestAVPlayer. In fact, the AVPlayer instance you created really IS getting deallocated. You won't be able to see your log in dealloc because an instance of that class is never created.
As suggested by another, replace [TestAVPlayer playerWithPlayerItem:item] with [[TestAVPlayer alloc] initWithPlayerItem:item]; and you should start seeing your logs.

Related

Why delegate method is not called - iOS

I have an AudioRecorder class for recording audio, and I have the following code:
_recorder = [[AVAudioRecorder alloc] initWithURL:audioFileURL settings:recordSettings error:&error];
if (_recorder && [_recorder prepareToRecord]) {
[_recorder recordForDuration:10.0]; // Record for 10 seconds
[_recorder setDelegate:self];
[_recorder setMeteringEnabled:YES];
}
So, here self is the AudioRecorder class which is declare as:
#interface AudioRecorder : NSObject <AVAudioRecorderDelegate>
#property AVAudioRecorder *recorder;
#property AVAudioSession *session;
#end
Then I have this callback method that should (but is not) called after the 10 seconds registration:
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag {}
Why is this not called after the 10 sec registration?
There are two common reason which make delegate methods not being called.
The first one is not setting the delegate property or implementing a wrong method (ie, typo on the method).
Which is not the case here.
The second one, is that the object is released (from memory) too soon.
In other words, the delegate method will be called later (here ~10 seconds after), and delegate properties are almost always weakly reference to avoid memory issue (cycle), when the object need to call its delegate method, the object doesn't exist anymore. That's your issue.
The small difference here, it's that's the parent of the delegate that is released too soon.
Because _recorder is a property of AudioRecorder, so as long as the AudioRecorder instance exists, it will still be here and let the delegate method be called.
But, the AudioRecorder is a local variable, so it will cease to exists after its scope (~after the next closing }).
So you need to make AudioRecorder a property of the object holding it.

iOS - singleton is not working as supposed in delegate

Currently I'm working on an app that uses four protocols for communication between classes. Three are working fine, but one is still not working. I've set it up same as the others but the delegate is always losing its ID. I'm quite new to Objective-C so I can't get to the bottom of it. Here is what I did:
I have a MainViewController.h with the delegate
#property (weak, nonatomic) id <PlayerProtocol> player;
and a MainViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[[Interface sharedInstance] Init];
NSLog(#"Player ID: %#", _player);
NSLog(#"viewDidLoad: %#", self);
}
- (void)sedPlayer:(id) pointer{ //sed is no typo!
_player = pointer;
NSLog(#"sedPlayer ID: %#", _player);
NSLog(#"sedPlayer: %#", self);
}
+ (instancetype)sharedInstance {
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
In the Interface.m (NSObject)
- (void)Init {
[[MainViewController sharedInstance] sedPlayer:self];
}
And of course a protocol.h but this is not of interest as the delegate does the trouble! When I run the code I get the following output on the console:
sedPlayer ID: <Interface: 0x1700ab2e0>
sedPlayer: <MainViewController: 0x100406e30>
Player ID: (null)
viewDidLoad: <MainViewController: 0x100409550>
So it is obvious that the singleton is not working as the instance of the MainViewcontroller is different. For the singleton I'm using the dispatch_once standard method as I do with the other protocols that work fine. ARC is turned on. Does anyone has a clue what is wrong here and why the singleton is not working?
Here's how I think you ended up with two instances of the MainViewController. The first one, I assume, is created when navigating to the screen associated with MainViewController. The second one is created when you call [MainViewController sharedInstance] in Interface.m.
As the ViewController view is lazy loaded ("View controllers load their views lazily. Accessing the view property for the first time loads or creates the view controller’s views." from the Apple docs under ViewManagement), you see the viewDidLoad: <MainViewController: 0x100409550> log only once, when the first MainViewController gets navigated to and loads up the view.
Here's my suggestion:
Since you do the Interface initializing in the - (void)viewDidLoad, you might as well set self.player = [Interface sharedInstance].
The code would look something like this:
- (void)viewDidLoad {
[super viewDidLoad];
self.player = [Interface sharedInstance];
NSLog(#"Player ID: %#", _player);
NSLog(#"viewDidLoad: %#", self);
}
You should also get rid of - (void)sedPlayer:(id) pointer and + (instancetype)sharedInstance in your MainViewController. It is never a good idea to have a ViewController singleton, since you might end up messing up the navigation or having multiple states of it.
For a more in-depth article on avoiding singletons, you can check objc.io Avoiding Singleton Abuse

iOS singleton viewDidLoad empty and on viewDidAppear not

I created a singleton in ios7 like this:
SharedData.h
#interface SharedData : NSObject
{
}
+ (id)sharedInstance;
#property (strong, nonatomic) NSMutableArray *list;
#end
SharedData.m
#import "SharedData.h"
#implementation SharedData
#synthesize list;
// Get the shared instance thread safe
+ (SharedData *)sharedInstance {
static dispatch_once_t once = 0;
static SharedData *sharedInstance = nil;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (id)init {
self = [super init];
if (self) {
//initialize
list = [[NSMutableArray alloc] init];
}
return self;
}
#end
I always use this code to access this class:
SharedData *sharedData = [SharedData sharedInstance];
The problem is now when I switch the view in my viewDidLoad method the list is empty but in my viewDidAppear method everything is fine. Any ideas?
EDIT:
This is the code how I change the views:
SharedData *sharedData = [SharedData sharedInstance];
//clear feed and add new feed
[sharedData.list removeAllObjects];
[sharedData.list addObjectsFromArray:newList];
//show new gui
[self.navigationController performSegueWithIdentifier:#"goToMain" sender:self];
NOTE: I push from a normal ViewController to a TabBarController -> NavigationController -> TableViewController to display the list.
I guess you have the confusion between these two viewcontroller methods:
-(void)viewDidLoad{
//
}
&
-(void) viewDidAppear{
//
}
viewDidAppear is the method which is called each time your view changes but viewDidLoad is the method which is not necessarily called each time your view changes.
ViewDidLoad method is called when view loads for the first time, after that it doesn't get called until the views are removed/released.
P.S: I suggest you to put the breakpoint in your viewDidLoad and viewDidAppear method and feel it. Your answer lies there.
Hope this helps you alot.
Good Luck.
The problem was i created a segue which went from the button to the next view. Because of this the viewDidLoad gets earlier called than the list assigned. I just changed the segue to go from view to view.
How are you changing from one viewController to the other? Wich classes are the parents of your destination ViewController?,
If you are modifying properties of the view in the prepareForSegue method... you are forcing the view to load.
For example, you are setting the list of your singleton in prepareForSegue, but before setting the list you are modifying a property of your destination viewController. (doing something like destVC.view = XXX or destVC.viewControllers = XX if you are subclassing a UITabBarViewController...) Then you are triggering the viewDidLoad method , and it's executing before you have set the list to the correct value.
Or maybe you are seguing in two different places to the destinationViewController. And when the viewDidLoad happens, you still have not updated the list on the singleton.
Here is the transcription of the chat with the poster of the question: https://chat.stackoverflow.com/transcript/55218

Object deallocates immediately after it has been initialized

I am trying to make a class that plays YouTube videos, but I am having several problems with it.
Here is my class that handles YouTube videos:
// this is the only property declared in the .h file:
#property (strong, nonatomic) UIView * view
// the rest of this is the .m file:
#import "MyYouTube.h"
#interface MyYouTube()
#property (strong, nonatomic) NSDictionary * contentData;
#property (strong, nonatomic) UIWebView * webView;
#property (nonatomic) int videoOffset;
#end
#implementation MyYouTube
#synthesize view,contentData,webView,videoOffset;
- (MyYouTube*) initIntoView: (UIView*) passedView withContent: (NSDictionary*) contentDict {
NSLog(#"YOUTUBE: begin init");
self=[super init];
videoOffset=0;
view=[[UIView alloc] initWithFrame:passedView.bounds];
[view setBackgroundColor:[UIColor blackColor]];
[view setAutoresizesSubviews:YES];
[view setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth];
contentData=contentDict;
NSString * compiledUrl=[[NSString alloc] initWithFormat:#"http://_xxx_.com/app/youtube.php?yt=%#",[contentData objectForKey:#"cnloc"]];
NSURL * url=[[NSURL alloc] initWithString:compiledUrl];
NSURLRequest * request=[[NSURLRequest alloc] initWithURL:url];
webView=[[UIWebView alloc] initWithFrame:passedView.bounds];
[webView loadRequest:request];
[webView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
[[webView scrollView] setScrollEnabled:NO];
[[webView scrollView] setBounces:NO];
[webView setMediaPlaybackRequiresUserAction:NO];
[webView setDelegate:self];
NSLog(#"YOUTUBE: self: %#",self);
NSLog(#"YOUTUBE: delegate: %#",webView.delegate);
[view addSubview:webView];
NSLog(#"YOUTUBE: end init");
return self;
}
-(void)webViewDidFinishLoad:(UIWebView*)myWebView {
NSLog(#"YOUTUBE: send play command");
[webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:#"playVideo(%d)", videoOffset]];
}
- (void) dealloc {
NSLog(#"YOUTUBE: dealloc");
}
#end
Here is the code that calls this class (this code is located in the appDelegate):
NSLog(#"about to load youtube");
ytObj=[[MyYouTube alloc] initIntoView:mainView withContent:cn];
NSLog(#"loaded youtube");
[mainView addSubview:[ytObj view]];
FYI mainView and ytObj are declared as this:
#property (nonatomic,strong) UIView * mainView;
#property (nonatomic,strong) MyYouTube * ytObj;
When this code is run, the app crashes and I get this in the console:
about to load youtube
YOUTUBE: begin init
YOUTUBE: self: <MyYouTube: 0x16503d40>
YOUTUBE: delegate: <MyYouTube: 0x16503d40>
YOUTUBE: end init
YOUTUBE: dealloc
loaded youtube
*** -[MyYouTube respondsToSelector:]: message sent to deallocated instance 0x166f19f0
If I set the UIWebView delegate to nil then the app doesn't crash, but, as expected, the YouTube video doesn't autoplay.
Can anyone explain to me:
Why does the object deallocates immediately after it has been
initialized?
Why the respondsToSelector: message is sent to an
instance other than the MyYouTube one?
How can I get the YouTube video to autoplay without the app crashing?
Many thanks.
EDIT
Totally my bad - ytObj is a strong property - I forgot to mention this. Ive added the code to reflect this.
EDIT 2
Ive added a breakpoint on the dealloc method, and this is the call stack:
0 -[MyYouTube dealloc]
1 objc-object::sidetable_release(bool)
2 -[MyAppDelegate playYouTube:]
The last entry here (playYouTube:) is the method that contains the code in my app delegate that is in the original post above. So, one more question:
What is objc-object::sidetable_release(bool), what does it do, and why is it releasing my YouTube object?
Why does the object deallocates immediately after it has been initialised?
Because nothing owns it.
You set it as the delegate to a view, but the delegate property of UIWebView is an assign property. The view doesn't take ownership of its delegate. This means that when ytObj goes out of scope (possibly before, depending on optimisation) nothing owns it, so it goes away.
EDIT
You also need to make sure that when a ytObj is deallocated, you set the delegate property of any views it is still a delegate of to nil. Otherwise the views will continue to try to send messages to the deallocated object.
How can I get the YouTube video to autoplay without the app crashing?
You need to make sure that ytObj lasts as long as the view of which it is the delegate and when it is deallocated, it's view's delegate is set to nil.
Another minor issue. Your -init function should test that self is not nil after invoking self = [super init]. It shouldn't run any of the rest of the initialisation code if self is nil.
1) (my first answer to your first question, before your edit)
This is happening because you're using ARC (automated reference counting) and you are not keeping your local "ytObj" variable ((which is NOT the "self.ytObj" property) around in the object that you created it in. As soon as the function that created the local "ytObj" variable finishes up and returns, the object is automagically dealloc'd.
Change this:
ytObj=[[MyYouTube alloc] initIntoView:mainView withContent:cn];
to this:
self.ytObj=[[MyYouTube alloc] initIntoView:mainView withContent:cn];
And for "best practices", I'd also suggest not doing so much work and/or code in your application delegate. App delegates are meant to receive application specific messages (like "application is suspending", "application is becoming active again", etc.), and you should do stuff like this in a subclassed view controller.
2)
The "respondsToSelector:" error you're seeing is because your YouTube object has been automagically dealloc'd. If it were still living, you wouldn't see that message.
3)
If you search here on Stackoverflow, you'll find other questions and answers that explain how to do autoplaying... like how about this one?
The object ytObj since is not strong referenced anywhere, it exists only inside the scope where is defined. Delegates most of time are declared as weak properties. None is keeping a strong reference to this object, thus ARC releases it.
Create a strong properties to ytObj and you will see everything working fine.

Retain the delegate of UIImagePickerController

I've wrote a class which gets an image from the camera. Its header is as follows:
typedef void(^ImageTakenCallback)(UIImage *image);
#interface ImageGetter : NSObject <UIImagePickerControllerDelegate, UIPopoverControllerDelegate>
{
UIImagePickerController *picker;
ImageTakenCallback completionBlock
}
-(void) requestImageInView:(UIView*)view withCompletionBlock:(void(^)(UIImage*))completion;
#end
As you can see, I'm trying to make something like that in client code:
[[[ImageGetter alloc] init] requestImageInView:_viewController.view withCompletionBlock:^(UIImage *image) {
// do stuff with taken image
}];
Here is how I've implemented ImageGetter:
-(void) requestImageInView:(UIView*)view withCompletionBlock:(ImageTakenCallback)completion
{
completionBlock = [completion copy];
picker = [[UIImagePickerController alloc] init];
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
picker.delegate = self;
[view addSubview:picker.view];
}
- (void)imagePickerController:(UIImagePickerController *)picker_
didFinishPickingImage:(UIImage *)image
editingInfo:(NSDictionary *)editingInfo
{
[picker.view removeFromSuperview];
picker = nil;
completionBlock(image);
}
The problem is since I'm using ARC, the instance of ImageGetter is deallocated instantly after call for -requestImage..., so the weak delegate of picker becomes nil.
Which are common ways to resolve such a issue?
I can see some ways, however, none of them seems to be quite right:
retain ImageGetter from client code, for example, assign it to a strong property. The problems here are: I wont be able to release it by setting this property to nil right after I get image, because this will mean setting retain count of object to 0 while executing the method of this object. Also, I don't want unnecessary properties (well, it is not a big problem, but nevertheless).
disable ARC for ImageGetter and manually retain at start itself and release after sending image to callback.
make static manager ImageGetterManager, which will have method requestImage..., it will create ImageGetter instances, retain them, redirect the requestImage... call, get callbacks from them and release. That seems the most consistent way, but is not it a bit complex for such a little code?
So how can I build such a class?
You can handle that within the ImageGetter class by creating and releasing a "self-reference".
In a class extension in the implementation file, declare a property
#interface ImageGetter ()
#property (strong, nonatomic) id selfRef;
#end
In requestImageInView:, set self.selfRef = self to prevent deallocation.
In the completion method, set self.selfRef = nil.
Remark: Actually you can manage the retain count even with ARC:
CFRetain((__bridge CFTypeRef)(self)); // Increases the retain count to prevent deallocation.
CFRelease((__bridge CFTypeRef)(self)); // Decreases the retain count.
But I am not sure if this is considered "good programming" with ARC or not.
Any feedback is welcome!
If this issue is introduced when switching to ARC, I should just go for option 1, and define it as a strong property.
However the behaviour is a bit different than you described for option 1: Setting the property to nil, does NOT mean the object is instantly released, it will just cause a decrement of the retaincount. ARC will handle that fine, the object will be released as soon as all referenced objects have 'released' it.
You can use the following strategy:
ImageGetter* imgGetter = [[ImageGetter alloc] init];
[imgGetter requestImageInView:_viewController.view withCompletionBlock:^(UIImage *image) {
// do stuff with taken image
[imgGetter releaseCompletionBlock]; // With this line, the completion block will retain automatically imgGetter, which will be released after the release of the completionBlock.
}];
Inside your ImageGetter implementation class, create a method that you can call inside the block like this.
-(void) releaseCompletionBlock
{
completionBlock = nil;
}

Resources