iOS Apns "Best-Effort" Fallback

App architecture when 'state changing' APNS fails

Question1 How should we handle the HEAD request response? (I'm guessing that for the server to be able to route HEAD requests, there must be some changes, but let's just assume that's outside the scope of the question).

You probably don't need to deal with HEAD requests. Using Etags is a standard mechanism which lets you make a GET request and the server can just return an empty body with 304 response if nothing has changed, or the actual new content if something has.

Question2 How often do we have to do this request? Based on this comment one solution could be to set a repeating timer in the viewDidAppear e.g. make a HEAD request every 2 minutes. Is that a good idea?

I think this is reasonable, especially if you want to inform your user when you are unable to make that request successfully. You might also consider using Apple's Reachability code to detect when you can or cannot talk to your server.

Question3 Now let's say we did that HEAD request, but the GET(PassengerInfoModel) is requested from 2 other scenes/viewControllers as well. The server can't differentiate between the different scenes/viewControllers. I'm guessing a solution would be have all our app's network requests managed through a singleton NetworkHandler. Is that a good idea?

Yes, I think having a singleton is reasonable, though I'm not sure why the server cares what view controller is making the request. Like can't they just request different urls?

What are different types of notifications in iOS and how to configure them correctly?

EDIT: While this answer is fully applicable, there are some additions (not changes) to notifications in iOS 12. I highly recommend watching WWDC 2018: What’s New in User Notifications and read this amazing and must read article.

Main changes are:

  • grouped notifications along with summary format
  • provisional notifications ie show notifications directly in notification center without user permission
  • critical notifications which ignore 'do not disturb' or 'mute'
  • ability to interact with the notifications in the extensions
  • ability to completely reset or update actions
  • ability to deeplink into app's notification Settings from phone's Notification Center

IMPORTANT NOTE: Not sure since when but from the Apple docs, the 'silent notification' has been renamed to 'background notification'

There are too many settings that need to be set right for it to work. I'll try to dissect them and make them easier to understand.

Overall, several things are important.

  • the overall difference between a silent and user notification
  • different types of user notifications
  • how a remote notification, i.e. the payload, is configured from your server
  • how to enable push notifications and remote notifications from background modes on your project
  • how to register your token with APNs for remote and silent notifications and APNs architecture
  • how to request permission for user notifications
  • enabling 'background app refresh' and 'notifications' from the device
  • what is content-available
  • understanding that the iOS is upstream to your app when it comes to receiving a remote notification
  • what happens when the OS receives notifications when the app has been user-terminated
  • A note on reliability and APNs architecture

I highly recommend everyone to watch the first 7 minutes of: WWDC 2015: What's new in Notifications. From there, the presenter mentions that there are 2 major types of notifications:

Silent Notifications

They happen in the background, hence you never see any alert/badge/sound. Things get downloaded without you knowing about them.

iOS 11 bug

See here.
iOS 11 initial releases were buggy for silent notifications. Make sure
you have the latest version for your testing, otherwise, it may not
work



User Notifications

Sample Image

As the name says, it has something to do with the user. That is, the user will see an alert/badge or hear a sound. It has 2 types.

Local Notifications

A Local Notification can be triggered in 3 different ways:

  • UNLocationNotificationTrigger:
    You see an alert when you're close to a Walmart store.

  • UNTimeIntervalNotificationTrigger: e.g. You see an alert every 10 minutes.

  • UNCalendarNotificationTrigger like December 1st 1:00PM 2017.

Remote Notifications

They are similar to local notifications but they are triggered from the server, e.g. a WhatsApp message that has a From field (Mom) and a body field (I love you!).

Token registration and APNs architecture:

To receive a silent or remote notification, you need to register for a token using:

application.registerForRemoteNotifications() 

Registering does NOT require user permission. This makes silent notifications to become seamless. See this moment of the WWDC video

Silent notifications are enabled by default. The user does not need
to approve your -- does not give permission to your app to use them,
and you can just start using them without asking the user for
permission.

From WWDC

Remember APNs is delivered to your users by APNs and not by your server. So your iOS code must send this token to your server. So the server can associate a given device token with the user. When you want to push to a certain user, your server just tells APNs to send a payload to a specific token. What's important to understand is that your server and APNs are two different things

The flow of it looks like this:

 

Sample Image

 

  1. server/provider sends a payload to APNs
  2. APNs send a notification to all target devices of a given account. e.g. your iPhone, Mac could both receive notifications for emails/messages.
  3. Then your iPhone/Mac will deliver that message to the app. APNs don't directly send messages to your app. It sends it to the device. Then the iOS sends it to your app.

For more on this see docs APNs Overview and Sending Notification Requests to APNs


To be able to show badges/alerts/sounds, you need to request permission from the user:

UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in

guard error == nil else {
//Display Error.. Handle Error.. etc..
return
}

if granted {
//Do stuff here..

//Register for RemoteNotifications. Your Remote Notifications can display alerts now :)
application.registerForRemoteNotifications()
}
else {
//Handle user denying permissions..
}
}

Question: Do I need to request access once for local notifications and once for remote notifications?

No. Just write the snippet above and it will request access for both remote and local.

Now let's get to the tricky part :D



Xcode Project + iPhone Settings

Do I need to enable something to receive silent notifications?

  1. You must enable Push Notifications from your Xcode capabilities:

Sample Image

If you don't enable this, your app won't receive a token. And without a token, the server doesn't recognize you.


  1. To be able to download stuff from the background, you need to enable: remote notifications from background modes.

To enable backgroundModes, you can do it either using your plist or Xcode capabilities.

The reason you can do it, either way, is because plist is closer to your code and is the old way, perhaps it's there for legacy support. Xcode capabilities is the newer, easy way.

plist:

Sample Image

Item 0 is just an index, it's not the key of a dictionary (something you normally see in plist), the UIBackgroundModes is an array of Strings. The strings must only come from an accepted value from the UIBackgroundModes Array.

Xcode Capabilities:

Check the Remote Notification in Xcode under background modes as below:

Sample Image

If you don't do any of the above, then toggling off notifications with:

Sample Image

will kill Remote & Local Notifications


However, if you do enable background app refresh from plist or Xcode capabilities, then even with notifications turned off for the app, you will still receive silent notifications!

If the user wants to disable silent notifications, he would have to disable both notifications and disable 'background app refresh for your app / across the system.
To disable 'background app refresh' across your system, you have to do this:

Sample Image

Why am I saying all this? To explain to you that settings of silent and push notifications are different for the user and the restrictions for sending them are different. For more, see this moment from the WWDC video. See here instead (the previous link was dead):

Silent notifications are enabled by default.

The user does not need to approve your does not give permission to
your app to use them, and you can just start using them without asking
the user for permission.

But silent notifications are the mechanism behind background app
refresh.

At any point you know that the user can go in settings and disable
them.

So you can't depend on them always being available.

You don't know if the user the turn them off, and you are not getting
a notification anymore.

This also means that silent notifications are delivered with the best
effort.

That means that when the notification arrives on the user's device,
the system is going to make some choices.

It's going to use different signals from the device and from the user
behavior, like power or the time of day to decide when it is a good
time to deliver the notification and launch your app.

It may try to save battery or it may try to match the user behavior
and make the content available when the user is more likely to use it.

Also see here.

CAVEAT: Even if you disable app background refresh and disable allow notifications, you can still receive silent notifications if your app is in FOREGROUND. If your app is in the background, it won't be delivered.


Do I need to enable something to receive remote notifications?

You just need to enable Push Notifications from your Xcode capabilities:

Sample Image

If you don't enable this, your app won't receive a token. And without a token, the server doesn't recognize you.



APNs Payload structure

Curious... Can you tell me what should my payload look like?

I highly recommend you see Apple§ documentation. It's very clear AND ALSO SEE Sending Notification Requests to APNs. Basically platform makes an HTTP/2 call to APNs and sends the desired payload. Sending the correct headers is critical otherwise your notifications are not delivered to the devices!

Thanks, but can you just tell me the important parts?

uhhmm... OK, but just so you know this is from the link I just said:

For Silent Notifications there is a single criterion:

  • The payload's aps dictionary must include the content-available key
    with a value of 1.
  • Per docs you can send other fields

If there are user-visible updates that go along with the background
update, you can set the alert, sound, or badge keys in the aps
dictionary, as appropriate.

A sample payload would look like this:

{
"aps" : {
"content-available" : 1
},
"acme1" : "bar",
"acme2" : 42
}

acme1, acme2, or just some custom data! But for the aps key, you MUST follow Apple's structure, otherwise, it won't map correctly and you won't be able to read data correctly.

Note: I haven't verified this, but another engineer mentioned that if you have provisional notifications enabled then to ensure silent notifications are delivered you must include an alert field with an empty body. For example:

{
"aps" : {
"content-available" : 1,
"alert" : {
"body" : "",
},
},
}

For User Notifications:

You need an alert key inside your aps.

As an example:

{
"aps" : {
"alert" : "You got your emails.",
"badge" : 9,
"sound" : "bingbong.aiff"
},
"acme1" : "bar",
"acme2" : 42
}

There is also a third option which I will discuss further down the answer.

As for what the fixed aps and alert dictionary keys are, see these Apple docs.

OK, got it. What is content-available?

Very simple. It's just a flag that tells your app that you need to wake up and download something because I have content available for download! For more info, see this exact moment.

By default the content-available flag is not included, i.e., by default the notifications you send won't trigger application(_:didReceiveRemoteNotification:fetchCompletionHandler:) or do something in your app. It would just show the notification. If you want to wake up the app (to do something in the background), you need to include content-available and set it to 1.

§: If you're using Firebase, your payload structure and keys may be slightly different. For example, the key content-available is replaced by content_available. For more, see Firebase documentation and also here.


I know you told me that I can only download something into my app when I'm using silent notifications, but is there a way that I can also wake my app up in the background AND download something for remote notifications?

Yes, but then similar to the silent notification, you must also set the content-available flag to 1, so it would know to wake up and download something. Otherwise, it would just pop and alert/badge/sound but won't download anything.

IMPORTANT NOTES:

  • If your app has only silent notifications, just enable "push notifications" + "remote notifications" from capabilities and set content-available to 1 for each payload.
  • If your app has only remote notifications, just enable "push notifications" from capabilities. There's nothing to do for the content-available.
  • However, if you want your notifications to show an alert/badge/sound and also download something in the background, you must have both "remote notifications" and "push notifications" enabled + set content-available to 1.

(THIRD OPTION)

{
"aps" : {
"content-available" : 1
"alert" : "You got your emails.",
"badge" : 9,
"sound" : "bingbong.aiff"
},
"acme1" : "bar",
"acme2" : 42
}

This moment from WWDC video mentions the

To Quote the Apple Engineer:

Now, you can in a user remote notification, you can set the same
content available flag that you set in silent notifications, and that
allows your app to have some time to download the content or update
the content that it wants to be displayed so that when the user taps
on the notification, your content is available. And the user sees what
it does. This is a way to have a silent notification inside a user
notifications like a summary.



Notifications and iOS Application life-cycle

I'm confused about remote notifications. I thought whenever I get a notification, my app becomes active in the background and downloads something. Can you explain?

e.g. at this moment:

Sample Image

  • Your iPhone has just received a remote notification with a body of "no sender". To receive this, WhatsApp ** doesn't** have to be running in the background, i.e., you don't need "Remote Notifications" enabled from BackgroundModes. You would still receive the notification even if your app was force-quit or suspended because the process is managed by the OS, not the WhatsApp app. However, if you want to be able to download the actual message or its image/video to WhatsApp (so that once your user opens WhatsApp, the video would be sitting there waiting for the user), well then you need your app to become active. To do so, you need content-available : 1 and implement application(_:didReceiveRemoteNotification:fetchCompletionHandler:) .

  • Similarly, if you disable cellular data for an app, you would still receive its notifications. However, by tapping on that notification, the user won't be able to make any network requests for that app. They would only be able to open the app.

  • Or as for another similar scenario, if the server/access point you're connected to has restricted access for, say, WhatsApp, it would still allow you to receive the APNs notifications. However, by tapping on that notification, the user won't be able to make any network requests for that app. They would only be able to open the app.

CAVEAT: If the app was force-quit by the user, then while you do get the notification for the above-mentioned reasons, you can't do anything to bring the app out of its terminated state automatically (even if you had content-available set to 1). None of your delegate methods would be hit. The user must open the app and only then your delegate methods will be reached.



A note on reliability and APNs architecture:

Although notifications are heavily used to deliver the actual content to the app, they are somewhat NOT designed to deliver content to the app. Rather, they are designed to notify the user that "hey something new has arrived (a 2b message or a 50kb small image, or a 10MB image or a 2 GB video). Open the app if you like. By the way, here's a small piece of it (the actual message itself if it can fit, the title of the image or a thumbnail shown in the notification, a title of the video or a thumbnail shown in the video". For more, see iOS APNs “best-effort” fallback. To repeat, you never download the 40MB attachment sent in the email. You just get notified of its existence. You send just enough (a thumbnail view of the attachment) so that the user is informed of what's new and can decide whether or not they need to open the app for more. When I was new to iOS, I thought you actually send the image/video through the push notification. You don't!

Specifically in the case of silent notifications:

When a device receives a background notification, the system may hold
and delay the delivery of the notification, which can have the
following side effects:

  • When the system receives a new background notification, it discards the older notification and only holds the newest one.

  • If something force quits or kills the app, the system discards the held notification.

  • If the user launches the app, the system immediately delivers the held notification.
    Pushing Background Updates to Your App docs

  • APNs sends a limited number of silent notifications—notifications with the content-available key—per day. In addition, if the device has already exceeded its power budget for the day, silent notifications are not sent again until the power budget resets, which happens once a day. These limits are disabled when testing your app from Xcode. See Pushing Background Updates to Your App.

Troubleshooting tips for handling errors returned from ANPs

Even for remote user notifications, the user may be off of the internet and this could cause expired content or APNs could throttle you if you're sending notifications too many or too quickly. See here again

Long story short the APNs and OS are King and you're beneath it. Hence you cannot rely on it to conform to your every command. Having that said it's super reliable in the sense that you see most messaging apps utilize it successfully.

Addendum How to generate push notification certificate, .p12 or .pem and how to test it all out?

Just see this terrific answer. It has the most number of screenshots I've ever seen.

If I use Apple Push Notification service for instant messaging will apple block my account?

I would discourage you from using APNS as a backbone of an "chatting app".

If you need fast chatting functionality you should write your own TCP-socket based server.

If every-few-second syncing is o.k. you can get away with a HTTP-based server backend (but all the pull-syncing can be hard on network traffic - so TCP-socket is still better choice).

You could however use APNS for fallback - when your app on certain device is not responding (i.e. is not connected to server) you can send an initial message trough APNS (to wake up your app & to notify the user there is a message waiting for him).

As soon as the user opens your app you should switch back to your TCP-socket or HTTP request based server communication.

As for your question: no, Apple would most probably (one can never know for sure) not reject your app just because of using APNS for chatting. But note (as the others said allready): messages between two users will get "lost" if they would interact too frequently - see the link Roman Barzyczak gave you.

Push notifications delivered to apple apns server but device received nothing

Apple doesn't give any guarantee on successful message delivery. you can see related answer here

If you're not seeing Apple push notifications when you're connected to a network, check with your network administrator to make sure related TCP ports are accessible.

To use Apple Push Notification service (APNs), your Mac and iOS clients need a direct and persistent connection to Apple's servers.

iOS devices try to connect to APNs using cellular data first. If the device can't connect to Apple's servers over the cellular connection, it then tries to connect using Wi-Fi.

If you use Wi-Fi behind a firewall or a private Access Point Name (APN) for cellular data, you'll need a direct, unproxied connection to the APNs servers on these ports:

TCP port 5223: For communicating with Apple Push Notification services (APNs)
TCP port 2195: For sending notifications to APNs
TCP port 2196: For the APNs feedback service
TCP port 443: For a fallback on Wi-Fi only, when devices can't reach APNs on port 5223

The APNs servers use load balancing, so your devices won't always connect to the same public IP address for notifications. It's best to allow access to these ports on the entire 17.0.0.0/8 address block, which is assigned to Apple.

APNs stands for the Apple Push Notification service. APN stands for Access Point Name, the gateway between a cellular data network and the Internet.

check this link for same explanation

and also check for How do I troubleshoot issues with Push Notifications on iOS?

AFNetworking and Push Notifications

APNS needs a server in order to work. The usual flow goes like this:

  1. The iOS Application asks user to enable push notifications
  2. Upon access granted, a device token is generated and then must be sent to the server.
  3. Your server must be setup with the proper APNS certificates generated from the Apple Developer site
  4. Then in your server's, when a new post is created, you need to add some logic where you load all the APNS token you've received already and then send the notification to the devices.

This is a very simple flow description but I guess you understood that you need to have access to the server to be able to do what you are trying to achieve.

Some third parties exists to handle push notifications (like Urban Airship), but those push notifications are usually pushed manually from a person, and not triggered from a server event

Should I update my app upon receiving payload? Or I should always update it by allowing it to download for itself?

First, downloading the payload usually isn't a problem. I'd imagine that the payload is usually very small (probably <1Kb for short text messages). In fact, the maximum size for a regular remote payload is 4KB (5KB for VoIP notifications). See Creating the Remote Notification Payload.

Second, it is hard to have your app access any remote notification data. The only way that is possible is through a Silent Notification, which is not recommended for simple text notifications. A Silent Notification wakes up your app and gives it 30 seconds to perform actions in the background before it is shut down again. You could potentially send a remote Silent Notification that causes your app to manually trigger a regular, local notification based on that remote one (or send both a silent and regular remote notification), but again, such use of Silent Notifications is not recommended:

Silent notifications are not meant as a way to keep your app awake in the background, nor are they meant for high priority updates. APNs treats silent notifications as low priority and may throttle their delivery altogether if the total number becomes excessive. The actual limits are dynamic and can change based on conditions, but try not to send more than a few notifications per hour.

See Configuring a Silent Notification for more info.

Another possible way that apps could "download" remote notifications as they are received is described in Modifying the Payload of a Remote Notification. You could simply save the notifications and choose not to modify them.

There is no universal yes/no answer that works for all apps to either of your questions. Instead, there are a number of different ways that apps could receive remote notifications and process their data.

EDIT: You should also implement application(_:didReceiveRemoteNotification:fetchCompletionHandler:) and retrieve/save data from the user info of the notification so that the data is available faster in your app. However, that method is not guaranteed to be called. So you could/should implement it, but more importantly, have your app check for new data from your own server so that nothing is missed.

How to trigger an app to fetch data via a REST-Service?

Actually Push Notification is the way to go.

On ios8 and later, push notifications are enabled by default for exactly this reason you want.
You cannot show something to the user (meaning pushes containing a json with an alert will not be shown), but your app is getting the payload and can initiate an action once the push is received.

From the documentation:

A user’s notification settings control only whether the system
displays local or remote notifications onscreen. Regardless of the
notification settings, local and remote notifications are delivered to
your app at the appropriate times
.



Related Topics



Leave a reply



Submit