I'm trying to create a class cluster as subclass of UIViewController to accomplish some points:
1. Different behavior of the ViewController depending on actual iOS version
2. iOS version checks don't clutter up the code
3. Caller doesn't need to care
So far I got the classes MyViewController, MyViewController_iOS7 and MyViewController_Legacy.
To create instances I call the method myViewControllerWithStuff:(StuffClass*)stuff which is implemented like:
+(id)myViewControllerWithStuff:(StuffClass*)stuff
{
if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1)
{
return [[MyViewController_iOS7 alloc] initWithStuff:stuff];
}
else
{
return [[MyViewController_Legacy alloc] initWithStuff:stuff];
}
}
The caller uses myViewControllerWithStuff:. After that the so created view controller gets pushed onto a UINavigationController's navigation stack.
This nearly works as intended with one big downside: ARC doesn't dealloc the instance of MyViewController_xxx when it gets popped from the navigation stack. Doesn't matter which iOS version.
What am I missing?
UPDATE: -initWithStuff:
-(id)initWithStuff:(StuffClass*)stuff
{
if (self = [super init])
{
self.stuff = stuff;
}
return self;
}
This method is also implemented in MyViewController. The differences kick in later (e.g. viewDidLoad:).
First of all: Thanks for all your help, comments, answers, suggestions...
Of course there was another strong reference to the MyViewController-object. But it wasn't that obvious, because it was not a property or instance variable.
In viewDidLoad I did the following:
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self saveState];
}];
This should prevent data loss in case the user sends the app to the background. Of course the block captures the needed parts of its environment. In this case it captured self. The block keeps self alive until it (the block) gets destroyed, which is the case when e.g. [[NSNotificationCenter defaultCenter] removeObserver:self]; gets called. But, bad luck, this call is placed in the dealloc method of MyViewController which won't get called as long as the block exists...
The fix is as follows:
__weak MyViewController *weakSelf = self;
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[weakSelf saveState];
}];
Now the block captures weakSelf. This way it can't keep the MyViewController-object alive and everything deallocs and works just fine.
Related
In my iOS application, I am posting a NSNotification and catching it in one of my UIView in main thread. I want to pass extra information along with the notification. I was using userInfo dictionary of NSNotification for that.
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotifyValueComputedFromJS" object:self userInfo:#{#"notificationKey":key,#"notificationValue":value,#"notificationColor":color,#"notificationTimeStamp":time}];
key, value, color and time are local variables which contains the value I need to pass. In UIView I am adding observer for this notification and I am using notification.userInfo to get these data
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification:) name:#"NotifyValueComputedFromJS" object:nil];
-(void)receiveNotification:(NSNotification *)notification
{
if ([notification.userInfo valueForKey:#"notificationKey"]!=nil && [[notification.userInfo valueForKey:#"notificationKey"] isEqualToString:self.notificationKey] && [notification.userInfo valueForKey:#"notificationValue"]!=nil) {
[self updateLabelWithValue:[notification.userInfo valueForKey:#"notificationValue"]];
}
}
The frequency in which this notification is posted is 4 times in one second. I am doing some animations also in main thread. The problem I am facing here is my UI is lagging. UI will respond to scroll events or touch events with huge delay(I have faced a delay of even 1 to 2 seconds). After some research I came to know that NSDictionary is bulky and will cause lag if used in main thread. Is there any other way I can pass my data through NSNotification?
I have tried out another way. I have created a custom NSObject class to save the data I want and I am passing it as the object parameter of postNotification method.
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotifyValueComputedFromJS" object:customDataObject userInfo:nil];
Here customDataObject is an instance of my custom NSObject class. I know the parameter is meant to be the sender of notification(usually it will be self). Is it a wrong approach if I am sending a custom object as parameter?
As BobDave mentioned, the key is to send the notification on some thread other than the main UI thread. This can be accomplished with dispatch_async, or with a queue.
The typical pattern for this behavior is sender:
-(void)sendDataToObserver {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotifyValueComputedFromJS" object:customDataObject userInfo:userInfo:#{#"notificationKey":key,#"notificationValue":value,#"notificationColor":color,#"notificationTimeStamp":time}];
});
}
And receiver (NOTE: weak self because retain cycles):
-(void)addObserver {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification:) name:#"NotifyValueComputedFromJS" object:nil];
}
-(void)receiveNotification:(NSNotification *)notification {
if ([notification.userInfo valueForKey:#"notificationKey"]!=nil && [[notification.userInfo valueForKey:#"notificationKey"] isEqualToString:self.notificationKey] && [notification.userInfo valueForKey:#"notificationValue"]!=nil) {
__weak typeof (self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf updateLabelWithValue:[notification.userInfo valueForKey:#"notificationValue"]];
});
}
}
Maybe you could use - addObserverForName:object:queue:usingBlock:
and use a non-main queue to execute the block in order to reduce the lag. Also, shouldn't the observer be added in a UIViewController, not a UIView?
I am trying to use this within a project: https://github.com/zakkhoyt/VWWPermissionKit
I do not understand KVO/Notification Center as much as I'd like so posting a question.
Basically the init and dealloc for the Permission Manager look like this:
- (instancetype)init {
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserverForName:VWWPermissionNotificationsPromptAction object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
dispatch_async(dispatch_get_main_queue(), ^{
VWWPermission *permission = note.userInfo[VWWPermissionNotificationsPermissionKey];
[permission presentSystemPromtWithCompletionBlock:^{
dispatch_async(dispatch_get_main_queue(), ^{
[permission updatePermissionStatus];
if(permission.status == VWWPermissionStatusDenied){
[self.permissionsViewController displayDeniedAlertForPermission:permission];
}
[self checkAllPermissionsSatisfied];
});
}];
});
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
dispatch_async(dispatch_get_main_queue(), ^{
[self readPermissions];
});
}];
}
return self;
}
-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
If I want to read a set of permissions I would call this:
NSArray *permissionsToRead = #[
[VWWCoreLocationWhenInUsePermission permissionWithLabelText:nil],
[VWWNotificationsPermission permissionWithLabelText:nil]
];
[VWWPermissionsManager readPermissions:permissionsToRead resultsBlock:^(NSArray *permissions) {
// Do something with the result
}
}];
This all works fine. The issue is that the dealloc is not being called, therefore the Notifications are still being called such as the UIApplicationDidBecomeActiveNotification being created in the init method.
As far as I can see the Permission Manager is created and not referenced and therefore it just hangs around.
The public method for the readPermssions is as follows:
+(void)readPermissions:(NSArray*)permissions resultsBlock:(VWWPermissionsManagerResultsBlock)resultsBlock{
VWWPermissionsManager *permissionsManager = [[self alloc]init];
[permissionsManager readPermissions:permissions resultsBlock:resultsBlock];
}
A new instance is created and another method is called then passes the resultsBlock back. There is nothing that releases this as far as I can tell. How would I get the dealloc to be called?
It's because NSNotificationCenter is retaining the observer object, which is retaining the block that is registered with it, which is capturing your view controller and preventing it from being deallocated.
If you want your view controller to be able to be released then you should create a weak reference (__weak typeof(self) weakSelf = self;) to it outside the block and use weakSelf inside the block.
You also aren't removing the observer correctly. When you add an observer using the notification center block api it returns an object which is what it is actually adding as the observer and which you need to keep a reference to and pass to removeObserver:.
I would suggest just not using the method to observe with a block since it adds more management trouble than it's worth. Use the one that takes a selector instead.
I'm writing an iOS game with a bunch of different types of enemies and items/drops. Specific ones need to trigger things on different events, so I'm experimenting with using NSNotificationCenter.
Everything works when listener is defined in a way that's retained. When I define it locally or add it to an NSMutableArray, it's lost and the postNotification throws a EXC_BAD_ACCESS
This breaks, because the listener isn't retained past this.
MyListener *listener = [[MyListener alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:listener selector:#selector(myEventHandler:) name:#"MyEvent" object:nil];
This is a super basic example of what works:
MyListener *listener;
-(id)init {
listener = [[MyListener alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:listener selector:#selector(myEventHandler:) name:#"MyEvent" object:nil];
}
The issue is that I will have a bunch of different enemy/item classes that need to listen for an event. I don't want a different class-level variable for every single one - but admit that I may be going about this the wrong way. I'm still somewhat new to iOS.
Well, first of all, your init snippet is entirely wrong. init methods should look more like this:
- (instancetype) {
self = [super init];
if (self) {
// initialize
}
return self;
}
And in your example, // initialize would be replaced with:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myEventHandler:)
name:#"MyEvent"
object:nil];
But what's most important here is that at some point before this object is completely deallocated, we must stop observing. If we do not, after this object is deallocated, NSNotificationCenter will try to send a message to a deallocated object, which causes your EXC_BAD_ACCESS.
You may wish to stop observing earlier than dealloc, but at a minimum, you need to add this to your class:
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
As for keeping the object alive, well that's as simple as keeping a strong reference to it. This is sort of an entirely different question. Although... if you're trying to keep an object alive simply to respond to a notification, there's no need to even use an object here.
You can give the notification center a block to respond to notification with using the following method:
- (id)addObserverForName:(NSString *)name
object:(id)obj
queue:(NSOperationQueue *)queue
usingBlock:(void (^)(NSNotification *))block
I have a situation in which it can happen, that the last strong reference to an observer is removed while the observer processes an incoming notification.
That leads to the observer being deallocated immediately. I would normally expect, that a currently running method can finish before an object is deallocated. And this is what happens during normal message dispatch.
A simplified version of the code:
TKLAppDelegate.h:
#import <UIKit/UIKit.h>
#import "TKLNotificationObserver.h"
#interface TKLAppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) TKLNotificationObserver *observer;
#end
TKLAppDelegate.m:
#import "TKLAppDelegate.h"
#implementation TKLAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Create an observer and hold a strong reference to it in a property
self.observer = [[TKLNotificationObserver alloc] init];
// During the processing of this notification the observer will remove the only strong reference
// to it and will immediatly be dealloced, before ending processing.
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotificationName" object:nil];
// Create an observer and hold a strong reference to it in a property
self.observer = [[TKLNotificationObserver alloc] init];
// During the manual calling of the same method the observer will not be dealloced, because ARC still
// holds a strong reference to the message reciever.
[self.observer notificationRecieved:nil];
return YES;
}
#end
TKLNotificationObserver.m:
#import "TKLNotificationObserver.h"
#import "TKLAppDelegate.h"
#implementation TKLNotificationObserver
- (id)init {
if (self = [super init]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notificationRecieved:) name:#"NotificationName" object:nil];
}
return self;
}
- (void)notificationRecieved:(NSNotification *)notification {
[self doRemoveTheOnlyStrongReferenceOfThisObserver];
NSLog(#"returing from notification Observer");
}
- (void)doRemoveTheOnlyStrongReferenceOfThisObserver {
TKLAppDelegate * delegate = [[UIApplication sharedApplication] delegate];
delegate.observer = nil;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"NotificationName" object:nil];
NSLog(#"dealloc was called");
}
#end
Using the App Delegate in this way is no good style and only done for demonstration purposes, the real code does not involve the app delegate.
The output is:
dealloc was called
returing from notification Observer
returing from notification Observer
dealloc was called
That is in the first case dealloc is called before the notification processing finished. In the second case it behaves as I expected.
If I keep a strong reference to self inside notificationReceived the dealloc only happens after the processing. My expectation was, that ARC, the runtime or whoever else keeps this strong reference for me.
What is wrong with my code?
Or is something wrong with my expectation?
Is there any Apple- or Clang-provided documentation on this?
My expectation was, that ARC, the runtime or whoever else keeps this
strong reference for me.
That is not the case, as documented in the Clang/ARC documentation:
The self parameter variable of an Objective-C method is never actually
retained by the implementation. It is undefined behavior, or at least
dangerous, to cause an object to be deallocated during a message send
to that object.
Therefore, if calling doRemoveTheOnlyStrongReferenceOfThisObserver
can have the side-effect of releasing self, you would have to use
an temporary strong reference to avoid deallocation:
- (void)notificationRecieved:(NSNotification *)notification {
typeof(self) myself = self;
[self doRemoveTheOnlyStrongReferenceOfThisObserver];
NSLog(#"returing from notification Observer");
}
A better solution would probably to avoid this side-effect.
the first dealloc probably happens as you set the observer property of the appDelegate twice and therefore the first instance is dealloced as soon as you set it the second time
I don't understand why I need to have a weak self in some blocks, while others seems to work fine.
If I don't have a weak ref to self with the Notification block, the dealloc won't be released. It works perfectly fine with the second though.
//When using this, dealloc is NOT being called
[[NSNotificationCenter defaultCenter] addObserverForName:PROD_DONE object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self hideAds];
}];
//When using this, dealloc IS being called
[_match endMatchInTurnWithMatchData:_match.matchData completionHandler:^(NSError *error) {
[self hideAds];
}];
If I create a weak ref to self, it works:
__weak GameViewController *weakSelf = self;
[[NSNotificationCenter defaultCenter] addObserverForName:PROD_DONE object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[weakSelf hideAds];
}];
This is because one reference goes away over time (for example - when the completion handler is called), the block is released. In this case, there is no retain cycle, because the reference to self will be released.
However, with the NSNotification example, the block reference must always be retained (unless it is removed manually) because it is still listening for the NSNotification. In this case, the reference to self causes a retain cycle causing the class to not be retained.