Cocoa Pods App not working in BGAppRefreshTask

I have an app which uses Cocoa Pods to use MQTTClient in connecting to io.adafruit. I use the app to get the various data point stored at io.adafruit and to even send data to adafruit to control a connected unit. I am using .xcworkspace to upload the app on my phone and everything works fine. I am hoping to wake my app every so ofter to get some data points from io.adafruit and if certain conditions exist, I want to send a notification to the user. I have added Background Modes to my app's Signing & Capabilities (Background fetch) and in my INFO I have Permitted background task scheduler identifiers with the String identifier LEVELIT.Refresh. The following is my code I am using in my AppDelegate.h

#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#import <MQTTClient/MQTTClient.h>
#import <UserNotifications/UserNotifications.h>

@interface AppDelegate: UIResponder <UIApplicationDelegate, UNUserNotificationCenterDelegate,UIAlertViewDelegate,MQTTSessionDelegate>{
    MQTTSession *session3;
}
@property  (nonatomic, retain) MQTTSession *session3;
@property (nonatomic, strong) NSPersistentContainer *persistentContainer;
@end

And this is the code I use in my AppDelegate.m

#import "AppDelegate.h"
#import <BackgroundTasks/BackgroundTasks.h>
#import <CloudKit/CloudKit.h>
#import <UserNotifications/UserNotifications.h>

static NSString* TaskID = @"LEVELIT.refresh";
@interface AppDelegate()
@property  (nonatomic, retain) NSString *myUserName;
@property  (nonatomic, retain) NSString *myUserKey;
@property  (nonatomic, retain) NSString *myTrips;
@property  (nonatomic, retain) NSString *atrip;
@property  (nonatomic, retain) NSString *trip;
@property  (nonatomic, retain) NSString *gettrip;
@property(strong) void (^expirationHandler)(void);
@end

@implementation AppDelegate
@synthesize session3,window;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
if (@available(iOS 13.0, *)) {
        [[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:TaskID
                                                              usingQueue:nil
                                                           launchHandler:^(BGAppRefreshTask *task) {
            
            [self handleAppRefreshTask:task];
        }];
    } else {
        // Fallback on earlier versions
    }
    
    UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
    [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UNAuthorizationOptionSound)
       completionHandler:^(BOOL granted, NSError * _Nullable error) {
        
    }];
    UNNotificationCategory* generalCategory = [UNNotificationCategory
         categoryWithIdentifier:@"GENERAL"
         actions:@[]
         intentIdentifiers:@[]
         options:UNNotificationCategoryOptionCustomDismissAction];
    
    NSUserDefaults *defaults2 = [NSUserDefaults standardUserDefaults];
    if([[[defaults2 dictionaryRepresentation] allKeys] containsObject:@"myUserNameFile"]){
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        self.myUserName =  [defaults objectForKey:@"myUserNameFile"];
        NSLog(@"myUserName1:%@",self.myUserName);
        self.myUserKey =  [defaults objectForKey:@"myUserKeyFile"];
        NSLog(@"myUserKey1:%@",self.myUserKey);
      
    }
    return YES;
}
}
-(void)schedualLocalNotifications{
    
}
- (void)setTaskCompletedWithSuccess:(BOOL)success;{
    NSLog(@"Completed");
}



- (void)applicationDidEnterBackground:(UIApplication *)application
{
    
    NSLog(@"Entering background");
    
 if (@available(iOS 13.0, *)) {
        BGAppRefreshTaskRequest *request = [[BGAppRefreshTaskRequest alloc] initWithIdentifier:TaskID];
        
        request.earliestBeginDate = [NSDate dateWithTimeIntervalSinceNow:15*60];
        
        NSError *error;
        BOOL success = [[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error];
        
        if (success == TRUE) {
          
        }
       
       
    }
 //BREAKPOINT 
}


-(void)handleAppRefreshTask:(BGAppRefreshTask *)task  API_AVAILABLE(ios(13.0)){
    //do things with task
  NSLog(@"Process started!");
    task.expirationHandler = ^{
        NSLog(@"WARNING: expired before finish was executed.");
 };
    MQTTCFSocketTransport *transport = [[MQTTCFSocketTransport alloc] init];
    transport.host = @"io.adafruit.com";
    transport.port = 1883;
    
    self.session3 = [[MQTTSession alloc] init];
    self.session3.userName = self.myUserName;
    self.session3.password = self.myUserKey;
    self.session3.transport = transport;
    self.session3.delegate = self;
    self.session3.keepAliveInterval = 30;
    // new stuff
    NSString *feeds = [self.myUserName stringByAppendingString:@"/feeds/"];
    self.atrip = [feeds stringByAppendingString:@"trip"];
    self.gettrip = [self.atrip stringByAppendingString:@"/get"];
   
   [session3 connectWithConnectHandler:^(NSError *error) {
       
            if(!error){
               [self.session3 subscribeToTopic:self.atrip atLevel:1 subscribeHandler:^(NSError *error, NSArray *gQoss){
            if (error) {
                NSLog(@"Subscription failed %@", error.localizedDescription);
            } else {
                NSLog(@"Subscription sucessfull! Granted Qos: %@", gQoss);
                NSData* data = [@"0" dataUsingEncoding:NSUTF8StringEncoding];
                [self.session3 publishData:data onTopic:self.gettrip retain:NO qos:0 publishHandler:nil];
        }
        }];
                
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(45* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    [task setTaskCompletedWithSuccess:YES];
                });
               self.session3.keepAliveInterval =30;
                }
            else {NSLog(@"[connectWithConnectHandler]Error Connect %@", error.localizedDescription);}
     }];
  task.expirationHandler = ^{
    NSLog(@"WARNING: expired before finish was executed.");
};
} }
}

//More code here but too much.
 @end

I am not sure about the local notification because the BGAppRefreshTask is not working.

I have placed a breakpoint just after the app enters background and the TASK is submitted. Then I type in e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"LEVELIT.refresh"]

The app launches in the background but I get an error

error: Header search couldn't locate module MQTTClient
error: couldn't install checkers, unknown error
Message from debugger: Terminated due to signal 9

When I tried running the app without .xcworkspace the project said it couldn't find MQTTClient. But with the workspace everything works fine.

Why would launching the app in the background cause the app not to locate the MQTTClient libraries? And how do I work around this problem?

Any and all help would be appreciated.

Replies

Are you testing on a device? Or in the simulator?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

On a device. I am not sure about something. When I put an app into the background and then flip it up to remove it from background is this terminating the app so it will not call BGAppRefreshTask? To get BGAppRefreshTask to be called do I need to put it in background mode and let Apple shut it down after a period of time?

When I put an app into the background and then flip it up to remove it from background is this terminating the app so it will not call BGAppRefreshTask?

Removing an app from the multitasking UI will, in general, prevent the app from running in the background. The system interprets that gesture as a strong indication from the user that they don’t want the app to run.

To get BGAppRefreshTask to be called

A normal user clears the above state by launching the app from the Home screen.

That does not mean that you can expect you app refresh tasks to be called promptly thereafter. I talked about this misconception in iOS Background Execution Limits.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

My desired result was to monitor my pool filler and notify the user if for some reason it doesn't shut off and overfills. Any suggestions?

My desired result was to monitor my pool filler

That’s not really feasible to do directly from iOS.

The best way to solve this problem is to stand up some separate hardware that connects to the accessory with MQTT and, when the state changes, sends a push notification to your app. That hardware would be mains-powered, which offloads the energy impact of this task from your battery-powered iPhone.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Yes I am trying to move from io.adafruit MQTT to my own server MQTT so I can do push notifications, or txt messages if circumstances arise. Thank you. Any idea of a good free MQTT Broker for mac would be.

Any idea of a good free MQTT Broker for mac would be.

I don’t have direct experience with such things.

In cases like this I usually defer to the (hilariously named) “Are we server yet?” website.

https://www.areweserveryet.org/

Sadly, it doesn’t specifically address MQTT. But I definitely remember seeing a server-side MQTT library based on SwiftNIO. If you search the ’net for MQTT SwiftNIO you’ll find some hits. If you get stuck, the above-mentioned website has a bunch of links to server-side Swift support resources, including Swift Forums but also some real-time stuff. My experience is that that community is very welcoming.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"