Norway


Libby the lifelike framework offers help (The framework icon cartoonified similar to Microsoft Word's Clippy asks: what is a framework, will it affect performance, and I just want to resolve these build errors!)  - frameworky - It Looks Like You Are Trying to Use a Framework

Libby the lifelike asks good questions. Apple has made it simple to split apart iOS and macOS code into separate modules, libraries, and frameworks. Frameworks are designed for more than packaging resources and modularizing code, and definitely are not designed to protect against recompiling certain files of your project when you build. In order to simplify your code base, speed up debugging, and package your code for maximum reusability, frameworks need to be understood on a deeper level than a toolbox-icon that is dragged and dropped into Xcode. To understand Frameworks, we’ll need to cover:

  • Static Libraries
  • Dynamic Libraries
  • Frameworks and Their Structure
  • Linking vs Embedding Frameworks in Xcode
  • Questions to Ask Before Using a Framework
  • Some Handy Command Line Tools for the More Curious

Note: Most of the content below is applicable to both macOS, tvOS and iOS programming. Any there are significant differences they will be noted.

Static Libraries

To look at static libraries, we will start with object files (If you aren’t familiar with these already, we have a post that explains what an object file is). At their most basic, object files are a structured collection of blocks of bytes on disk. Some of these blocks contain program code, others contain structures that are used down the pipeline by the linker and loader. For a bird’s eye view of an object file, try running this in the terminal

$ objdump -macho -section-headers /bin/ls

Object files come in four forms: relocatable, executable, shared, and bundle. Relocatable object files contain code and data that can be statically linked at compile time with other relocatable object files in order to create an executable. Executable object files are ready to be loaded into memory and executed. Shared object files are a special version of relocatable object files that will be covered in the next section. We won’t cover bundles, but on macOS, this filetype is typically used for plugins.

If you compile a C source code file with no main function, then the result is a relocatable object file, for example:

fancy.c

void fancySwap(int *xp, int *yp) {
    if (xp == yp) return; // try it!
    *xp = *xp ^ *yp;  
    *yp = *xp ^ *yp;  
    *xp = *xp ^ *yp;  
}

The resulting fancy.o will contain the symbol fancySwap and its implementation.

To work with relocatable object files in a more convenient format, object files can be archived into .a (a is for archive. See man ar) files. The .a file is known as a static library or static archive. When functions or data from a .a file are used as part of an executable, the linker is enough to only link the symbols used. However, the linker includes (copies and relocates) all of this code in the executable object file at a static address. The result of this is a larger executable that is slower to load into memory. To make matters worse, if ten programs use this archive, there are effectively ten copies of the code on your system. Additionally, if the author of the .a file used updates the source, the executable must be recompiled and redistributed. The dynamic library was invented as an alternative to the rigidity of static libraries.

As of Xcode 9.0, static Swift libraries are now supported (Xcode Release Notes – 33297067). If you pick the Static Library project template make sure the PRODUCT_MODULE_NAME is set so users of your library have something to import. As of Xcode 9., this is handled in iOS, but not in macOS templates.

Dynamic Libraries

A dynamic library (also referred to as: shared library, shared object, and dynamically linked library) is an archive of object files that can be loaded at an arbitrary memory address and linked with a program in memory at load-time or runtime. This is called dynamic linking and is done by the dynamic linker (dyld on macOS). Shared libraries have the .dylib extension on macOS, the elusive .DLL extension in Windows, and .so on Linux. Whereas static libraries are statically linked by a static linker at compile time, dynamic libraries are dynamically linked by a dynamic linker at load or runtime. Linking at runtime is a cumbersome process; man dlopen will give a quick overview of dynamic loading. An application where runtime link/loading is practical and performant is high performance web servers. Dynamic libraries can be unloaded and upgraded without any downtime to the running application. Dynamic libraries can also be loaded on a per-request basis. If a client of the web-server requests a particular resource, the server can load in the required library to create the resource, then unload it once it has responded to the request to reduce its memory footprint. A less contrived example of dynamic loading is an application supporting plugins which are dynamically loaded.

Shared object files solve the problems mentioned above with static object files. Multiple executables can dynamically link to a single copy of a shared library on disk. Shared libraries can be updated without having to make changes to the executable. Shared libraries even have their dependencies loaded and linked automatically, assuming the dependencies are present in the search path. macOS makes extensive use of shared libraries; check out the contents of the /usr/lib folder. If you want your own shared library to be used, you have to ensure it is somehow embedded in your app, or create an installer package that drops your dylib in /usr/local/lib. Why not /usr/lib? The system reserves the right to drop whatever it needs in /usr/lib, (that directory is owned by root) so you are better off using local.

After reading the pervious paragraph, you might think, “Great, I may not have to recompile, but now I have to keep in mind backwards compatibility for all users of my dylib.” You are correct, you do have to think about API stability as you make updates to a shared library. Or maybe this is why you came here. To learn about the framework, a bundling format that addresses compatibility and more.

Frameworks

Most frameworks are shared libraries with hassle-free packaging. This is easy to confirm. A framework is a folder with a particular structure that encapsulates shared resources. These can be images, xibs, dynamic libraries, static libraries, documentation files, localization files, and more. They are packaged as a bundle on the file system, but unlike most bundles (like a .app file), frameworks don’t have anything to hide and reveal that they are in fact a folder.

Note: The structured directory “bundle” referred to here is not the same as the bundle object file type discussed at the beginning of this post.

A framework expanded in finder(├── Libby.app
└── MyCustomFramework.framework
    ├── Headers -> Versions/Current/Headers
    ├── Modules -> Versions/Current/Modules
    ├── MyCustomFramework -> Versions/Current/MyCustomFramework
    ├── Resources -> Versions/Current/Resources
    └── Versions
        ├── A
        │   ├── Headers
        │   │   ├── MyCustomFramework-Swift.h
        │   │   └── MyCustomFramework.h
        │   ├── Modules
        │   │   ├── MyCustomFramework.swiftmodule
        │   │   │   ├── x86_64.swiftdoc
        │   │   │   └── x86_64.swiftmodule
        │   │   └── module.modulemap
        │   ├── MyCustomFramework
        │   └── Resources
        │       └── Info.plist
        └── Current -> A
)  - finder framework - It Looks Like You Are Trying to Use a Framework

Below is a walkthrough of the subdirectories and symbolic links that make up a framework bundle.

This folder contains the C and Obj-C headers that the framework exposes to the public. Swift does not use these headers. If a framework is written in Swift, Xcode will create this folder by default for interoperability. If you are sometimes mystified by when to add @objc in Swift code, try this: look in Build Settings to discover the value for SWIFT_OBJC_INTERFACE_HEADER_NAME, add and remove @objc annotations and other interop directives (@objc(your:objc:name), @objcMembers) from your source code and see how this affects the objective-c header named by the SWIFT_OBJC_INTERFACE_HEADER_NAME.

Modules

This folder contains LLVM and Swift module information. .modulemap files are used by clang, and are discussed at length here. The files contained in the .swiftmodule directory are similar to header files. However, unlike header files, the files in here are in a binary format that is “undocumented and subject to change”. This is what Xcode uses when you Cmd-click a swift function whose module, but not source code, is availabe.

Although these are binary files, they are structured as llvm bitcode, and as such, we can gather information from them with llvm-bcanalyzer and llvm-strings.

MyCustomFramework

This is a symlink to the dylib which Finder labels a Unix executable, but it is actually a relocatable shared object file.

Resources

Localized resources, xibs, documentation files, images, and other assets can go in here.

The symlink destinations are shown in the picture. Historically, they existed to handle versioning. If a new version was introduced, it would be added to the folder under directory Versions/B. Current will also be updated to point at B. Meanwhile, applications dynamically linked to A will still link to version A rather than Current. How? When an executable is built, the dynamic linker records the path of the version of the framework known to be compatible with the executable. Today even Apple rarely, if ever, uses this feature.

Linking vs Embedding Frameworks in Xcode

Here we discuss a commonly configured part of Xcode, your target’s general settings.

Xcode's Target General Settings View  - target settings - It Looks Like You Are Trying to Use a Framework

Almost always select “Linked Frameworks and Libraries” if you need to use a framework unless you plan on linking and loading at runtime (dlopen strikes again). What then is the difference between these two options? Why does Xcode automatically add a framework to the Link section if you choose to Embed it? Think about what you’ve just read about where dylibs are stored and what has to be done if you want to package your own dylib.

Correct! Embedding actually adds a copy of that framework into your application bundle under the Frameworks directory by default. Your application won’t be able to do anything with that Framework unless it links to it. Xcode takes the liberty of doing this for you. System frameworks are already present on either iOS or macOS so you can safely link to these at the absolute path that they are kept on the system. Your own custom frameworks won’t be present on a user’s system, and therefore they have to be embedded in the application bundle.

Linking and embedding indirectly imply dynamic and/or static linking. We now know that embedding wouldn’t make any sense for a static library becuase the symbols from the static library are compiled into the executable, so Xcode won’t let you drop a static library under the Embed section. It will however, let you embed a framework with static libraries. This is an inefficient use of frameworks.

Just because you can, doesn’t mean you should: Technically on macOS you can load a static .a file at runtime and then mark the area of memory it occupies as executable, effectively loading a static library at runtime. This technique is what JIT compilers do, but marking memory segments as executable is not allowed on iOS.

It Looks like You are Trying to Use a Framework

What are factors to consider before using a framework or moving your code into a framework?

  • Are you developing a suite of apps? If so, this is part of the essence of frameworks and Libby says you should proceed with enthusiasm keeping in mind that to truly share the frameworks, you will have to build an installer package that places the shared frameworks in /Library/Frameworks or ~/Library/Frameworks.

  • Do you have a little bit of common code that you share amongst apps? A framework might not be right here. If you’re just trying to share small amounts of code the overhead of packaging and versioning a framework might outweigh the tasks of keeping the source in sync.

  • Are you trying to modularize your code? If you are moving code into a framework to modularize it, you might be better off using Swift’s access control. Frameworks shouldn’t be used as a namespacing mechanism. That said, creating a private framework internally to separate out reusable code from app specific code is not unheard of.

  • Don’t use frameworks as “recompile shields!” Some projects will split a large part of the project’s core code that is close to finished out into a framework to speed up compile times. This “works” because as we know, frameworks don’t have to be recompiled with the executable, but this complicates code modularity, debugging, and autocompletion among other things.

  • Are you packaing your code as third-party for use by others as-is? If this is the case, you might be choosing between dylib, static library, and Framework. It is currently hip to dynamic frameworks for both mac and iOS because of their portability and pervasiveness and ease-of-use. Also consider sharing the source code too to let others make the decision that works best for them!

A parting thought to pair with these usage tips: frameworks are incredibly common to iOS and macOS programming, but their advantages and potential performance benefits are rarely exercised while their portability and ease of creation are abused. I’ve described framework utopia above. For a less idealized take on their usage, read this article by Wil Shipley from 2005 that has aged particularly well.

Here are some command line tools that, with the exception of llvm-bcanalyzer come with macOS out of the box.

  • file: Running this with a filename as the first argument is a great first move. It simply outputs the type of file with details of architecture, object file format and more.
  • nm: symbols defined in an object file. Running with the -u flag will show the undefined symbols, meaning symbols that a file expects to have defined by another shared object file.
  • lipo: You won’t run into this too often in the wild, but it is a neat one. It can perform various operations related to fat object archives. For instance if you have a universal (intended for multiple ISAs) binary, you can use lipo to extract only the architecture you care about so your executable isn’t bloated with unneeded archives.
  • objdump: Considered by many to be the great-grand-daddy of inspecting object files
  • ar: This is the tool used to bundle multiple relocatable object files into a more portable archive file. It outputs .a library files.
  • llvm-bcanalyer: This can dump llvm bitcode files into a semi-human-readable format. Though powerful, objdump won’t let you analyze llvm bitcode files, so it’s good to know about this one.

Questions?

Check out the sources below if you have more questions than answers after reading this. Also leave comments and I’ll do my best to address them.

Sources



Source link

LEAVE A REPLY

Please enter your comment!
Please enter your name here