I have a func which uses the nw_path_monitor_t to register for network events.
// Entry point.
// Will be called from AppDelegate when app starts up
void TestNWPathMonitor () {
PrintToFile("TestingNWPathMonitor\n");
NotificationReceiver *notification_receiver = [[NotificationReceiver alloc] init];
// Set up the notification receiver to listen for wifi notification
[notification_receiver RegisterNotification];
monitor = nw_path_monitor_create ();
nw_path_monitor_set_update_handler (monitor, WifiNetworkChangeCB);
nw_path_monitor_start(monitor);
}
I've provided the callback, which will be invoked when there is a change in network events. In the callback (as shown below), I'm looking out for wifi events and posting a notification to the default notification center.
nw_path_monitor_update_handler_t WifiNetworkChangeCB = ^ (nw_path_t path) {
PrintToFile("Wifi Network change!!\n");
nw_path_status_t status = nw_path_get_status (path);
if (nw_path_uses_interface_type (path, nw_interface_type_wifi)) {
if (status == nw_path_status_satisfied) {
PrintToFile("nw_path_status_satisfied\n");
[[NSNotificationCenter defaultCenter] postNotificationName:#"WifiNetworkChange" object:nil];
} else {
PrintToFile("!(nw_path_status_satisfied)\n");
}
}
};
This is the NotificationReceiver class:
// NotificationReceiver.h
#include <Foundation/Foundation.h>
#interface NotificationReceiver : NSObject
- (void) HandleNotification : (NSNotification *) pNotification;
- (void) RegisterNotification ;
#end
// NotificaitonReceiver.m
#implementation NotificationReceiver
- (void) HandleNotification : (NSNotification *) pNotification {
PrintToFile([[NSString stringWithFormat:#"Received notification: %#\n", pNotification.name] UTF8String]);
}
- (void) RegisterNotification {
PrintToFile("RegisterNotification!\n");
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(HandleNotification:) name:#"WifiNetworkChange" object:nil];
}
#end
RegisterNotification, called at the beginning (as shown in the first code snippet) will add the instance as an observer and HandleNotification is the receiver of the wifi notification posted from the WifiNetworkChangeCB block.
The problem is, when I receive the wifi event, the WifiNetworkChangeCB is invoked and the postNotificationName function is executed (Have verified with the debugger), but HandleNotification doesn't receive the notification.
I'm getting the following output:
TestingNWPathMonitor
RegisterNotification!
Wifi Network change!!
Whereas, the expected output is:
TestingNWPathMonitor
RegisterNotification!
Wifi Network change!!
Received notification: WifiNetworkChange
I have read the documentation of the notification center to understand its usage. Have also referred this answer.
I have also referred the documentation of the funcs I'm using (added them as hyperlinks while explaining the problem), everything seems fine.
But I'm obviously missing something (Since it didn't work). Any help will be greatly appreciated.
Reason: your C function TestNWPathMonitor() allocates NotificationReceiver *notification_receiver but the object created is nowhere stored when you leave the scope. So with ARC memory management the object will be released when the scopes block is left, aka its stack is "empty" again.
your monitor aka typedef NSObject<OS_nw_path_monitor> *nw_path_monitor_t; seems to be a global so it will still exist after leaving the scope giving you possibly the misconception that same would be the case for objc allocations, well yes and no. Same would have happend to monitor if it would have been a local variable.
Debugging: Observering [NSNotificationCenter defaultCenter] allows you to catch Notifications almost anywhere in your code, no matter what thread you wait for them, its a NSString based API for good reason with all its pro's and cons. Because of that easy approach it can be hard to find why it is not working. But basically placing an Observer in main.m or APPDelegate should always tell you if it is properly working on the posting side to be sure you did not miss-spell the NotificationName. To avoid the latter case we often declare
extern NotificationName const kSomeNiceNotification;
// in .h && the following in .m
NotificationName const kSomeNiceNotification = #"kSomeNiceNotification";
and use this global key instead as name.
Hint: you can also create a single time Notification that will be triggered and destroy itself when received with other implications you have to think of when doing so. like so (from the Xcode docs)..
NSNotificationCenter * __weak center = [NSNotificationCenter defaultCenter];
id __block token = [center addObserverForName:#"OneTimeNotification"
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
NSLog(#"Received the notification!");
[center removeObserver:token];
}];
See [NSOperationQueue mainQueue] in above code snippet?
You could pass nil there but then the notifications block would execute on the thread the notification was send. When used in UI code, which most often is the usecase for Notifications this is important as UI tasks needs to be executed on the main thread where passing nil forces you later on to wrap UI stuff in
dispatch_async(dispatch_get_main_queue(), ^{ /* UI code block */ }); // or
dispatch_sync(dispatch_get_main_queue(), ^{ /* UI code block */ });
which you don't need to do when you told the notification observer where to execute the notifications block when received.
Ps: in objc we start methodnames in small letters, you will run in trouble when setter & getters violate the "camelCased" methodname rule because the interface #property NSObject *someName; becomes -(NSObject*)someName; as getter and -(void)setSomeName:(NSObject*)somename; as setter with modern objc. This tells also why we use the lower underscore to mark local class variables that are the counterparts of almost any property.. in this given example the property NSObject *someName would have an internal _someName counterpart. Not going deeper here as in oldschool objc there is more to know about the class declarations #dynamic ... & #synthesize ... that allow more detailed control of the (internal) local class variable name.
Why to bother about that? Your NotificationReceiver *notification_receiver could override a class property with the same name giving you the impression you made everything right but is still not working as the declaration would still leave the stack empty then. So declaring the variable like _notification_receiver = ... in the methods/function block would make very clear you meant the internal counterpart of its #property NotificationReceiver *notification_receiver; and not a extra local variable.
Related
I have some bluetooth class that needs to be alive during all views .
That means, that i have to call functions in this class from different views, and ALSO get delegates from this class to different views .
So , i have to initialise it once to create connection, than later, i have to use it again from different views(classes) and register to get notifications from it.
I was thinking about a singleton, that HE will create an instance of that bluetooth class, and i can access him from anywhere .
But, i would also like that any view can get delegates from it .
How would i do that ?
i have read What should my Objective-C singleton look like?
But maybe singleton is not what i need ?
How can you create a class to always be alive, and register to get delegates from it from anywhere ?
(how can it be done with app delegate class ? )
I have had the similar query a while back
Problem : Multiple Classes need to receive delegate calls from single instance
Solution: I used a combination of sharedInstance , delegates and NSNotifications to handle the problem
SharedInstance
+ (SomeBluetoothClass *) sharedInstance {
static dispatch_once_t once;
static SomeBluetoothClass *sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
Delegate
#property (weak, nonatomic) id <SomeBluetoothClassDelegate> delegate;
Since delegate can respond to only one class at a time. Assign class in focus based on your logic. Then whenever you want send info to all send it across using NSNotificationCenter from the sharedInstance. Send the info through using userInfo dictionary of NSNotifications
Notifications
[[NSNotificationCenter defaultCenter] postNotificationName:SomeBluetoothClassNotification
object:self
userInfo:info];
Model the structure of SomeBluetoothClass to be thread safe and handle all notifications along with the delegates and it should work fine.
Have a lots way for your situation. Firstly, you should understand that creating object is not too heavy.
So if you want to use Delegate, you can create a Factory method
Ex:
+ (instancetype)bluetoothManagerWithDelegate:(id<delegate>)delegate {
return [self alloc] initWithDelegate:delegate];
}
So that you also don't care about conflict of concurrence. Because you have separate Bluetooth class.
If you still want to use Singleton, in this situation, it depends on how many object you want to notify.
Only 1 views, just use delegate, and set new delegate when you present new view.
More than one, you can use NSNotificationCenter or Observer, you can google these keywords, it have a lot of tutorial and document on the internet help you use it.
I think you can create NSMutableArray in your singleton with links on your views and call someMethod for all objects when is needed. Don't forget remove views from array when its don't need anymore. That is simple realization of pattern called "Observer".
You need to create a class that should be allocated memory once in a lifetime
I am posting a small code snippet which can help you.
In your .m file
#pragma mark - Shared Instance
static BlootoothClass *_sharedblootoothclass = nil;
+ (BlootoothClass *) sharedClass {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedblootoothclass = [[self alloc] init];
// DO YOUR STUFF TO INTIALISE YOUR CLASS
});
return _sharedblootoothclass;
}
in Your .h file
+ (BlootoothClass *) sharedClass
dispatch_once is the queue with dispatch the instance of your class one in a lifetime and you can access its function all over in the app any time.
Now to get any data from it you can get it from instance from any where like
[BlootoothClass sharedClass].anyObject
And you also can send post notification from here in any of its function
- (void)detectedBlootoothdevice{
[[NSNotificationCenter defaultCenter] postNotificationName:#"newdevicedetected" object:nil];
}
You should not use delegates as you cant call same delegate function at multiple class because last delegate get overwritten.
A number of Cocoa Touch classes leverage a design pattern of coalescing events. UIViews, for example, have a method setNeedsLayout which causes layoutSubviews to be called in the very near future. This is especially useful in situations where a number of properties influence the layout. In the setter for each property you can call [self setNeedsLayout] which will ensure the layout will be updated, but will prevent many (potentially expensive) updates to the layout if multiple properties are changed at once or even if a single property were modified multiple times within one iteration of the run loop. Other expensive operations like the setNeedsDisplay and drawRect: pair of methods follow the same pattern.
What's the best way to implement pattern like this? Specifically I'd like to tie a number of dependent properties to an expensive method that needs to be called once per iteration of the run loop if a property has changed.
Possible Solutions:
Using a CADisplayLink or NSTimer you could get something working like this, but both seem more involved than necessary and I'm not sure what the performance implications of adding this to lots of objects (especially timers) would be. After all, performance is the only reason to do something like this.
I've used something like this in some cases:
- (void)debounceSelector:(SEL)sel withDelay:(CGFloat)delay {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:sel object:nil];
[self performSelector:sel withObject:nil afterDelay:delay];
}
This works great in situations where a user input should only trigger some event when a continuous action, or things like that. It seems clunky when we want to ensure there is no delay in triggering the event, instead we just want to coalesce calls within the same run loop.
NSNotificationQueue has just the thing you're looking for. See the documentation on Coalescing Notifications
Here a simple example in a UIViewController:
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(configureView:)
name:#"CoalescingNotificationName"
object:self];
[self setNeedsReload:#"viewDidLoad1"];
[self setNeedsReload:#"viewDidLoad2"];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setNeedsReload:#"viewWillAppear1"];
[self setNeedsReload:#"viewWillAppear2"];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self setNeedsReload:#"viewDidAppear1"];
[self setNeedsReload:#"viewDidAppear2"];
}
- (void)setNeedsReload:(NSString *)context
{
NSNotification *notification = [NSNotification notificationWithName:#"CoalescingNotificationName"
object:self
userInfo:#{#"context":context}];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification
postingStyle:NSPostASAP
coalesceMask:NSNotificationCoalescingOnName|NSNotificationCoalescingOnSender
forModes:nil];
}
- (void)configureView:(NSNotification *)notification
{
NSString *text = [NSString stringWithFormat:#"configureView called: %#", notification.userInfo];
NSLog(#"%#", text);
self.detailDescriptionLabel.text = text;
}
You can checkout the docs and play with the postingStyle to get the behavior you desired. Using NSPostASAP, in this example, will give us output:
configureView called: {
context = viewDidLoad1;
}
configureView called: {
context = viewDidAppear1;
}
meaning that back-to-back calls to setNeedsReload have been coalesced.
I've implemented something like this using custom dispatch sources. Basically, you setup a dispatch source using DISPATCH_SOURCE_TYPE_DATA_OR as such:
dispatch_source_t source = dispatch_source_create( DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0, dispatch_get_main_queue() );
dispatch_source_set_event_handler( source, ^{
// UI update logic goes here
});
dispatch_resume( source );
After that, every time you want to notify that it's time to update, you call:
dispatch_source_merge_data( __source, 1 );
The event handler block is non-reentrant, so updates that occur while the event handler is running will coalesce.
This is a pattern I use a fair bit in my framework, Conche (https://github.com/djs-code/Conche). If you're looking for other examples, poke around CNCHStateMachine.m and CNCHObjectFeed.m.
This borders on "primarily opinion based", but I'll throw out my usual method of handling this:
Set a flag and then queue processing with performSelector.
In your #interface put:
#property (nonatomic, readonly) BOOL needsUpdate;
And then in your #implementation put:
-(void)setNeedsUpdate {
if(!_needsUpdate) {
_needsUpdate = true;
[self performSelector:#selector(_performUpdate) withObject:nil afterDelay:0.0];
}
}
-(void)_performUpdate {
if(_needsUpdate) {
_needsUpdate = false;
[self performUpdate];
}
}
-(void)performUpdate {
}
The double check of _needsUpdate is a little redundant, but cheap. The truly paranoid would wrap all the relevant pieces in #synchronized, but that's really only necessary if setNeedsUpdate can be invoked from threads other than the main thread. If you're going to do that you also need to make changes to setNeedsUpdate to get to the main thread before calling performSelector.
It's my understanding that calling performSelector:withObject:afterDelay: using a delay value of 0 causes the method to be called on the next pass through the event loop.
If you want your actions to be queued up until the next pass through the event loop, that should work fine.
If you want to coalesce multiple different actions and only want one "do everything that accumulated since the last pass through the event loop" call, you could add single call to performSelector:withObject:afterDelay: in your app delegate (or some other single instance object) at launch, and invoke your method again at the end of each call. You could then add an NSMutableSet of things to do, and add an entry to the set each time you trigger an action that you want to coalesce. If you created a custom action object and overrode the isEqual (and hash) methods on your action object, you could set it up so there would only ever be a single action object of each type in your set of actions. Adding the same action type multiple times in a pass through the event loop would add one and only one action of that type).
Your method might look something like this:
- (void) doCoalescedActions;
{
for (CustomActionObject *aCustomAction in setOfActions)
{
//Do whatever it takes to handle coalesced actions
}
[setOfActions removeAllObjects];
[self performSelector: #selector(doCoalescedActions)
withObject: nil
afterDelay: 0];
}
It's hard to get into details on how to do this without specific details of what you want to do.
I'm quite new to iOS development, and I've been trying to solve the following problem:
I have a ViewController displaying information that changes with time. I have another controller (TimeController) managing time. TimeController has an NSTimer firing every second to check whether I've entered a new time slot (there's some logic behind it, and if I've entered a new time slot that means that the information in the ViewController needs to be updated.
In my understanding, I need some kind of callback procedure but I couldn't figure out how to do it - I read about blocks but to be honest they're quite overwhelming and I couldn't relate my problem to the examples I saw.
The TimeController looks something like the following:
// TimeController.h
#interface TimeController : NSObject
#property (weak) NSTimer *periodicTimer;
#property NSInteger timeslot;
#end
// TimeController.m
#import "TimeController.h"
#implementation TimeController
-(void)startTimer {
self.periodicTimer = [NSTimer scheduledTimerWithTimeInterval:(1) target:self
selector:#selector(onTimer) userInfo:nil repeats:YES];
}
-(void)onTimer {
// check if anything has changed.
// If so, change timeslot. Notify "listening" objects.
}
In a simple example with a single ViewController depending on TimeController, I'm imagining something like this:
// ViewController.h
#interface ViewController : UIViewController
#property TimeController* timeCtrl;
#end
// ViewController.m
#import "ViewController.h"
#import "TimeController.h"
-(void)onNotificationFromTimeController {
// timeslot has changed in the TimeController.
NSInteger tslot = timeCtrl.timeslot;
// figure out new display value depending on tslot. Update the view
}
What's missing here (among other stuff like proper initialisation of timeCtrl) are the callback mechanisms. I'd appreciate any help on this!
Notifications
Add a listener to the event (let's call it "TimeNotificationEvent") in ViewController:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onNotificationFromTimeController) name:#"TimeNotificationEvent" object:nil];
In the onTimer method in TimeController.m, add the following code to post a notification:
[[NSNotificationCenter defaultCenter] postNotificationName:#"TimeNotificationEvent" object:nil];
Sidenote: To stop listening for notifications:
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"TimeNotificationEvent" object:nil];
Easy to setup
You can have multiple listeners for the same event
Blocks
Add a property in TimeController.h:
#property (nonatomic, copy) dispatch_block_t timerBlock;
Add a call to the timerBlock in TimeController.m:
-(void)onTimer {
// Check for nil to avoid crash
if (_timerBlock) {
_timerBlock();
}
}
Assign the block in ViewController:
_timeCtrl.timerBlock = ^{
// Do stuff to the UI here
};
A bit more complex (retain cycles, syntax etc)
Only one listener (with this particular example implementation)
Easier to follow the code
I am seeing some random crashes with my app (although not reproducible when I run through same steps). I am observing the contentOffset property of the scrollview to take some action when it changes.
But I am getting below exception (randomly) with my below code of KVO registration and de-registration.
Is there any safe check that can be applied here.
*** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <MyPagingController 0x1f05e460> for the key path "contentOffset" from <UIScrollView 0x1f0a8fd0> because it is not registered as an observer.'
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.scrollView addObserver:self forKeyPath:#"contentOffset" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}
- (void)viewWillDisappear:(BOOL)iAnimated {
[super viewWillDisappear:iAnimated];
[self.scrollView removeObserver:self forKeyPath:#"contentOffset"];
}
Your unsubscribing code somehow gets hit more often than the subscribing code. Unfortunately KVO does not handle this nicely and the failure throws an exception rather than just doing nothing as you would expect. You either need to make sure it only gets hit once, or at least catch the exception like this:
#try
{
[self.scrollView removeObserver:self forKeyPath:#"contentOffset"];
}
#catch (NSException * __unused exception) {}
}
As much as I love KVO, this need to balance observing and removing is a real issue. You will also get crashes if you nil an object that was KVO observing without telling the sending class to de-register you and the class then tries to send an update to the receiver object. Oops! Bad Access!
As there is no official way to ping to see if you have added an observer (which is ridiculous given that a huge part of what goes on in OS is based on KVO), I just use a simple flag system to ensure that nothing gets added more than once. In the example below, I have a bool flag to track the observer status.
#interface RepTableViewController ()
#property (nonatomic, assign) BOOL KVOSet;
#end
#pragma mark - KVOObserving
- (void)_addKVOObserving
{
//1. If the BOOL flag shows we are already observing, do nothing.
if (self.KVOSet) return;
//2. Set the flag to YES BEFORE setting the observer as there's no guarantee it will happen immediately.
self.KVOSet = YES;
//3. Tell your class to add you up for the object / property you want to observe.
[[ELRepresentativeManager sharedManager]addObserver:self
forKeyPath:#"myRepresentative"
options:NSKeyValueObservingOptionNew
context:nil];
}
- (void)_removeKVOObserving
{
//1. Do nothing if we have not set an observer
if (!self.KVOSet) return;
//2. If we have an observer, set the BOOL flag to NO before removing
self.KVOSet = NO;
//3. Remove the observer
[[ELRepresentativeManager sharedManager] removeObserver:self
forKeyPath:#"myRepresentative" context:nil];
}
Yes, using flags to check state is frowned upon by the coding-police. But there really is no other way to be sure except to bean count.
Whatever you do, remember that some classes need to observe even after viewWillDisappear is called (but the view still exists somewhere in the hierarchy), so you can't just do the observe/remove from viewWillAppear/WillDisappear trick. You may need to use delegate callbacks to the object that created the view (and will destroy it) to ensure you never leave a KVO calling class hanging. Having said that, I'm no expert in this stuff, so I'm sure there are people who can do this with way more finesse than my patented bean-counting lol
Background:
All my OpenTok methods are in one ViewController that gets pushed into view, like a typical Master/detail VC relationship. The detailVC connects you to a different room depending on your selection. When I press the back button to pop the view away, I get a crash (maybe 1 out of 7 times):
[OTMessenger setRumorPingForeground] message sent to deallocated instance xxxxx
or
[OTSession setSessionConnectionStatus:]: message sent to deallocated instance 0x1e1ee440
I put my unpublish/disconnect methods in viewDidDisappear:
-(void)viewDidDisappear:(BOOL)animated{
//dispatch_async(self.opentokQueue, ^{
[self.session removeObserver:self forKeyPath:#"connectionCount"];
if(self.subscriber){
[self.subscriber close];
self.subscriber = nil;
}
if (self.publisher) {
[self doUnpublish];
}
if (self.session) {
[self.session disconnect];
self.session = nil;
}
//});
[self doCloseRoomId:self.room.roomId position:self.room.position];
}
Here is a trace:
Here is the DetailViewController on Github: link here
How to reproduce:
Make a selection from the MasterVC, that takes you into the DetailVC which immediately attempts to connect to a session and publish
Go back to previous, MasterVC quickly, usually before the session has had an a chance to publish a stream
Try this several times and eventually it will crash.
If I slow down and allow the publisher a chance to connect and publish, it is less likely to cause a crash.
Expected result:
It should just disconnect from the session/unpublish and start a new session as I go back and forth between the Master/DetailVC's.
Other:
What is your device and OS version?
iOS 6
What type of connectivity were you on?
wifi
Zombies Enabled?
Yes
ARC Enabled?
Yes
Delegates set to nil?
Yes, as far as I know
Any help solving this crash would be greatly appreciated. Perhaps I'm missing something basic that I just can't see.
What seems to happen is that the OTSession object in the OpenTok library continues to to send messages to objects in that library that have since been deallocated by switching views. The library has a [session disconnect] method that works fine if you give it enough time, but it takes close to 2-3 seconds, and that's a long time to pause an app between views.
This might be a stupid question, but:
Is there anyway to stop all processes initiated by a certain VC?
Closing the session from viewWillDisappear() works if you can determine for sure that the view is going to be popped, not pushed or hidden. Some answers suggest putting this code in dealloc(). Regarding those suggestions, Apple says,
You should try to avoid managing the lifetime of limited resources using dealloc.
So, here is how you can determine for sure that your view will get popped. viewWillDisappear() is called when the view is popped from the stack, or is otherwise pushed somewhere else. This is the easiest way to determine which, and then unpublish/disconnect if it is truly popped. You can test for this with isMovingFromParentViewController. Also, here is where you can remove specific observers.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated]
// This is true if the view controller is popped
if ([self isMovingFromParentViewController])
{
NSLog(#"View controller was popped");
// Remove observer
[[NSNotificationCenter defaultCenter] removeObserver:self.session];
...
//dispatch_async(self.opentokQueue, ^{
if(self.subscriber){
[self.subscriber close];
self.subscriber = nil;
}
if (self.publisher) {
[self doUnpublish];
}
if (self.session) {
[self.session disconnect];
self.session = nil;
}
//});
[self doCloseRoomId:self.room.roomId position:self.room.position];
}
else
{
NSLog(#"New view controller was pushed");
}
}
Ref: Testing for Specific Kinds of View Transitions
Looks like OpenTok have a bug with usage NSNotificationCenter inside of OTSession and OTMessenger classes. You can see these classes in call-stack are separated with NSNotificationCenter calls:
You can manually unsubscribe your OTSession object when dealloc (hope OpenTok uses defaultCenter):
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self.session];
}
You need to check if this code (dealloc) is really executed. If not - you need to fix problem of UIViewController deallocation. A lot of other answers contains tips how to help UIViewController to be deallocated.
-(void)viewDidDisappear:(BOOL)animated is called whenever the view is hidden, not only when it is popped from the view stack.
So if you push a view over it, viewWillDisappear will be called and your objects deleted.
This is specially problematic if you load these same objects from viewDidLoad: instead of viewDidAppear:.
Perhaps you should put your unpublish/disconnect code in -(void)dealloc.
This is what Apple suggests:
-(void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
But this is only the last resort to remove observers, still often a good habit to always add it to make sure everything is cleand up on dealloc to prevent crashes.
It's still a good idea to remove the observer as soon as the object is no longer ready (or required) to receive notifications.
I most of the time put such a code in the viewWillDisappear, but I guess that doesn't really matter.
I believe the issue is that your session delegate is not set to nil. Just add the following in your viewDidDisappear:
self.session.delegate=nil;
You must call [super viewDidDisappear:animate]; at the beginning. May be it will fix your issue.
And better cleanup your session and subscriber in dealloc method:
- (void) dealloc {
[self.session removeObserver:self forKeyPath:#"connectionCount"];
if(self.subscriber){
[self.subscriber close];
self.subscriber = nil;
}
if (self.publisher) {
[self doUnpublish];
}
if (self.session) {
[self.session disconnect];
self.session = nil;
}
[self doCloseRoomId:self.room.roomId position:self.room.position];
//[super dealloc]; //for non-ARC
}
According to the stack trace you have posted, the notification center reaches out to an OTSession instance that is still alive. Afterwards, this instance provokes a crash calling methods on deallocated objects.
Adding to that the two different deallocated instance messages, we know there are asynchronous events occuring after the death of some objects that trigger the random crash you are having.
As ggfela suggested, you should make sure to nil out the delegates you have connected to the OpenTok framework. I strongly suggest you do that in the dealloc method as we want to make sure that after that point, no one has any dangling references to your object :
- (oneway void)dealloc
{
self.session.delegate = nil;
self.publisher.delegate = nil;
self.subscriber.delegate = nil;
}
Another odd thing in the code is that your handler for sessionDidConnect: creates a new dispatch_queue every time it is being called in order to call doPublish:. This means that you have concurrent threads sharing the SROpenTokVideoHandler instance which makes it prone to race conditions.