How does Swift's "silent" import work?

Just for some context:
People sometimes say "A file containing a ViewModel should never import UIKit".
As if not importing UIKit in a file would be a guarantee that the file doesn't depend on UIKit, which you can verify is not the case by doing the following.

  1. In eg SceneDelegate.swift, define a global constant of some UIKit type:

    let someUIColor = UIColor.systemPink
    
  2. In some other file, that does not import UIKit, use that global and note that it compiles anyway:

    struct MyViewModel {
      let color = someUIColor
    }
    

    So not importing UIKit didn't stop my view model from depending on UIKit ...
    Writing out the type explicitly will result in an error though:

    struct MyViewModel {
      let color: UIColor = someUIColor // ERROR: Cannot find type 'UIColor' in scope
    }
    

What's behind this seemingly erratic behavior, ie why does it compile or not depending on whether the type is inferred or explicit?

You can use names from:

  • The file you're in, unless they're private to a type you're not in
  • The module you're in, if those names are internal, public or open;
  • The modules you import.

someUIColor is a name in your module. UIColor is not, and files that use it need to import UIKit before mentioning it.

5 Likes