In my project I am using the C-based XPC API, since NSXPCConnection is not available on the platform I am targeting. Currently I use a weak reference to prevent the connection handler block from retaining self, as follows:
__block VTVoltControllerProxy *proxy = self;
xpc_connection_set_event_handler(_connection, ^(xpc_object_t object) {
xpc_type_t type = xpc_get_type(object);
...
if (type == XPC_TYPE_ERROR && object == XPC_ERROR_CONNECTION_INVALID) {
if ([[proxy delegate] respondsToSelector:#selector(voltControllerDidDisconnectFromHost:)]) {
[[proxy delegate] voltControllerDidDisconnectFromHost:proxy];
}
}
});
However, an issue is introduced whenever the connection is cancelled inside the -dealloc method of my class:
- (void)dealloc
{
...
xpc_connection_cancel(_connection);
xpc_release(_connection);
...
}
Because cancelling an XPC connection is an asynchronous operation, the connection handler is called after the class instance has already been deallocated, causing proxy to point to an object that no longer exists.
Is there a way that I can safely cancel the connection in -dealloc and have the connection handler call the delegate method after cancellation?
You should be able to change the event handler to point at an event handler which is only used for the purpose of watching that the connection closes. You can either queue the pending connections in another object (perhaps a global or static) or just make the assumption that any connection calling this separate event handler is being called because it is being cancelled (check the event type of course).
Running into the same problem today. I don't know if you already resolved this or not. But what if dealloc waits for the XPC connection to be closed before continue.
It's possible to introduce a conditional variable to achieve this behavior.
But I am wondering what the drawback it could bring.
Related
I use an NSURLConnection to download a file and when the user taps back I don't necessarily know if the connection is finished and has been properly disposed. So I added the following check where if the connection is not null, cancel it.
if (self.urlConnection){
[self.urlConnection cancel];
}
This worked in iOS 7/8 and I never once received an exception. But now in iOS 9 when I do the check to see if the connection exists (and it doesn't) I get an exception. This is the first line above, before I have actually made a call to the url.
I don't understand why checking to see if an object is nil would ever throw an exception and if it does - how can I be expected to guard against this exception.
Is there a new way to make sure an object exists and hasn't been released before I make a call to it?
Edit: This is how the property is declared:
#property (nonatomic, assign) NSURLConnection *urlConnection;
If the object hasn't actually been instantiated yet, the check works fine. It's only when the connection finishes and becomes nil and then I try to check if it is nil that the check explodes. This wasn't happening on earlier iOS versions.
Yes, there is ... use a weak reference.
__weak typeof(self) weakSelf = self;
...
..
.
[weakSelf.urlConnection cancel]
NSURLConnection is an object and needs to be a strong reference. Assign is only appropriate for primitive values. If you really intended to make this property weak, then use this pattern to ensure that your object does not go out of scope while using it:
//code that goes in some method
NSURLConnection* strongConnection = self.urlConnection;
if(strongConnection) {
//code to do stuff with the url connection
[strongConnection cancel];
}
I also urge caution with weak references particularly when you have said object handling notifications or observing a change. I've been bitten more than once with the weak object getting destroyed and the notification or observer does not get unregistered.
consider the following method snippet.
- (void) closeSocket {
...
dispatch_async(dispatch_get_main_queue(), ^{
// last message before actual disconnection:
[self.connectionListenerDelegate connectionDisconnected:self];
}
self.connectionListenerDelegate = nil;
...
}
This method of my "socket" implementation class can be called by external object, in some arbitrary thread (main, or other). I wish to only notify my delegate once, on the main thread, and remove the delegate so that other background events and possible incoming data won't reach it.
In other words, I want to make sure connectionDisconnected: is the last call from the socket to the delegate.
I know code blocks capture their environment's variables etc. But will the block capture and retain the self.connectionListenerDelegate when created?
If closeSocket is being called on some background thread, and dispatches the connectionDisconnected: asynchronously on the main thread, and I nullify my weak reference to my delegate right away - maybe the block will have a nil object and won't send its message?
What is the right way to go about this?
I guess I could use the old
[self.connectionListenerDelegate performSelectorOnMainThread:#selector(connectionDisconnected:) withObject:self waitUntilDone:NO];
which retains both the receiver and parameter object (self), but I prefer GCD dispatch_async and I'd like to better understand blocks.
If closeSocket is being called on some background thread, and dispatches the connectionDisconnected: asynchronously on the main thread, and I nullify my weak reference to my delegate right away - maybe the block will have a nil object and won't send its message?
I think that after self.connectionListenerDelegate = nil; runs, all methods in the dispatch method will get the nil reference when accessing the connectionListenerDelegate.
So, the best way to go about this would be to transfer the delegate reference to a temporary object for use inside the block:
id<ConnectionListenerDelegate> *tempDelegateRef = self.connectionListenerDelegate;
dispatch_async(dispatch_get_main_queue(), ^{
// last message before actual disconnection:
[tempDelegateRef connectionDisconnected:self];
}
self.connectionListenerDelegate = nil;
I'm not sure if you need strong/weak references or something like that.
My first question is how do I get the didAcceptConnectionWithInputStream:outputStream: callback in NSNetServiceDelegate to get called?
Follow up question: can I still establish a connection between a client and server, although I never get a callback saying that a connection was accepted (via didAcceptConnectionWithInputStream:outputStream:)?
I understand that calling publishWithOptions, while passing in the NSNetServiceListenForConnections option is supposed to result in the NetServiceDelegate callback (didAcceptConnectionWithInputStream:outputStream:) to be called. However, that callback is not getting called.
Here are the steps I am taking, to publish:
Create NSNetService with
self.netService = [[NSNetService alloc] initWithDomain:#""
type:_serviceType
name:(_name == nil) ? #"" : _name
port:0];
Schedule service in current runloop, in default mode
Set the delegate to my Server wrapper object
call publishWithOptions:NSNetServiceListenForConnections
Here are the steps I take, to browse services:
Create an NSNetServiceBrowser, and set its delegate to my client wrapper object
Call searchForServicesOfType for the same service type and domain as NSNetService
List services in a UITableView for the UI, to allow a user to select a service
When a user selects a service, set the service's delegate to my client object, and call getInputStream:outputSteam: on the service
After getInputStream:outputSteam: returns success, I would expect didAcceptConnectionWithInputStream:outputStream: to get called. However this does not occur.
Thanks for your help!
The problem is that didAcceptConnectionWithInputStream:outputStream: must be called from the side accepting the connection.
Once the service is available, you call get the streams
[service getInputStream:&istream outputStream:&ostream]
Once this happens on the side receiving the request the delegate method
- (void)netService:(NSNetService *)sender didAcceptConnectionWithInputStream:(NSInputStream *)inputStream outputStream:(NSOutputStream *)outputStream
will be called
In my experience, it is not the act of calling getInputStream:outputStream: on the client that causes didAcceptConnectionWithInputStream:outputStream: to be called on the server.
On the client, after calling getInputStream:outputStream:, your client then needs to call [inputStream open] and [outputStream open] before the didAcceptConnectionWithInputStream:outputStream: will be called.
It's all a part of lazy initialization.
Calling getInputStream:outputStream: will give you back two perfectly good NSStreams ready to use. So, say, you want to write some data? First, open the write stream...
BAM! netService:didAcceptConnectionWithInputStream:outputStream: is called.
The issue has been discussed here and here, but I wonder if there is a more solid way to solve this whether you have delegates or not - when a function is called after a delay.
At a certain point in a program, at a button push, an object - a CCLayer - is created. That layer creates several objects, some of them at callbacks. That created object layer has a "back" button which destroys it. I am running into a problem when the callbacks, etc are triggered AFTER that object is destructed and try to access objects that don't exist anymore - where the "message sent to deallocated instance 0x258ba480" gives me this good news. How do I avoid that?
1) Is there a way to kill the callbacks (because I obviously don't need them anymore)
2) should/can I test for the existence of these possibly non-existent objects at the callbacks themselves
3) something else?
(My callback is code for checking for an internet connection that I copied from this illustrious website - may it live long and prosper-, using Reachability, and I could solve the problem by simply moving it to the main view and setting a flag on the child view, but I don't want to.)
- (void)testInternetConnection
{
internetReachableFoo = [Reachability reachabilityWithHostname:#"www.google.com"];
// Internet is reachable
internetReachableFoo.reachableBlock = ^(Reachability*reach)
{
// Update the UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Yayyy, we have the interwebs!");
//I do the net stuff here
});
};
// Internet is not reachable
internetReachableFoo.unreachableBlock = ^(Reachability*reach)
{
// Update the UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Someone broke the internet :(");
noNetMessageLabel.visible=true; //<-------this goes kaboom
noNetFlag=true;
});
};
[internetReachableFoo startNotifier];
}
There are basically two ways to avoid deallocated delegates from being messaged:
Hold onto the objects you want to message later. That way they won’t get deallocated. This is the case with block callbacks – if a block references some object, the object gets retained until the block ceases to exist. If you message some objects from a block and hit a deallocated object, you must have screwed up the memory management somewhere.
Clear the delegation link before you release the delegate. Nowadays this is usually done using weak, zeroing properties that are automatically set to nil when the referenced object is deallocated. Very convenient. Not your case.
You might consider several options:
First, you may just check for existence of an object before passing message to it:
if (noNetMessageLabel)
noNetMessageLabel.visible = true;
But personally I consider that as a bad architecture.
More wise decision, from my point of view, would be move the code of displaying any alert regarding internet connectivity to the model.
Create method like this in AppDelegate or in the model:
- (NSError*)presentConnectivityAlert
{
if () //any error condition checking appropriate
[[NSNotificationCenter defaultCenter]
postNotificationName:#"connectivityAlert"
object:self
userInfo:userInfo];
}
Also you may consider moving internet checking code to the model too.
In the ViewControllers of your app implement listening to this notification.
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(didReceiveRemoteNotification:)
name:#"connectivityAlert"
object:nil];
}
- (void)viewDidUnload {
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:#"connectivityAlert"
object:nil];
}
-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo {
if (self.isViewLoaded && self.view.window) {
//Present the user with alert
}
}
Thus you have more general and quite versatile approach to handle connectivity issues throughout all your application.
Is there a way to kill the callbacks
It's not possible to cancel block (in your case), but it's possible to cancel NSOperation in NSOperationQueue. But that will require to rewrite your implementation of Reachability.
I have a CTCallCenter instance created (alloc/init) after launch. The event handler is never called (neither an NSLog nor a breakpoint fire). If I ask it later about current calls it returns nil. If I call [[[CTCallCenter alloc] init] currentCalls] at the same time then it does return the calls.
As an example while a call is active (I brought the app back to the foreground and a timer later fired) here is a single NSLog showing the long lived CTCallCenter instance, what it returns for currentCalls and what a new transient instance returns:
CTCallCenter(0x1e5639c0): {
server_connection: 0x1e5640e0 currentCalls: {(
)} callEventHandler: 0xa0c90
}
(null)
{(
CTCall (0x1e5a0fb0) {
callState: [CTCallStateIncoming]
Call ID: [1EB2A082-4A12-48C2-A76C-2244F8F402EE]
}
)}
It is apparent there is a handler registered, and that the long lived instance thinks there are no calls. I'm happy to always use a transient instance to get the call list, but I really need the event handler to fire.
I have found the answer. The [[CTCallCenter alloc] init] must be run in the main queue. I was running it in a custom serial dispatch queue. Presumably the CTCallCenter code is doing something with the current queue or perhaps requiring a run loop.
If you do the alloc/init outside of the main queue then it will be correct if immediately called for currentCalls but that data never gets updated nor does the event handler fire. There is no mention of this in the documentation, nor are errors returned or exceptions raised.