I have the following scenario: iOS app (peripheral) X OSX app (central)
I instantiate my peripheral manager with CBPeripheralManagerOptionRestoreIdentifierKey.
In my peripheral's didFinishLaunchingWithOptions I send a local notification after getting a peripheral with UIApplicationLaunchOptionsBluetoothPeripheralsKey (don't do anything with it)
In my peripheral's willRestoreState I also trigger a notification (don't do anything other than that)
If my peripheral app is still running in the background before it gets killed due to memory pressure, I get messages from the OSX central just fine.
After the iOS app gets killed, when OSX central sends a message, both notifications mentioned above come through on iOS, but the message I was actually expecting doens't.
I've not resintantiated my peripheralManager at any moment, where and how should I do it? I only have one peripheralManager for the entire cycle of my app.
Any suggestions are welcome.
UPDATE:
if do
let options: Dictionary = [CBPeripheralManagerOptionRestoreIdentifierKey: "myId"]
peripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: options)
in willRestoreState, my apps just lose connection
Right, after having re-viseted all topics on this matter 100 times I've finally figured it out, here is exactly how it should be implemented:
in AppDelegate's didFinishLaunchingWithOptions:
if let options = launchOptions {
if let peripheralManagerIdentifiers: NSArray = options[UIApplicationLaunchOptionsBluetoothPeripheralsKey] as? NSArray {
//Loop through peripheralManagerIdentifiers reinstantiating each of your peripheralManagers
//CBPeripheralManager(delegate: corebluetooth, queue: nil, options: [CBPeripheralManagerOptionRestoreIdentifierKey: "identifierInArray"])
}
else {
//There are no peripheralManagers to be reinstantiated, instantiate them as you normally would
}
}
else {
//There is nothing in launchOptions, instantiate them as you normally would
}
At this point, willRestoreState should start getting called, however, there will be no centrals in your array of centrals if you have one to manage all centrals subscribed to your characteristics. Since all my centrals are always subscribed to all of my characteristics in one single service I have, I simply loop though all subscribedCentrals in any of the characteristics re-adding them to my array of centrals.
Please modify this according to your needs.
In willRestoreState:
var services = dict[CBPeripheralManagerRestoredStateServicesKey] as! [CBMutableService]
if let service = services.first {
if let characteristic = service.characteristics?.first as? CBMutableCharacteristic {
for subscribedCentral in characteristic.subscribedCentrals! {
self.cbCentrals.append(subscribedCentral as! CBCentral)
}
}
}
After this you should be ok to call any methods you have prepared to talk to any central, even if you deal with several of them simultaneously.
Good luck!
Related
Apple introduced Activity Tracing to support debugging asynchronous code. I have some difficulties using it properly. My example scenario is a small MacOS app just downloading a file:
- (IBAction)actionDownload:(NSButton *)sender {
os_activity_label_useraction("actionDownload");
os_log_t logDemo = os_log_create("ActivityTracingDemo", "demo");
os_log_debug(logDemo, "actionDownload start (1)");
os_activity_t actTop = os_activity_create(
"HTTP Download",
OS_ACTIVITY_NONE,
OS_ACTIVITY_FLAG_DETACHED);
os_activity_apply(actTop, ^{
os_log_debug(logDemo, "actionDownload start");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(2 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
os_log_debug(logDemo,
"actionDownload two second later");
});
NSURL *url = [NSURL URLWithString:
#"https://www.google.de/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"];
// Get the current activity (or create a new one,
// if no current activity exists):
os_activity_t act = os_activity_create(
"HTTP Response",
OS_ACTIVITY_CURRENT,
OS_ACTIVITY_FLAG_IF_NONE_PRESENT);
NSURLSessionDownloadTask *downloadPhotoTask =
[ [NSURLSession sharedSession]
downloadTaskWithURL:url
completionHandler:^(NSURL *location,
NSURLResponse *response,
NSError *error)
{
os_activity_apply(act, ^{
// Now the same activity is active,
// that initiated the download.
os_log_debug(logDemo, "actionDownload received data (restored activity)");
});
os_log_debug(logDemo, "actionDownload received data");
}];
[downloadPhotoTask resume];
});
}
Filtering for my messages in Console.app I am getting:
Obviously NSURLSession does not call the completionHandler with the same activity active that initiated the download. I had to manually apply that activity within the callback. Is there a better way to do this? I thought that activities are designed to trace things across process. In that case it is not even working inside the same process without doing some extra work.
In the activity view of the Console.appI am getting:
The tree view looks promising, to get a quick overview about what application scenarios are triggered. Initially I thought that it is not
necessary to apply a new activity in an action callback and instead it would be possible to use os_activity_label_useraction to get the scenario displayed in Console.appactivity view on top level. Obviously that's not the case. I can't find that label in any log.
My solution is to create a new detached activity in actionDownload. This activity is visible on top level in the Console.appactivity view. What I dislike with this solution are two things:
First, I had to explicitly create a new activity with a new scope. This adds lots of noise to the source code. I have many very short action methods in my project. Also the messages view works without this. There I am just getting what I am interested in by filtering for subsystem, category and activity id.
Second, the connection to the initiating activity gets lost.
Would be great to get some hints about how to properly use Activity Tracing and especially the hierarchy thingy.
We are in advanced stages of developing a Swift2.2 app and hence have decided to migrate to 2.3 in the interim and do the full Swift 3 migration later. However we are unable to get beacon detection working post conversion to Swift 2.3. The method "didRangeBeacons" keeps returning an empty array. The same code was working in Swift 2.2 so we know we have all the permissions etc in place.
Also if we open the "Locate" app on the same ipad then our app also starts returning data in "didRangeBeacons". Have tried various versions of the apps out there and all Swift2.3 apps are behaving the same way. Can't make out what the Locate app is doing... Anyone on the same boat??
Here is the code we are using. Am not sure this is supposed to be written here or in comments but couldn't put code within comments somehow...
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
let region = CLBeaconRegion(proximityUUID: NSUUID(UUIDString: "9735BF2A-0BD1-4877-9A4E-103127349E1D")!, identifier: "testing")
// Note: make sure you replace the keys here with your own beacons' Minor Values
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.locationManager.delegate = self
self.locationManager.requestAlwaysAuthorization()
self.locationManager.startMonitoringForRegion(self.region)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func locationManager(manager: CLLocationManager, didStartMonitoringForRegion region: CLRegion) {
print("didStartMonitoringForRegion")
self.locationManager.requestStateForRegion(region)
}
func locationManager(manager: CLLocationManager, monitoringDidFailForRegion region: CLRegion?, withError error: NSError) {
print("monitoringDidFailForRegion")
}
func locationManager(manager: CLLocationManager, didDetermineState state: CLRegionState, forRegion region: CLRegion) {
print("didDetermineState")
if state == .Inside {
//Start Ranging
self.locationManager.startRangingBeaconsInRegion(self.region)
self.locationManager.startUpdatingLocation()
}
else {
//Stop Ranging here
self.locationManager.stopUpdatingLocation()
self.locationManager.stopRangingBeaconsInRegion(self.region)
}
}
func locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion) {
print(beacons.count)
}
}
[Update post some more attempts to get this working]
App works in foreground mode if we remove self.locationManager.startMonitoringForRegion(self.region)
and call self.locationManager.startRangingBeaconsInRegion(self.region)
directly after self.locationManager.requestAlwaysAuthorization()
This is sub-optimal because we don't get entry and exit events or state but atleast we are getting beacon counts.
There are a number of anecdotal reports of beacon detection problems on iOS 10. Symptoms include:
Improper region exit events, especially when the app is in the background, followed by entry events if the shoulder button is pressed.
Periodic dropouts in detections of ranged beacons, with callbacks providing an empty beacon list when beacons are in the vicinity.
Ranged beacon callbacks return proper results when a different beacon ranging or detection app is running that targets iOS 9.x.
This is presumably a bug that will be fixed in an iOS update. Until then, some users have reported that setting the app deployment target in XCode to 9.x will resolve the issue.
Try constructing your location manager after the view loads. In other words, change:
let locationManager = CLLocationManager()
to
let locationManager : CLLocationManager!
And then add this to the viewDidLoad:
locationManager = CLLocationManager()
I have seen strange behavior with LocationManager constructed on initialization of a UIViewController.
While I was testing beacon ranging, I downloaded multiple applications that I forgot to uninstall. After I uninstalled all beacon-related applications and re-installed just the needed applications, I was able to get ranging beacons to work.
Many im services automatically display messages once the user on the other end has sent a message.
Right now, the only way I can think of to do this is to use an nstimer which will run the appropriate block of code which fetches the messages and updates the table view. This is resources intensive and can waste one of the requests per second. Is there any way to automate this process and make it happen only when a new message has been sent/received?
Here's an example of using didReceiveRemoteNotification inside of your app delegate to respond to push notifications. In particular, you care about the case where you are receiving the notification while the app is active.
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
if (PFUser.currentUser() == nil) {
return
}
if (application.applicationState == UIApplicationState.Inactive || application.applicationState == UIApplicationState.Background) {
// Received the push notification when the app was in the background
PFAnalytics.trackAppOpenedWithRemoteNotificationPayload(userInfo)
// Inspect userInfo for the push notification payload
if let notificationPayloadTypeKey: String = userInfo["someKey"] as? String {
// Do something
}
} else {
// Received the push notification while the app is active
if let notificationPayloadTypeKey: String = userInfo["someKey"] as? String {
// Use NSNotificationCenter to inform your view to reload
NSNotificationCenter.defaultCenter().postNotificationName("loadMessages", object: nil)
}
}
}
Then you just need to add a listener inside of your view controller. Inside of viewDidLoad add the following which will call the function loadMessages whenever a notification is received.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "loadMessages", name: "loadMessages", object: nil)
If you download the code for Parse's Anypic example project you can see how they handle remote notifications.
I was working through Core Data Stack in Swift - Demystified but when I got to the line
self.context = NSManagedObjectContext()
I got the warning
`init()` was deprecated in iOS 9.0: Use -initWithConcurrencyType: instead
I see that I can do one of the following for self.context =
NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.ConfinementConcurrencyType)
NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType)
NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
but since ConfinementConcurrencyType is also deprecated now that leaves me MainQueueConcurrencyType and PrivateQueueConcurrencyType. What is the difference between these two and how should I choose which one to use? I read this documentation, but I didn't really understand.
You essentially will always have at least 1 context with NSMainQueueConcurrencyType and many contexts with NSPrivateQueueConcurrencyType. NSPrivateQueueConcurrencyType is used typically for saving or fetching things to core data in the background (like if attempting to sync records with a Web Service).
The NSMainQueueConcurrencyType creates a context associated with the main queue which is perfect for use with NSFetchedResultsController.
The default core data stack uses a single context with NSMainQueueConcurrencyType, but you can create a much better app by leveraging multiple NSPrivateQueueConcurrencyType to do any work that does not affect the UI.
Replace these two function with the following one:
lazy var managedObjectContext: NSManagedObjectContext = {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
// MARK: - Core Data Saving support
func saveContext () {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
abort()
}
}
}
I've read Apple's Pasteboard Programming Guide, but it doesn't answer a particular question I have.
I'm trying to write a Cocoa application (for OS X, not iOS) that will keep track of everything that is written to the general pasteboard (so, whenever any application copies and pastes, but not, say, drags-and-drops, which also makes use of NSPasteboard). I could (almost) accomplish this by basically polling the general pasteboard on a background thread constantly, and checking changeCount. Of course, doing this would make me feel very dirty on the inside.
My question is, is there a way to ask the Pasteboard server to notify me through some sort of callback any time a change is made to the general pasteboard? I couldn't find anything in the NSPasteboard class reference, but I'm hoping it lurks somewhere else.
Another way I could imagine accomplishing this is if there was a way to swap out the general pasteboard implementation with a subclass of NSPasteboard that I could define myself to issue a callback. Maybe something like this is possible?
I would greatly prefer if this were possible with public, App Store-legal APIs, but if using a private API is necessary, I'll take that too.
Thanks!
Unfortunately the only available method is by polling (booo!). There are no notifications and there's nothing to observe for changed pasteboard contents. Check out Apple's ClipboardViewer sample code to see how they deal with inspecting the clipboard. Add a (hopefully not overzealous) timer to keep checking for differences and you've got a basic (if clunky) solution that should be App-Store-Friendly.
File an enhancement request at bugreporter.apple.com to request notifications or some other callback. Unfortunately it wouldn't help you until the next major OS release at the earliest but for now it's polling until we all ask them to give us something better.
There was once a post on a mailing list where the decision against a notification api was described. I can't find it right now though. The bottom line was that probably too many applications would register for that api even though they really wouldn't need to. If you then copy something the whole system goes through the new clipboard content like crazy, creating lots of work for the computer. So i don't think they'll change that behavior anytime soon. The whole NSPasteboard API is internally built around using the changeCount, too. So even your custom subclass of NSPasteboard would still have to keep polling.
If you really want to check if the pasteboard changed, just keep observing the changeCount very half second. Comparing integers is really fast so there's really no performance issue here.
Based on answer provided by Joshua I came up with similar implementation but in swift, here is the link to its gist: PasteboardWatcher.swift
Code snippet from same:
class PasteboardWatcher : NSObject {
// assigning a pasteboard object
private let pasteboard = NSPasteboard.generalPasteboard()
// to keep track of count of objects currently copied
// also helps in determining if a new object is copied
private var changeCount : Int
// used to perform polling to identify if url with desired kind is copied
private var timer: NSTimer?
// the delegate which will be notified when desired link is copied
weak var delegate: PasteboardWatcherDelegate?
// the kinds of files for which if url is copied the delegate is notified
private let fileKinds : [String]
/// initializer which should be used to initialize object of this class
/// - Parameter fileKinds: an array containing the desired file kinds
init(fileKinds: [String]) {
// assigning current pasteboard changeCount so that it can be compared later to identify changes
changeCount = pasteboard.changeCount
// assigning passed desired file kinds to respective instance variable
self.fileKinds = fileKinds
super.init()
}
/// starts polling to identify if url with desired kind is copied
/// - Note: uses an NSTimer for polling
func startPolling () {
// setup and start of timer
timer = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: Selector("checkForChangesInPasteboard"), userInfo: nil, repeats: true)
}
/// method invoked continuously by timer
/// - Note: To keep this method as private I referred this answer at stackoverflow - [Swift - NSTimer does not invoke a private func as selector](http://stackoverflow.com/a/30947182/217586)
#objc private func checkForChangesInPasteboard() {
// check if there is any new item copied
// also check if kind of copied item is string
if let copiedString = pasteboard.stringForType(NSPasteboardTypeString) where pasteboard.changeCount != changeCount {
// obtain url from copied link if its path extension is one of the desired extensions
if let fileUrl = NSURL(string: copiedString) where self.fileKinds.contains(fileUrl.pathExtension!){
// invoke appropriate method on delegate
self.delegate?.newlyCopiedUrlObtained(copiedUrl: fileUrl)
}
// assign new change count to instance variable for later comparison
changeCount = pasteboard.changeCount
}
}
}
Note: in the shared code I am trying to identify if user has copied a
file url or not, the provided code can easily be modified for other general
purposes.
For those who need simplified version of code snippet that gets the job done in Swift 5.7,
it just works (base on #Devarshi code):
func watch(using closure: #escaping (_ copiedString: String) -> Void) {
let pasteboard = NSPasteboard.general
var changeCount = NSPasteboard.general.changeCount
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
guard let copiedString = pasteboard.string(forType: .string),
pasteboard.changeCount != changeCount else { return }
defer {
changeCount = pasteboard.changeCount
}
closure(copiedString)
}
}
how to use is as below:
watch {
print("detected : \($0)")
}
then if you attempt copy any text in your pasteboard, it will watch and print out to the console like below..
detected : your copied message in pasteboard
detected : your copied message in pasteboard
in case, full code sample for how to use it for example in SwiftUI:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onAppear {
watch {
print("detect : \($0)")
}
}
}
}
func watch(using closure: #escaping (_ copiedString: String) -> Void) {
let pasteboard = NSPasteboard.general
var changeCount = NSPasteboard.general.changeCount
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
guard let copiedString = pasteboard.string(forType: .string),
pasteboard.changeCount != changeCount else { return }
defer {
changeCount = pasteboard.changeCount
}
closure(copiedString)
}
}
}
It's not necessary to poll. Pasteboard would generally only be changed by the current view is inactive or does not have focus. Pasteboard has a counter that is incremented when contents change. When window regains focus (windowDidBecomeKey), check if changeCount has changed then process accordingly.
This does not capture every change, but lets your application respond if the Pasteboard is different when it becomes active.
In Swift...
var pasteboardChangeCount = NSPasteboard.general().changeCount
func windowDidBecomeKey(_ notification: Notification)
{ Swift.print("windowDidBecomeKey")
if pasteboardChangeCount != NSPasteboard.general().changeCount
{ viewController.checkPasteboard()
pasteboardChangeCount = NSPasteboard.general().changeCount
}
}
I have a solution for more strict case: detecting when your content in NSPasteboard was replaced by something else.
If you create a class that conforms to NSPasteboardWriting and pass it to -writeObjects: along with the actual content, NSPasteboard will retain this object until its content is replaced. If there are no other strong references to this object, it get deallocated.
Deallocation of this object is the moment when new NSPasteboard got new content.