Long lived CTCallCenter instance shows no calls and never calls event handler - ios

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.

Related

URLSessionWebSocketTask.receive() doesn't throw on cancel()

I am repeatedly calling URLSessionWebSocketTask.receive. I've tried recursively calling the version with the completion handler, as well as looping over the new async version.
At some point, I'm not receiving any more messages, and I want to cancel the web socket. So I call URLSessionWebSocketTask.cancel(). In the log, I see the message [websocket] Read completed with an error Operation canceled. But the completion handler is never called/the async receive method never returns. This means that the Task I launched to do the receiving never closes.
Why doesn't the completion handler get called when the web socket is cancelled?
Are you actually seeing a leak with the closure-based approach?
I see a leak when using the async version, but if I use the closure-version the URLSessionWebSocketTask-object is freed. I don't get any call to the closure, but for my use case that does not matter.
If I use the delegate on the session I will get a callback when the websocket is closed, but I have to call session.invalidateAndCancel when cleaning up.
I also needed to add [weak self] to the closure since I call my receive-function recursively, capturing self.

Result of calling performSelector: on a "sleeping" thread's current run loop?

I asked myself a question, but have no answer yet so I hope you could have one:
What happened if there is a NSThread having his current NSRunLoop(named runLoop1 for example) in "sleeping" state (well, runLoop1 called [NSThread sleepForTimeInterval:/*...*/]) while an other NSThread is calling [self performSelector:#selector(selector:) onThread:runLoop1 withObject:nil waitUntilDone:NO]?
I hope I'm understandable ^^
The runloop can be explained as an infinite loop:
while(!exit) {
// Do stuff here
}
This runs on a thread and if this thread is in sleep then so is the loop and no events will be called on it.
So then what are performSelector methods:
Imagine there is an array of invocations with that loop which will be executed when appropriate. Since there is a method to perform the selector after a delay there is also a time stamp.
while(!exit) {
NSMutableArray *notExecuted = [NSMutableArray new];
for(Executable *item in [self.pendingExecutables copy]) {
if(item.executionDate && [item.executionDate compare:[NSDate date]] == NSOrderedDescending) {
[notExecuted addObject:item];
}
else {
[item execute];
}
}
self.pendingExecutables = notExecuted;
}
So calling performSelector really does nothing but adds the data needed to perform it into some array. The runloop must be running for the execution to actually happen. So in your case nothing happens, the selector will not be performed because the loop is not executing since the whole thread is sleeping.
You can then also understand what happens if you block the main thread. No touch events, no system notifications, no nothing. It is all just kept in array and it will be called once the thread is unblocked and another loop occurs. The main loop also sends the date on each cycle which is then used for the watch dog. Since the OS works on another thread then your application it is the OS that will check that date and if it is relatively old you get to "application not responding" state. And then the OS may decide to kill your application.
Note that this is oversimplified but it is enough to get a basic understanding on how these things work.

CTCallCenter in Swift

I'm trying to use CTCallCenter in Swift, however it always displays error.
I suppose it may cause in how to use closure but actually I don't familiar about it.
Does anybody have idea to resolve this issue?
Here is my code
import CoreTelephony
class ViewController: UIViewController{
var callCenter:CTCallCenter = CTCallCenter()
override func viewDidLoad() {
callCenter.callEventHandler(call:CTCall) -> Void in{
//will get CTcall status here
}
}
}
There are three errors.
1, Braced block of statements is an unused closure
2, Expected expression
3, Consecutive statements on a line must be separated by ";".
I tried to change as it indicated but any ways are not correct.
Thanks in Advance!
I got this working using the following code:
import CoreTelephony
class SomeClass: UIViewController {
private var callCenter = CTCallCenter()
override func viewDidLoad() {
super.viewDidLoad()
callCenter.callEventHandler = { (call:CTCall!) in
switch call.callState {
case CTCallStateConnected:
println("CTCallStateConnected")
self.callConnected()
case CTCallStateDisconnected:
println("CTCallStateDisconnected")
self.callDisconnected()
default:
//Not concerned with CTCallStateDialing or CTCallStateIncoming
break
}
}
}
func callConnected(){
// Do something when call connects
}
func callDisconnected() {
// Do something when call disconnects
}
}
Hope it helps.
From the Apple documentation :
Responding to Cellular Call Events
Dispatched when a call changes state.
Declaration:
var callEventHandler: ((CTCall!) -> Void)!
Discussion:
This property’s block object is dispatched on the default priority global dispatch queue when a call changes state. To handle such call events, define a handler block in your application and assign it to this property. You must implement the handler block to support being invoked from any context.
If your application is active when a call event takes place, the system dispatches the event to your handler immediately. However, call events can also take place while your application is suspended. While it is suspended, your application does not receive call events. When your application resumes the active state, it receives a single call event for each call that changed state—no matter how many state changes the call experienced while your application was suspended. The single call event sent to your handler, upon your application returning to the active state, describes the call’s state at that time.
For example, suppose your application changes from the active to the suspended state while a call is in the connected state. Suppose also that while your application is suspended, the call disconnects. When your application returns to the active state, you get a cellular call event indicating that the call is disconnected.
Here is a more complex example. Suppose your application changes from the active to the suspended state after the user has initiated a call but before it connects (that is, your application suspends while the call is in the dialing state). Suppose further that, while your application is suspended, the call changes first to the connected state and then to the disconnected state. When your application returns to the active state, you get a single cellular call event indicating that the call is disconnected.
May be now you can understand how to declare that.

NSRunLoop makes app non-responsive sometimes

In my app, I need to make https calls to a restful web api and process the results upon return. The number of simultaneous service calls is never fixed, hence the related code has been written accordingly. The data fetched from the service is temporarily stored on an SQLite DB within the app. Following is the structure how it works.
When the user navigates to any screen or UI component thereof for which data needs to be fetched, the view controller calls a method on its designated model object. This method then checks whether the data is already present in the DB or it needs to be fetched. In case data is present, it returns the same to the view controller. Otherwise, it initiates an asynchronous service request and waits till the response comes, after which it returns the data to the VC. Therefore, the VC initialises a loading indicator before calling the specified model, and dismisses the same after control is returned from this function.
Here it is important that the function on the model waits till the response is received from the web api. This is done by registering for an NSNotification which will be issued by the service module once returned data is written to the DB. A boolean variable it set to false upon making the service request and set to true once the response is received. An NSRunLoop runs on the false condition of this boolean variable. Hence once the variable is set to true, the rest of the processing can continue.
Following are the relevant pieces of code in which all this is implemented:
[serviceModule initServiceCall:#"25" withDictionary:[NSDictionary dictionaryWithObjects:#[asOfDate] forKeys:#[#"toDate"]]];
dataReady=NO;
NSString *notificationName = #"dataReady";
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(useNotificationFromServiceModule:) name:notificationName object:nil];
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (!dataReady && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
The rest of the function continues after this.
This is the function that handles the notification:
-(void)useNotificationFromServiceModule:(NSNotification *)notification {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
dataReady=YES;
});}
The usual process is that once the notification is sent, the NSRunLoop quits and the rest of the method completes, returning to the view controller which then dismissed the loading indicator. The problem is that sometimes this does not happen. While the notification is issued (I can see the console log), the NSRunLoop does not end. The loading indicator continues to appear on the screen and stays that way until the screen is tapped once. When the screen is tapped, the NSRunLoop ends and the rest of the process continues randomly.
This does not happen always. It happens quite randomly, maybe about 4-5 times out of 10. Kindly provide some inputs/pointers to indicate why this may be happening.
If you are using the run loop directly, you are either very clever or very stupid. In the first case, you'll find the answer yourself. In the second case, it would be much much better if you followed the same pattern as everyone else does, which is running your networking code on a background thread and using dispatch_async when the results arrive.

Cancelling XPC connection in -dealloc when cancellation handler references self

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.

Resources