IOS 10 and watchOS 3 bring many exciting new system extension points to developers. From Siri to Messages, the number of ways for apps to interact with the system continues to grow.

These new integrations, along with a large number of existing integrations, are often added in the form of application extensions. Apple’s App Extension Programming Guide:

“App extensions allow you to provide customizable functionality and content beyond the App itself as users interact with other apps or systems.”

Because an application extension is a completely separate entity (a process completely separate from your application process), it needs a way to share functionality and data with the parent application. Consider a fitness app that allows users to start a workout with the Siri extension, and both the app and the Siri extension require access to user-created workouts, workout search functions and additional user preference Settings.

The good news is that Apple provides some mechanism to make this sharing of data and functionality possible. The bad news is that the process of migrating an old and complex project to use these mechanisms is not easy. The purpose of this article is to guide you through the details of getting your old iOS project organized and ready for application expansion.

Extended shared code

In your project. The first and most important aspect of sharing in your application and application extensions is the code itself. The simplest and most crude approach is to add any code you want to share to both the target application and the application extension. If you do. Not only would it lead to repeated compilation of the entire code, it would also receive my contempt and ridicule. A better way to share code is through embedded dynamic libraries. Here are some advanced steps on how to do this, and some special considerations when making changes to an existing project.

Creating a dynamic library

Create a New dynamic library (File → New → Target; Select Framework & Library → Cocoa Touch Framework). A new project will be created in your project structure and in the disk directory.




Choosing Cocoa Touch Framework from File → New → Target → Framework & Library

In existing projects, I’ll assume you migrated Objective-C code. If this is the case, make sure you choose Objective-C as the language type when creating your project. This will not prevent you from adding Swift codes later, but if you do, you need to be aware of the following points:

In your application extension project configuration. You can enable the Allow App Extension API only option in the General TAB. This will ensure that you do not access any system API that is not available in the application extension, thus ensuring that your framework can be used in both the application and the extension.

Mobile code

Add the resource files you want to share to the new dynamic library project and delete them from the application project. It would be a good idea to start with as little code as possible, and then share more and more code as you run into problems with various extraction functions. It is also strongly recommended that you move files on disk (and not just files within the project) to avoid conflicting project file ownership.

Configure the primary header file

When you create a new library project, Xcode automatically creates a main header for you. If you want to give the library to others to use, this is the header specified for Objective-C code, such as your main applications and extensions. Here is a simple example of the main header for a library called “Services” :

/ /! Project version number for Services. FOUNDATION_EXPORT doubleServicesVersionNumber; / /! Project version string for Services. FOUNDATION_EXPORT const unsignedcharServicesVersionString[]; #import #import #importCopy the code

Also note that it is not enough to specify a header: you must also select the header and change the visibility to Public in the properties inspector (right panel of Xcode)




Setting the umbrella header’s visibility to Public

Finally, remember that for Swift code, which is slightly different, there is no longer any need to configure visibility in the project configuration or when referenced in the main header file. As you can see by comparison, visibility for all Swift code is directly controlled by the language’s access control features (private, internal, and public).

Other Matters needing attention

Some minor pitfalls to consider when creating a new dynamic library. As mentioned earlier, you can actually reference Swift code in objective-C libraries and, in general, things will work out just as you would expect.

When writing code, there seems to be no need to create a bridge file for objective-C code, if you want to use Swift (inside the dynamic library) in Objective-C code, it will be part of the public header file (e.g. Reference main header), objective-C code exposed in this way is automatically available in your static library Swift code when using the bridge file, conversely (accessing the Swift library from within Objective-C), You just need to import the automatically generated Swift file header (for example #import). Doing so exposes the Swift code based on the access control you specify.

Using dynamic Libraries

Once a new dynamic library has been created and configured, it can be used in applications and application extensions. The first task is to introduce dependencies in the Embedded Binaries configuration of applications and scale-up projects:




Add your framework under Embedded Binaries for both app and extension

Note: General panel in Xcode Project Settings for each project

Once you’ve introduced the dynamic library, it’s much easier to import Services in Swift and @import Services in Objective-C; Can reference this module.

Shared data

If both your main application and application extensions need access to user data that the application writes to disk, it is not enough to simply migrate the code into the dynamic repository. Because the application extension does not have the same permissions as the main application to access these files, it cannot access the data you wrote to the sandbox space.

Create an application group

The solution to this problem is to create and configure an application group for your application and for reading and writing in a shared location, rather than the file hierarchy of the main application. This applies to any shared library that uses file I/O to execute code, whether directly interacting with files or using an abstraction layer like disk files to support core data.

To create a new App group, you first need to create your App Groups according to the App Groups section of Apple Developers Introduction. Used as application identifiers in the same way, in order to reverse domain name (for example: com. Mycompany. AwesomeWorkouts). Once you’ve created your app group, you need to turn on the Capabilities option in each project’s app and app extensions




Click this switch to enable the app group

Make sure your project group is configured, then click on the right side to open the application group. After Xcode does some dark magic, you’ll see a list of app groups in your personal account, enabling access to shared data for each app and extension you create.

Accessing a Shared container

Now that your application and application extensions can access the application group, it’s time to change all file I/O code to point to the shared container of the application group, not the app-specific location. This can get the root directory for the shared container (note: Swift 3 is the latest language version at the time of this writing):

let rootURL=FileManager.default().containerURLForSecurityApplicationGroupIdentifier("group.com.mycompany.AwesomeWorkouts")Copy the code

This will give you a root directory for reading and writing applications and extensions. Note that when operating on the application group identifier, you must start with “group.” otherwise the query will fail.

The migration

You have now configured the application and application extensions to access data from a shared location. New users of your app will start out fine. However, users using existing data will suddenly lose all of their data. SHH, of course! Because you changed all the code to point to the new shared location, all the current data is left in the deprecated application.

There are many ways to solve this problem. But the most straightforward way is to perform a one-time migration when the new version of the application is opened for the first time. Write code that runs only when the old location has data (sandbox) and not when the new location (shared container) has no data, if both conditions are met. Copying the necessary data to a shared container is a pleasure. Be sure to perform this migration before any code attempts to read the file system. This also includes initialization of core data.

Shared configuration

After dealing with code and data sharing, you still have a problem with application Settings. The most common way to save this data is through NSUserDefaults. Unfortunately, this approach suffers from the same problem as the default iOS traditional file I/O, which is stored by default in a location that only the main application can access. Fortunately, there are two very easy ways to expose this data to applications and extensions.

Application of group

Let’s polish the old application group again. Just as you can write file data to shared containers. You can also read and write user defaults through application groups. Instead of accessing standard defaults. The default value for accessing the shared container looks like this:

let defaults=UserDefaults(suiteName:"group.com.mycompany.MyApp")Copy the code

You also copy all the old user defaults to the new user defaults through a similar migration process. When migrating files, access to any project is given priority.

Up to the Key – Value stored

The second way to share user Settings is to use iCloud key-value storage. There is a good existing documentation on how to use the system. You can access key-value stores within application extensions and applications, as this is a good way to share configuration data. If you already use iCloud key-value storage for configuration data. And you’re done! Simply access it through the shared library. If you’re having trouble deciding which method to use. I think this approach is better because even if the app is deleted, your user profile data will be synchronized across multiple devices.

conclusion

That’s it! Once you have completed the above steps. Your old project will shine. Pulling out abstract shared data and services into an embedded framework may seem like a lot of work, but given apple’s direction of movement (running common code in many different environments). It makes it easier for you to adopt new application extensions as described. The future of iOS is the underlying system that guides apps. Ensuring that the best practices are in line with Apple’s latest architecture will allow apps to continue to succeed.

If this article doesn’t make you feel like nonsense (or it does), follow me on Twitter @Nickbona, where I’ll talk about software development and technology.