[iOS study notes] - talk about static library and dynamic library

[iOS study notes] - talk about static library and dynamic library

In the development of iOS projects, we use static libraries and dynamic libraries all the time. Without the support of libraries, we will be very difficult. For example, if you want to draw an interface, you can't do without the UIKit library. The various basic data structures you want to use, such as NSString, NSArray, etc., are also inseparable from the Foundation library. In addition to the official library, we will also download third-party open source libraries from open source communities such as Github for use during development. Generally, the third-party libraries we use or libraries developed by ourselves are used as static libraries, while most of the libraries provided by the system are dynamic libraries, which are convenient for multi-process sharing. Although we use libraries every day, do you really understand static libraries and dynamic libraries? What is the structure of static library and dynamic library? What is the difference between static library and dynamic library? How are they applied? In this blog, we will discuss these issues.

1 Introduction

There are many similarities between static libraries and dynamic libraries, and of course there are also many differences.

From the suffix name, the library file with the suffix .a is a static library, and the library file with the suffix .dylib is a dynamic library. In iOS development, more often the libraries we use are suffixed with .framework. The framework can be a static library or a dynamic library. The framework itself is a packaging method. We know that when we write code, we write "source code", and in order for the computer to understand these source code, we need a compiler to compile the source code and compile it into "machine code" that the computer can understand. A source file of .o will be compiled into a binary .o file, whether a static library or a dynamic library is a collection of .o files. It is not enough for developers to only have library files composed of .o files. It is impossible for us to easily call the methods in the library without header files during development. Therefore, header files are also required to convert the library. The interface provided in is exposed, and sometimes, some other resources may be needed, for example, the library related to the page will have some built-in image resources, etc. The function of the framework is to package the library files, header files, and resource files together, which is convenient We use it. The following figure describes the relationship between framework files and library files:

2. Create a static library

Before getting a deeper understanding of static libraries, we can create a static library experience. First, use Xcode to create a new project and select Framework, as shown in the following figure:

The created framework project template will generate a header file with the same name as the project, and a Resources resource folder. We can create new functional class files. For example, we can create a new class named MyLog and a MyTool class. The code as follows:

MyLog.h

// MyLog.h
#import <Foundation/Foundation.h>

@interface MyLog : NSObject

+ (void)log:(NSString *)str;

@end

MyLog.m

#import "MyLog.h"

@implementation MyLog

+ (void)log:(NSString *)str {
    NSLog(@"MyLog:%@",str);
}

@end

MyTool.h

#import <Foundation/Foundation.h>

@interface MyTool : NSObject

+ (NSInteger)add:(NSInteger)a another:(NSInteger)b;

@end

MyTool.m

#import "MyTool.h"

@implementation MyTool

+ (NSInteger)add:(NSInteger)a another:(NSInteger)b {
    return a + b;
}

@end

In the library header file generated by default, these two function header files are introduced as follows:

#import <Foundation/Foundation.h>

//! Project version number for MyStatic.
FOUNDATION_EXPORT double MyStaticVersionNumber;

//! Project version string for MyStatic.
FOUNDATION_EXPORT const unsigned char MyStaticVersionString[];

#import "MyLog.h"
#import "MyTool.h"

Before building frameworkwrok, we can set the framework to be built as a dynamic dynamic library or a static library. We first build it as a static library, and set the Mach-o Type of the compile option to Static Library, as follows:

After that, you can let Xcode build, and then you can find the generated framework file in the corresponding Products folder, as shown in the following figure:

If you look at the package content of this framework file, you will find that there are 5 types of files, as follows:

Among them, the signature file of the framework is stored in _CodeSignature.

The header files are stored in the headers. It should be noted that when compiling the framework project, the header files that need to be exposed should be set to public.

The Info.plist file is the configuration file of the current framework.

The modulemap file in Modules is used to manage the LLVM module map and define the component structure.

Next, we can try to use this static library, use Xcode to create a new iOS project named LibDemo, drag the MyStatic.framework file built earlier into this project, and find the Framework Search Paths and Header in the project's compile options. In Search Paths, configure the path of the framework and the path of the header file respectively, as shown in the following figure:

Modify the ViewController.m file of the test project as follows:

#import "ViewController.h"
#import "MyStatic.framework/Headers/MyStatic.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSInteger a = 100;
    NSInteger b = 200;
    NSInteger c = [MyTool add:a another:b];
    [MyLog log:[NSString stringWithFormat:@"%ld", c]];
}


@end

Running the code, we can see from the console that our static library is working properly. You may think that the way of importing the header files above is very ugly. You can create a new folder in the project and copy the header files in the framework package, as shown below:

In this way, you can use the functions in the framework like referencing the header files in the project:

#import "ViewController.h"
#import "MyStatic.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSInteger a = 100;
    NSInteger b = 200;
    NSInteger c = [MyTool add:a another:b];
    [MyLog log:[NSString stringWithFormat:@"%ld", c]];
}

@end

3. Try the dynamic library

The process of building and using static libraries seems very easy, and dynamic libraries should be similar. Let's try it now, use Xcode to create a new framework project named MyDylib, change the Mach-O Type in the compile options to Dynamic Library, and create some simple test classes as follows:

MyObjectOne.h

#import <Foundation/Foundation.h>

@interface MyObjectOne : NSObject

@property(copy) NSString *name;

@end

MyObjectOne.m

#import "MyObjectOne.h"

@implementation MyObjectOne

@end

MyObjectTwo.h

#import <Foundation/Foundation.h>

@interface MyObjectTwo : NSObject

@property(copy) NSString *title;

@end

MyObjectTwo.m

#import "MyObjectTwo.h"

@implementation MyObjectTwo

@end

In the same way, drag the built framework file into the test project, configure the header file path, and add the test code as follows:

#import "ViewController.h"
#import "MyStatic.h"
#import "MyDylib.framework/Headers/MyDylib.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSInteger a = 100;
    NSInteger b = 200;
    NSInteger c = [MyTool add:a another:b];
    [MyLog log:[NSString stringWithFormat:@"%ld", c]];
    
    MyObjectOne *one = [[MyObjectOne alloc] init];
    one.name = @"Hello";
    [MyLog log:one.name];
}

Try compiling and running. So far, everything seems to be normal, but when the program runs, it will crash, and the console will output the following information:

dyld[72035]: Library not loaded: @rpath/MyDylib.framework/MyDylib

The reason for this exception is that the dynamic library file was not found. The difference between the dynamic library of the static library and the dynamic library appeared. How to solve this problem is actually very simple. We find the output executable file compiled by the current test project, click to display the package content, Create a new Frameworks folder in it, and copy the MyDylib.framework file into it, as shown in the following figure:

Now run the project again, you will find that the program can be executed normally. However, the operation of manually copying the dynamic library to the executable file is very inelegant. If we really want to use the dynamic library in the project, we will more often use automated scripts to realize the step of copying the library files.

Through these practices, we seem to be able to feel the difference between static libraries and dynamic libraries, but what is the difference? We continue to explore with questions.

4. The difference between static library and dynamic library

Ⅰ. How to load

The reason why the previous dynamic library cannot be found is actually that the loading method of the dynamic library and the static library is different.

Static library: When the static library is linked, it will be completely copied into the executable file. If there are multiple applications that use the same static library, there will be a complete static library code in the binary file of each application.

Dynamic library: When the program is linked, the dynamic library will not be copied into the binary file, but will be dynamically loaded into the memory by the system for the program to call when the program is running. Due to this feature, the dynamic library system can be loaded only once and shared by the application.

For the static library dynamic library loading method, we can go a step further. First of all, the static library will be completely copied into the executable file. The completeness here is actually inaccurate. When we introduce third-party libraries, we often need to configure the -Objc option in the Other Linker Flags of the project. The function of this item It is to set the link optimization.

By default, when the static library is linked, it will not copy all the code to the executable file, it will only copy the used code, which can reduce the size of the final application package, but the dynamism of the OC language determines It is not necessary to refer to the code directly, and this connection method often causes runtime problems.

After setting the -Objc option, the linker will load all the OC classes and their corresponding Categories regardless of whether the code is used or not.

After setting the -all_load option, the linker will load all object files, not only limited to OC files.

Setting the -force_load parameter can specify to force all object files of a static library to be loaded. For this static library, the effect is the same as -all_load.

For dynamic libraries, the linker has no way to do such optimization, because the dynamic library is loaded at runtime, and the linker does not know which code will be used. The size optimization seems to be better than the dynamic library, but is it really so? We leave a foreshadowing first, and then analyze it later.

Ⅱ. The file structure is different

The essential difference between a static library and a dynamic library is that the structure of the built files is completely different. Library files can be viewed using the MachOView tool.

Let's talk about the static library first. The structure of the static library opened by MachOView is as follows:

It can be seen that the structure of the static library is actually relatively simple. Except for some description files and symbol tables of the library itself, it is basically a collection of other executable files. As you can see in the figure, each executable file will have Some header data, these header data record the executable name, size and other information. You can click on any executable file, which contains various code segments, data segments and other data that we are familiar with:

Let's look at the dynamic library again, its structure is as follows:

It can be seen that the dynamic library itself is an executable file, which is not a simple collection of all the internal .o files, but an image file that is finally linked. Since the dynamic library is linked at runtime, it cannot be optimized at compile time, which may increase the size of the application package, but in practical applications, we mostly use the -Objc parameter to force the static library to link all OC files, and Each .o file in the static library will have a header information, while the dynamic library omits this part of the information, so in the end, the static library is not necessarily better in terms of affecting the size of the application package. But one thing is certain, static libraries are linked at compile time, which will save application startup time. Often when doing optimization projects, there is no fixed plan. We have to choose the most suitable plan according to the actual situation.

5. Dynamic library and runtime

Ⅰ. Loading of dynamic libraries

When it comes to runtimes, there's a lot for developers to do. First of all, let's think about the previous test project. If we don't copy the dynamic library file to the IPA package, why can't the program run find the library file? And why do we need to copy the dynamic library into the Frameworks folder of the IPA package? No other folder?

To explain the above problem, we still have to look at the loading principle of the dynamic library. You can use MachOView to open the executable file of the test application package and find the Load Commands section in it, as shown in the following figure:

It can be seen that there are some loading instructions for dynamic libraries. Foundation, UIKit, etc. are all dynamic libraries of the system. We can see the detailed loading path in its details, as follows:

For our own MyDylib library, the load path is as follows:

It can be seen that this dynamic library is loaded from the path @rpath/MyDylib.framework/MyDylib. The setting of this loading path has been determined when the dynamic library is compiled. We can look at the MyDylib project in the compilation configuration of Xcode Options, find the Dynamic Library Install Name option, as shown below:

The @rpath here is actually an environment variable. You can configure the value of @rpath in the application project. Search for rpath in the compilation options of the LibDemo project, and you can see the configuration of this environment variable:

Now we are clear, in fact, the dynamic library file does not have to be placed in the Frameworks folder, you can modify the loading path of the dynamic library by modifying the path of the @rpath variable.

For this loading method of the dynamic library, in principle, we can modify the loading path of the binary file, or directly replace the dynamic library file in the package to achieve some reverse injection functions, which is very cool.

Ⅱ. Code loading dynamic library

Dynamic libraries are loaded at runtime, and we can also use code to dynamically control the loading of dynamic libraries at runtime. You can delete all the references to MyDylib in the test project, and also remove the configured header file path. We copy this dynamic library into the project's Bundle, as follows:

The code to modify the ViewController class is as follows:

#import "ViewController.h"
#import "MyStatic.h"
#import <dlfcn.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSInteger a = 100;
    NSInteger b = 200;
    NSInteger c = [MyTool add:a another:b];
    [MyLog log:[NSString stringWithFormat:@"%ld", c]];
    
    NSString *path = [[[NSBundle mainBundle] pathForResource:@"MyDylib" ofType:@"framework"] stringByAppendingString:@"/MyDylib"];
    // 载入动态库
    void * p = dlopen([path cStringUsingEncoding:NSUTF8StringEncoding], RTLD_LAZY);
    if (p) {
        // 加载动态库成功 直接使用
        Class cls = NSClassFromString(@"MyObjectOne");
        NSObject *obj = [[cls alloc] init];
        [obj performSelector:@selector(setName:) withObject:@"Hello"];
        [MyLog log:[obj performSelector:@selector(name)]];
    }
}

@end

At this point, compile and run the project again. If you observe the binary file of the test project, there is no loading of MyDylib in the load command, but the program can still be executed normally. The function of the dlopen function is to load the dynamic link at runtime. After the library is loaded successfully, we can directly call the code in the dynamic library with the help of the runtime method of OC. In this way, we can actually realize the dynamic download and use of the plug-in, so that the application has a very high hot update capability, but it should be noted that the way of dynamically downloading the dynamic library is not allowed to be listed on the AppStore, we can only test the App Or use it in corporate apps.

Going a step further, in fact, the dynamic library is not necessarily read from the local sandbox. When debugging locally, you can read the dynamic library file from any location and load it, which can realize many very cool functions locally, such as Injection tool, which monitors the changes of code files through a service, then packages it into a dynamic library and injects it into the program, and then replaces classes and methods at runtime to achieve the hot update effect of locally developed iOS projects, which is very easy to use.

Focus on technology, understand love, be willing to share, be a friend

QQ:316045346

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/2340880/blog/5323143