How to detect memory warnings in iOS App Extension - ios

I'm writing an iOS extension that extends NEPacketTunnelProvider in the NetworkExtension framework released in iOS 9. I'm running into a situation where iOS is killing the extension once hits 6MB of memory used.
In a regular iOS app, there are two ways to detect memory warnings and do something about it. Either via [UIApplicationDelegate applicationDidReceiveMemoryWarning:(UIApplication*)app] or [UIViewController didReceiveMemoryWarning]
Is there a similar way to detect memory warnings within an extension? I've searched up and down the iOS extension documentation but have come up empty thus far.

ozgur's answer does not work. UIApplicationDidReceiveMemeoryWarningNotification is a UIKit event and I haven't found a way to get access to that from an extension. The way to go is the last of these options: DISPATCH_SOURCE_TYPE_MEMORYPRESSURE.
I've used the following code (Swift) in a Broadcast Upload Extension and have confirmed with breakpoints that it is called during a memory event right before the extension conks out.
let source = DispatchSource.makeMemoryPressureSource(eventMask: .all, queue: nil)
let q = DispatchQueue.init(label: "test")
q.async {
source.setEventHandler {
let event:DispatchSource.MemoryPressureEvent = source.mask
print(event)
switch event {
case DispatchSource.MemoryPressureEvent.normal:
print("normal")
case DispatchSource.MemoryPressureEvent.warning:
print("warning")
case DispatchSource.MemoryPressureEvent.critical:
print("critical")
default:
break
}
}
source.resume()
}

I am not very familiar with the extensions API, however my basic hunch says that you can register any of your object as observers of UIApplicationDidReceiveMemoryWarningNotification from within that class:
NSNotificationCenter.defaultCenter().addObserverForName(UIApplicationDidReceiveMemoryWarningNotification,
object: nil, queue: .mainQueue()) { notification in
print("Memory warning received")
}

Related

EXC_BAD_ACCESS when access != nil static var

Context: The app needs to be connected to a bluetooth device 100% of the time. While using the app, the CBPeripheral object is stored in a static variable for use throughout the user's session in app.
//This is in AppDelegate class.
static var connectedDevice: CBPeripheral? = nil
.........
class BluetoothScanner : NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
//(...)
func connectToDevice(peripheral: CBPeripheral)
{
AppDelegate.connectedDevice = peripheral
//(...)
}
}
At the start of the app, access operations to that variable occur in a loop. The first accesses to the variable are correct. But always, before ending the loop (at about 40 access to var), it happens EXC_BAD_ACCESS
if(AppDelegate.connectedDevice != nil)
{
//Thread 3: EXC_BAD_ACCESS (code=2, address=0x16ec8ff28)
print("Device debug: " + AppDelegate.connectedDevice!.debugDescription)
}
The problem occurs when calling 'debugDescription' but the same problem occurs when calling any other function of 'connectedDevice'
AppDelegate.connectedDevice is only accessed (in that period) it is never modified, nor released.
I have tested the app for two weeks very thoroughly and without problems. But when uploading it to the AppStore it indicated that I needed to update the XCode to the latest version in order to upload compilation to the AppStore. This problem has appeared without touching the code, only updating the XCode. I have even tested (before updating XCode) that this object is not released during hours of use of the app.

How to ignore iOS VoIP notification if call has already ended?

We're developing a video calling application and rely on APNS VoIP notifications. Due to our design it sometimes happens that the VoIP notification arrives to the device when the call has already ended or the recipient has declined it (missed call for example).
The problem with that approach is that iOS requires you to report all incoming VoIP notifications in some way - either that there's new incoming call or the current call has been updated.
Is there any way to ignore the unnecessary/redundant VoIP notification? The current approach I came up with is really nasty i.e. first I report new unknown incoming call and then immediately I report its end. This causes the native call UI to be shown for a brief moment.
private var provider: CXProvider?
private var uuid = UUID()
//...
func ignorePushNotification() {
self.provider?.reportNewIncomingCall(with: self.uuid, update: CXCallUpdate(),
completion: { error in
// ignore
})
self.provider?.reportCall(with: self.uuid, endedAt: nil, reason: reason)
}
Unfortunately, there isn't a better way to ignore a VoIP Push. But I suggest you to improve the code as follows.
func ignorePushNotification() {
provider?.reportNewIncomingCall(
with: self.uuid,
update: CXCallUpdate(),
completion: { error in
self.provider?.reportCall(with: self.uuid, endedAt: nil, reason: .failed)
})
}
Given the asynchronous nature of CallKit, if you don't do that, it could happen that the end of the call executes before the reportNewIncomingCall. It's probably very rare but it could happen.

Chromecast Sleep/Background issue in iOS app

I am facing a very big issue while using Chromecast in my application. I am using normal GCKUICastButton to connect to the Chromecast. The video plays well.
I am using the default Cast receiver for my application. When the device goes to sleep, sometimes, Chromecast stops and sometimes, after sleep mode, the device disconnects after some time. After going through lot of forums and Stack Overflow questions, I implemented the below code
extension GCKSessionManager {
static func ignoreAppBackgroundModeChange() {
let oldMethod = class_getInstanceMethod(GCKSessionManager.self, #selector(GCKSessionManager.suspendSession(with:)))
let newMethod = class_getInstanceMethod(GCKSessionManager.self, #selector(GCKSessionManager.suspendSessionIgnoringAppBackgrounded(with:)))
method_exchangeImplementations(oldMethod, newMethod)
}
func suspendSessionIgnoringAppBackgrounded(with reason: GCKConnectionSuspendReason) -> Bool {
guard reason != .appBackgrounded else { return false }
return suspendSession(with:reason)
}
}
Then in my code I wrote the below line
GCKSessionManager.ignoreAppBackgroundModeChange()
Now suddenly, the Chromecast does not disconnect however after few minutes of sleep, it disconnects as well as, kill the app as well. How can I retain the Chromecast play session even if the device goes to sleep or goes to background.
As I am using GCKUICastButton so I am not using the GCKDeviceManager so I am unable to use the ignoreAppStateNotification using GCKDeviceManager, can you advice if I can use that as well.
I have also added the GCKCastOption code as well in AppDelegate.

AVPlayer removing observer crash in Swift 2.2

I have a video app that I built a while back in Swift 1 and I've been trying to migrate to Swift 2.2. It all (finally) works apart from a weird crash to do with observers.
func removeObservers()
{
print("REMOVING OBSERVERS")
if ( !self.is_image && self.player != nil ) {
if (self.player?.observationInfo != nil) {
self.player?.removeObserver(self, forKeyPath: "currentItem.status")
self.player?.removeObserver(self, forKeyPath: "readyForDisplay")
}
}
NSNotificationCenter.defaultCenter().removeObserver(self)
}
This worked previously using SwiftTryCatch but with the lines in place crashes with "'Cannot remove an observer for the key path "readyForDisplay" from because it is not registered as an observer.'" OR with that an observer is registered on a deallocated object if I comment it out.
If I add a do { } catch {} to it I get an error that "this does not throw" and it just crashes the same. How do I go about putting this in some form of try-catch format?
In Swift 2, the libs got annoyingly strict about errors that are truly unexpected (which throw) versus errors that the programmer could have prevented (which do not throw, but just crash your app).
(I’m not a fan of this distinction, or at least not of all the specific decisions Apple made about which errors fall in which category. The JSON API verges on the nonsensical in this department. But…we work with the API we’ve got.)
The NSKeyValueObserving docs say:
It is an error to call removeObserver:forKeyPath: if the object has not been registered as an observer.
“It is an error” is Apple code for “you are responsible for never doing this, and if you do, your app will crash in an uncatchable way.”
In these situations, there is usually an API call you can make to check the validity of the thing you’re about to do. However, AFAIK, there’s no KVO API call you can make to ask, “Is X observing key path Y of object Z?” That means you have three options:
Figure out why you’re trying to remove an observer from something you’re not observing, and prevent that using your program’s own internal logic.
Keep a weak instance var for “player I’m observing,” and check that for a match before attempting to remove the observer.
Add self as an observer before removing it. (I’m pretty sure that a redundant add is OK.)
Since you are making a call removeObserver(self) at the end of the method, why cant you uncomment above code? Because removeObserver(self) removes all the observers if registered any. I hope this solves your issue.
NSNotificationCenter.defaultCenter().removeObserver(self)
status is a property of either AVPlayer or AVPlayerItem.
readyForDisplay is a property of AVPlayerLayer

UI Tests + postNotificationName + never reaches observer + Xcode 7

I have UI Tests target for testing MyApp. To test specific MyApp conditions I need to post notifications from UI Test target to MyApp target. To post notification from UI Test target function I am using this:
NSNotificationCenter.defaultCenter().postNotificationName(name, object: nil, userInfo: aUserInfo)
It looks that this notification never reaches observer from UI Test target, but it works fine when posting this notification from MyApp target.
How to post notification from UI Target to MyApp target?
Using Xcode 7.
Have similar problem (trying to ensure NSNotification is being posted after certain UI action). Did small research on this.
NSNotification not being received because UI test and app are running in different processes. NSNotification cannot go through process bounds, and NSDistributedNotificationServer is not available on iOS. So, currently there is no default and easy way to post NSNotifications between UI test suite and app instance.
However, there is some ways to communicate between processes, and maybe write small wrapper or even NSDistributedNotificationCenter iOS surrogate for testing purposes. Check out this great article from Realm: https://academy.realm.io/posts/thomas-goyne-fast-inter-process-communication/
I wanted to use UI Testing to test for memory leaks and to do this I wanted to get informed in the UI test case when ever a view controller's deinit is called. So I came up with this to provide the IPC mechanism:
/**
Provides simple IPC messaging. To use this class you need to include
#include <notify.h>
in your bridging header. (Ab)uses UIPasteboard for message delivery, ie.
should not be used in production code.
*/
public class SimpleIPC {
/// Event notification name for libnotify.
private static let notifyEventName = "com.foo.SimpleIPC"
/// libnotify token.
private var token: Int32 = 0
/// Starts listening to the events
public func listen(callback: (String? -> Void)) {
notify_register_dispatch(SimpleIPC.notifyEventName, &token, dispatch_get_main_queue()) { token in
callback(UIPasteboard.generalPasteboard().string)
}
}
public class func send(message: String) {
UIPasteboard.generalPasteboard().string = message
notify_post(SimpleIPC.notifyEventName)
}
deinit {
notify_cancel(token)
}
}
It uses a combination of libnotify and UIPasteboard for the notification + data delivery. Usable for 1-way communication as is, for 2-way either make the payload include a sender token or use 2 instances with parametrized libnotify event names.
Matti
Is this for a Mac app or an iOS app? For a Mac app you can use NSDistributedNotificationCenter as such.
In your subscriber:
NSDistributedNotificationCenter.defaultCenter().addObserver(object, selector: #selector(ObjectsClass.myFuncWithoutParentheses), name: "uniqueString", object: nil)
In your publisher:
NSNotificationCenter.defaultCenter().postNotificationName("uniqueString" object:nil)

Resources