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
Related
Good day.
I am developing an app to play multiple musics but I'm stuck where the music stop when I select share at the sidebar (I want the music to continue to play because user didn't pause it)
I am using api from RESideMenu and I suspect initRootController is the cause to made the music stop.
Someone suggested me to put the music at the appDelegate because the music might be deallocated when it switch view controller. However, I think that this is not a good way to do as I will later add on more musics with different image background and the architecture of the app will be very messy as I stock each music in ThemeObject and call the music in cafeViewController.
Is there a better way to do this?
This is my code >>> source.
I've check your repo and the sound seems to happen inside your ThemeObject and the only place where you create and link one of those is inside your CafeViewController. So every time the CafeViewController gets unloaded this will remove the only reference to your ThemeObject and it will be garbage collected. To check if the CafeViewController gets unloaded you could put a breakpoint inside this method:
- (void)dealloc {
// Just a line where you can put your breakpoint
}
The advice to put it inside the AppDelegate isn't completely backwards as indeed you would be better off to put it inside an object that is around all of the time. However to abuse AppDelegate as a dumping ground for all your centralised features is a bad practice. For simple apps you might be better off with a Singleton approach, where you always have one instance of an object and that object maintains itself during the existence of your application.
This is what a typical singleton looks like:
#interface ThemeManager : NSObject
#property NSArray *themes;
+ (id)sharedManager;
// Add other methods here
#end
#implementation ThemeManager
+ (id)sharedInstance {
static ThemeManager *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (id)init {
if (self = [super init]) {
ThemeObject *cafeTheme = [[ThemeObject alloc] initWithBackgroundImg:#"cafeBG.png" audio:#"cafeAudio"];
ThemeObject *cafeTheme1 = [[ThemeObject alloc] initWithBackgroundImg:#"cafeBG.png" audio:#"cafeAudio"];
// Create as much as you need in the same way
self.themes = #[cafeTheme, cafeTheme1]; // And add them to the array of themes
}
return self;
}
// Implement other methods
#end
So you never init directly but always ask for the shared instance by calling something like
MusicManager *manager = [MusicManager sharedInstance];
ThemeObject *firstTheme = (ThemeObject *) [manager.themes firstObject];
[firstTheme setAudioPlay];
You can start, pause, stop and change songs with this central object without worrying about the lifecycle of your ViewControllers. You also can start a song from for example CafeViewController and you can stop the song CafeViewController started from the HotelViewController when you start the Hotel song.
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.
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
I have one NSMutableArray in FirstViewController declared as firstArray.
I want to copy the secondArray into firstArray.
In the SecondViewController,
Self.FirstViewController.firstArray = self.secondArray;
When I attempt to NSLog the firstArray.count from the FirstViewController, it display 0. It should have two objects in the array
Anyone can advise on this?
You can choose one of this solutions:
Singleton
Passing Data between ViewControllers
Delegation
You can find all the info you need right here: https://stackoverflow.com/a/9736559/1578927
Singleton example:
static MySingleton *sharedSingleton;
+ (void)initialize
{
static BOOL initialized = NO;
if(!initialized)
{
initialized = YES;
sharedSingleton = [[MySingleton alloc] init];
}
}
It looks like either the second array has already been deallocated when passing the reference to the first view controller, or the first view controller itself has already been nilled out. If the first is true, then you may need a different model object to hold your data rather than persisting it in the controller layer of your app. If that is not the case, then you may want to consider a direct copy. The easiest way of doing this is to declare the firstArray property as the keyword copy rather than strong in your interface file.
If you do need to persist the data in the model layer of your app, a singleton pattern object would indeed be one way of achieving this as EXEC_BAD_ACCESS (nice name!) points out. A slightly more modern (though functionally equivalent) way of writing a singleton is as follows.
#interface MySingleton : NSObject
#property (strong, readwrite) id myData;
+ (id)sharedSingleton
#end
#implementation MySingleton
+ (id)sharedSingleton
{
static MySingleton *singleton = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleton = [[MySingleton alloc] init];
// Do other setup code here.
});
return singleton;
}
#end
Note the use of dispatch_once - this makes certain that the static singleton can only be created once (whereas technically, you can invoke +[NSObject initialize] as many times as you feel like manually, though I'd never advise doing so).
You may also take advantage of NSNotificationCenter
SecondViewController.m
[[NSNotificationCenter defaultCenter] postNotificationName:#"arrayFromSecondVC" object:secondArray];
FirstViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(populateArray:) name:#"arrayFromSecondVC" object:nil];
}
-(void)populateArray:(NSNotification *)notif
{
self.firstArray = [notif object];
}
And remove the notification when the viewUnload or didRecieveMemoryWarning method.
Hope it helps.
I've asked a question about the same issue before, and the solutions worked, but it was not a compatible iOS 4.3 solution, and I thought my design is not the right one.
Now I want to show a MFMailComposeView(Controller) as a modal view on top of my RootView(Controller) when i press a button. And instead of making it the delegate i want to make a simple NSObject which implements the protocol.
Who is also capable to show the MFMailComposeView(Controller) in the RootViewController.
I am trying this design/solution which gives me memory allocation/access problems.
RootViewController.m:
- (IBAction)tapExportButton:(id)sender
{
SendMailController *sendMailController = [[SendMailController alloc]initWithParentViewController:self];
[sendMailController openMailDialog];
[sendMailController release];
}
SendMailController.h
#interface SendMailController : NSObject <MFMailComposeViewControllerDelegate>
- (id)initWithParentViewController:(UIViewController *)parentViewController;
- (void)openMailDialog;
#property (retain, nonatomic) UIViewController* parentViewController;
#end
SendMailController.m
#import "SendMailViewController.h"
#implementation SendMailController
#synthesize parentViewController = _parentViewController;
- (id)initWithParentViewController:(UIViewController *)parentViewController
{
if (self=[super init]) {
self.parentViewController = parentViewController;
}
return self;
}
- (void) dealloc
{
self.parentViewController = nil;
[super dealloc];
}
- (void)openMailDialog
{
if ([MFMailComposeViewController canSendMail])
{
MFMailComposeViewController *mailer = [[MFMailComposeViewController alloc] init];
mailer.mailComposeDelegate = self;
...
mailer.modalPresentationStyle = UIModalPresentationPageSheet;
[self.parentViewController presentModalViewController:mailer animated:YES];
[mailer release];
}
}
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
switch (result)
...
// Remove the mail view
[controller.parentViewController dismissModalViewControllerAnimated:YES];
}
#end
When I set a breakpoint in the delegation method, it crashes already before that.
Is is something with the delegate property of mailer (MFMailComposeViewController)?
The problem is that you create your instance of SendMailController and try to show the composer view, and then you release the SendMailController. This causes it to be deallocated. It looks like it works because the composer view is on screen - this is because it has been retained by the presentModalViewController call.
To fix, you need to retain your instance of SendMailController and release it when the composer has been dismissed.
The correct way to do it (and required if you use ARC, and you should use ARC) is to provide a delegate callback to tell the owner that it's finished - which kind of makes the class pointless if all it does is wrap the composer.
The cheating way (which only works when not using ARC, and which you need to be very careful with) is to have your object retain itself when it presents the composer and release itself when the composer is dismissed.
The underlying problem being your root view controller containing all the logic, you should look at using child view controllers (if a single screen holds all your UI). Usually though, your root view should be the simple class (like a master list of options) and the views it presents would be more complex (the detail views). You need to look at ensuring that the appropriate class owns the responsibility for each screen of UI.