Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift library that supports both app and extension

I've created a library for my iOS app but I get the warning linking against a dylib which is not safe for use in application extensions.

I know this is because I haven't enabled Allow app extension API only for my library. So when I enable that setting my library gives errors where I have used UIApplication.shared.

I know I cannot use shared in my extension, and don't actually need to, but this library is used by both my app and my extension.

So the question is, how can I compile the library with a guard around UIApplication.shared?

I already use this:

#if !IS_EXTENSION
    // Cancel the background task
    UIApplication.shared.endBackgroundTask(bgTask)
#endif

and set IS_EXTENSION for Active Compilation Conditions in my extension target, and also IS_EXTENSION=1 for Preprocessor Macros in my app extension, however the library still gives me warnings on these lines.

like image 698
Darren Avatar asked Nov 05 '20 14:11

Darren


2 Answers

1st solution

I recommend you to use UIApplication injection into your lib. It can be achieved like this.

Your lib instance can look similar to this:

class DarrenLib {

    static var shared = DarrenLib()
    
    // User application instance everywhere where needed in your lib.
    private var application: UIApplication?
    
    func setup(_ app: UIApplication) {
        self.application = app
    }
}

As example injection can be done in the did finish launching function:

func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
    
    DarrenLib.shared.setup(application)
    
    return true
}

I would not recommend use preprocessor macros, because it becomes depending on additional setup requirements which can not be done throw source code.

2nd solution

Also as you are currently using app and extension targets. You could create these frameworks:

  1. DJSwiftCommonHelpers. In this framework will contain source code which will work on an app and extension target.
  2. DJSwiftAppHelpers. This framework will contain source code which will contain UIKit specific cases like UIApplication shared instance.
  3. DJSwiftExtensionHelpers. In this framework will contain source code specific only for extensions if here are any.

DJApp will be embedding DJSwiftCommonHelpers and DJSwiftAppHelpers frameworks.

DJNotificationExtension will be embedding DJSwiftCommonHelpers and DJSwiftExtensionHelpers frameworks.

enter image description here

like image 177
Ramis Avatar answered Sep 20 '22 14:09

Ramis


There are several answers to your questions. In general, answer is both YES and NO.

First of all:

#if !IS_EXTENSION
    // Cancel the background task
    UIApplication.shared.endBackgroundTask(bgTask)
#endif

This approach is more or less ok and would probably work, but for sure not with Carthage (that's what I see you use in https://github.com/ddaddy/DJSwiftHelpers), maybe with Pods or SwiftPM if you can assure flag is properly set on compile time.

The reason is simple and you actually answered it yourself - it is a compilation time flag.

Carthage compiles framework and builds the binary as a part of carthage build/update process, so you end up with already existing binary. This flag was already resolved, and was probably false, since not set. So no build time flags in app or extension target would 'work' for carthage libraries.

As for the carthage - what I can suggest is making additional targets/schemes in DJSwiftHelpers.xcodeproj. The same way as multiplatform support is handled for carthage libraries that have multi platform support, like both iOS, tvOS and macOS. Add a scheme DJSwiftHelpers-extensions, make sure it builds binary using proper build flags, and carthage will produce two distinctive binaries. Link one to extensions and second to applications, and it might work.

Other possibilities:

  1. Use UIApplication injection as proposed in Ramis answer

  2. Use the injection, but actually you can define second library to be only used for applicatyion targets, containing one ObjC class with overriden '+ load' method, like:

#import 'yours helpers library'

@implementation SomeClass

+ (void)load {
    // This is called once, when module is being loaded,
    // "Invoked whenever a class or category is added to the Objective-C
    // runtime; implement this method to perform class-specific behavior
    // upon loading."
    [YourHelperLibraryClass setupWithApplication:[UIApplication shared]];
}

@end

It is a bit messy, but will allow not to inect in app delegate. I would personally not use it for that though.

  1. Use cocoapods or SwiftPM - if I remember correctly, the library code would be compiled together with the app, so it's possible that it will "see" the compile time flags set there.
like image 40
Andrzej Michnia Avatar answered Sep 22 '22 14:09

Andrzej Michnia