Is there a Prefix Header (or something with this functionality) in Swift?
Asked Answered
S

4

10

Is there a way to get the functionality of a prefix header in Swift? I don´t want to import external libs in every file where they are used.

Smallish answered 24/2, 2015 at 11:24 Comment(0)
J
44

No. But you don't need it — there's no cost to import UIKit beyond the time it takes you to type twelve characters. (Or use an Xcode New File template that has them there already.)

That's the TLDR. For the whole story, read on...


In (Obj)C, the old way to make API available for use in a source code file was textual inclusion. The preprocessor would see your #import <Foundation/Foundation.h> directive and copy all the text from that header file (and from any other headers it includes, and the headers they include, and so on) into your source file before passing it off to the compiler. As you might expect, recompiling thousands of lines of system header declarations for each file in a project wasn't so performant.

So, we got precompiled headers some years ago—you'd put your common #imports in one place, and the compilation step for those parts would be done once, with a result that the compiler backend could reuse for each file in your project. But that had its problems, too: there's a maintenance burden to keeping your PCH happy, and it doesn't let you restrict the namespace used in each source file (i.e. if you want one .m file in your project to see only the symbols it needs to use, and not all the other stuff used elsewhere in your project).

And on top of that, textual inclusion has an underlying fragility problem. If you #define something above your #import lines, and that define changes a symbol used in the imported headers, those headers will have compile errors (or fail in more subtle ways, like defining the wrong API). There are conventions to keep that from happening, but conventions aren't enforced — you're always a typo / new team member / bad merge away from everything falling apart.

Anyway, textual inclusion wasn't so great, even with precompiled headers, so in Xcode 5 Apple introduced Modules. (Actually, not just Apple. They're in the LLVM/Clang compiler suite, so they're open source.) Modules are based on semantic import, not textual inclusion — that is, a module tells the compiler at an abstract level what API symbols it makes available to your source code, rather than pasting in the text of those symbols' definitions — so they're not fragile, and they're individually precompiled on the back end so building your project stays fast.

Modules are the default for ObjC projects now. (Notice that if you create a new ObjC project, it doesn't include a precompiled header anymore. You can turn modules off, so if you have an old project you might still be using textual inclusion and precompiled headers.) You can find out more about ObjC modules in Session 404 from WWDC 2013.


Why all this business about ObjC? We're talking Swift, right? Well, Swift is based on a lot of the same infrastructure.

Swift uses modules from the start, so it's always based on semantic import. That means there's no build-time performance hit and no fragility. All that Swift import does is tell the compiler what symbols you need (and the linker where to find them when producing your binary executable).

So the only cost to putting the same imports at the top of every file is the typing. And that's a necessary cost — in Swift, the source file is a semantic unit, and there's real meaning to deciding what goes into it. For example, the behaviors of many of the Swift standard library types change if you import Foundation, to enable bridging with Cocoa collection and value types — if there's a part of your app that wants to work strictly with Swift collection and value types, you might not want to import Foundation (or Cocoa or UIKit or something else that includes it).


Update: Furthermore, what you choose import in a Swift file has real meaning.

For example, how the compiler optimizes generics and static/dynamic dispatch depends on what declarations are visible in a given file, so if you import more than you need to, you may generate slower code. So generally, it's best to import only what you need.

Explicit imports also help with clarity and readability. If imports were project-wide, then when you copy-paste code out of one project and into another you'd see lots of errors in the new location... and it'd be a lot less clear what imports you need to resolve them.


"But I hate putting the same several imports at the top of every file all the time," you say. Let's think about that a little.

  • Do you really need several? Most modules transitively import their dependencies. You don't need to import Foundation if you're already importing Cocoa (OS X) or UIKit (iOS/tvOS/watchOS). And if you're writing a SpriteKit or SceneKit game, for example, you automatically get UIKit/Cocoa (for whichever platform) and Foundation for free.

  • Do you really need the same in every file? Sure, you're in a UIKit project so you're using UIKit almost everywhere. But that's just one import, twelve characters at the top. Maybe your project is also using Contacts or Photos or CoreBluetooth or HealthKit... but it probably doesn't need to use all of those in every single type you define. (If it does, your code probably suffers from poor separation of concerns.)

  • Are you really managing import statements all the time? I don't know about your projects, but in most large projects I've worked on, I'd say at least 90% of the development activity involves editing existing source files, not creating new ones... after starting up work on a project or major feature, very seldom are we (re)defining the set of source files or their dependencies. And there are shortcuts that can help with (among other things) setting up imports, like Xcode file templates.

Jonasjonathan answered 24/2, 2015 at 18:43 Comment(6)
+1 for the excellent answer! But it still doesn´t seem 'right' to me to import all the stuff I need everytime in every source file. It has some smell of code duplication, even though it might not be exactly that. What if I need to import some new thingy in all of my source files? Do I need to open all 485 of them and add the new import line? That can´t be the final solution...Smallish
If you have a project where 485 source files really each have the exact same set of dependencies, there might be a code smell, but not in the language... There's not much cost to overimporting, either, but there are also good reasons for each file to import only what it uses.Jonasjonathan
I agree with your arguments, there are cases though where import some new thingy in all source files is not a code smell. For example adding logging functionality into a project would require importing it in every source file where it is needed. That's unfortunate there isn't a mechanism to get logging functions available across the entire project.Coper
@Coper You could wrap that logging framework in you own internal functions which would be available everywhere. And that's a good practice to wrap such functionality anyway.Skald
@Jonasjonathan You say "how the compiler optimizes generics and static/dynamic dispatch depends on what declarations are visible in a given file". Do you have some kind of documentation on the subject ? I would really like to know more about that.Skald
@Skald I'm not aware of reference docs on the subject, but it's a topic that's come up in several of the WWDC presentations on Swift and Xcode over the past few years.Jonasjonathan
P
20
  1. Create a Objective-C Bridging Header file:

    [New File→iOS→Source→Header File]: Bridging-Header.h

  2. Go to this new header and import your external libs:

    @import Module1Name;
    @import Module2Name;
    ...
    
  3. Go to Build Settings, set the path of Objective-C Bridging Header:

    [Target→Build Settings→Swift Compiler - Code Generation→Objective-C Bridging Header]: $(SRCROOT)/.../Bridging-Header.h

Then you can use external libs in every file without import code.


References:

Plasterwork answered 2/2, 2016 at 5:51 Comment(0)
C
0

There is a -enable-bridging-pch feature. But it seems that not working in Xcode 9 :(
I decided to write this ansewer just to fully cover the topic.

Cyton answered 13/10, 2017 at 11:44 Comment(1)
This is for precompiling the headers which define the public API of your Objective-C code which shall be visible in your Swift code in a mixed language project. The Swift compiler will have to parse the bridging header every time it compiles a Swift file. So, precompiling speeds this up. See also swift.org/blog/bridging-pchPublea
Q
0

You can create module that will import yours dependencies and import only it.

For example call Core. It will contain only single swift file with imports.

Every import should begin with @_exported keyword.

For example:

@_exported import UIKit;
@_exported import Combine;
Quinidine answered 18/12, 2020 at 15:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.