[iOSスタディノート]-静的ライブラリとダイナミックライブラリについて話します
iOSプロジェクトの開発では、常に静的ライブラリと動的ライブラリを使用しますが、ライブラリのサポートがなければ、非常に困難になります。たとえば、インターフェイスを描画する場合、UIKitライブラリなしでは実行できません。NSString、NSArrayなど、使用するさまざまな基本データ構造もFoundationライブラリから切り離せません。公式ライブラリに加えて、開発中に使用するために、Githubなどのオープンソースコミュニティからサードパーティのオープンソースライブラリもダウンロードします。一般的に、私たちが使用するサードパーティのライブラリまたは私たち自身が開発したライブラリは静的ライブラリとして使用されますが、システムによって提供されるライブラリのほとんどは動的ライブラリであり、マルチプロセス共有に便利です。私たちは毎日ライブラリを使用していますが、静的ライブラリと動的ライブラリを本当に理解していますか?静的ライブラリと動的ライブラリの構造は何ですか?静的ライブラリと動的ライブラリの違いは何ですか?それらはどのように適用されますか?このブログでは、これらの問題について説明します。
1はじめに
静的ライブラリと動的ライブラリの間には多くの類似点があり、もちろん多くの違いもあります。
接尾辞の名前から、接尾辞.aのライブラリファイルは静的ライブラリであり、接尾辞.dylibのライブラリファイルは動的ライブラリです。iOS開発では、多くの場合、使用するライブラリには.frameworkという接尾辞が付いています。フレームワークは静的ライブラリでも動的ライブラリでもかまいません。フレームワーク自体はパッケージ化メソッドです。コードを書くときは「ソースコード」を書くので、コンピューターがこれらのソースコードを理解するには、コンパイラーがソースコードをコンパイルして、コンピューターが理解できる「マシンコード」にコンパイルする必要があります。静的ライブラリまたは動的ライブラリが.oファイルのコレクションであるかどうかに関係なく、.oのソースファイルはバイナリ.oファイルにコンパイルされます。開発者が.oファイルで構成されるライブラリファイルだけを持つだけでは不十分です。開発中にヘッダーファイルなしでライブラリ内のメソッドを簡単に呼び出すことは不可能です。したがって、ライブラリを変換するにはヘッダーファイルも必要です。提供されるインターフェイスインターフェイスのが公開され、場合によっては他のリソースが必要になることがあります。たとえば、ページに関連するライブラリには、いくつかの組み込みの画像リソースなどがあります。フレームワークの機能は、ライブラリファイル、ヘッダーファイルをパッケージ化することです。 、とリソースファイルを一緒に使用すると便利です。次の図は、フレームワークファイルとライブラリファイルの関係を示しています。
2.静的ライブラリを作成します
静的ライブラリをより深く理解する前に、静的ライブラリエクスペリエンスを作成できます。最初に、次の図に示すように、Xcodeを使用して新しいプロジェクトを作成し、Frameworkを選択します。
作成されたフレームワークプロジェクトテンプレートは、プロジェクトと同じ名前のヘッダーファイルとResourcesリソースフォルダーを生成します。新しい機能クラスファイルを作成できます。たとえば、MyLogという名前の新しいクラスとMyToolクラスを作成できます。次のとおりです。
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
デフォルトで生成されるライブラリヘッダーファイルでは、次の2つの関数ヘッダーファイルが導入されています。
#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"
フレームワークをビルドする前に、フレームワークをダイナミックダイナミックライブラリまたは静的ライブラリとしてビルドするように設定できます。まず、フレームワークを静的ライブラリとしてビルドし、コンパイルオプションのMach-oTypeを静的ライブラリに設定します。
その後、次の図に示すように、Xcodeをビルドさせて、生成されたフレームワークファイルを対応するProductsフォルダーに見つけることができます。
このフレームワークファイルのパッケージコンテンツを見ると、次の5種類のファイルがあることがわかります。
その中で、フレームワークの署名ファイルは_CodeSignatureに保存されます。
ヘッダーファイルはヘッダーに保存されます。フレームワークプロジェクトをコンパイルするときは、公開する必要のあるヘッダーファイルをパブリックに設定する必要があることに注意してください。
Info.plistファイルは、現在のフレームワークの構成ファイルです。
Modulesのmodulemapファイルは、LLVMモジュールマップを管理し、コンポーネント構造を定義するために使用されます。
次に、この静的ライブラリの使用を試み、Xcodeを使用してLibDemoという名前の新しいiOSプロジェクトを作成し、このプロジェクトに以前にビルドされたMyStatic.frameworkファイルをドラッグして、プロジェクトのコンパイルオプションでFrameworkSearchPathsとHeaderを検索パスで見つけます。次の図に示すように、フレームワークのパスとヘッダーファイルのパスをそれぞれ構成します。
テストプロジェクトのViewController.mファイルを次のように変更します。
#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
コードを実行すると、コンソールから静的ライブラリが正しく機能していることがわかります。上記のヘッダーファイルをインポートする方法は非常に醜いと思うかもしれません。以下に示すように、プロジェクトに新しいフォルダーを作成し、フレームワークパッケージにヘッダーファイルをコピーできます。
このようにして、プロジェクト内のヘッダーファイルを参照するようなフレームワークの関数を使用できます。
#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.ダイナミックライブラリを試してください
静的ライブラリを構築して使用するプロセスは非常に簡単なようで、動的ライブラリも同様である必要があります。今すぐ試してみましょう。Xcodeを使用してMyDylibという名前の新しいフレームワークプロジェクトを作成し、コンパイルオプションのMach-Oタイプをダイナミックライブラリに変更して、次のようにいくつかの簡単なテストクラスを作成します。
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
同様に、ビルドされたフレームワークファイルをテストプロジェクトにドラッグし、ヘッダーファイルのパスを構成して、次のようにテストコードを追加します。
#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];
}
コンパイルして実行してみてください。これまでのところ、すべてが正常に見えますが、プログラムを実行するとクラッシュし、コンソールから次の情報が出力されます。
dyld[72035]: Library not loaded: @rpath/MyDylib.framework/MyDylib
この例外の理由は、ダイナミックライブラリファイルが見つからなかったためです。スタティックライブラリのダイナミックライブラリとダイナミックライブラリの違いが現れました。この問題の解決方法は実際には非常に簡単です。次の図に示すように、現在のテストプロジェクトをクリックしてパッケージの内容を表示し、その中に新しいFrameworksフォルダーを作成して、MyDylib.frameworkファイルをコピーします。
プロジェクトを再度実行すると、プログラムが正常に実行できることがわかります。ただし、ダイナミックライブラリを実行可能ファイルに手動でコピーする操作は非常にエレガントではありません。プロジェクトでダイナミックライブラリを本当に使用したい場合は、自動スクリプトを使用してライブラリファイルをコピーする手順を実現することがよくあります。
これらの実践を通して、静的ライブラリと動的ライブラリの違いを感じることができるようですが、違いは何ですか?私たちは質問を続けて探求します。
4.静的ライブラリと動的ライブラリの違い
Ⅰ。ロードする方法
以前のダイナミックライブラリが見つからない理由は、実際にはダイナミックライブラリとスタティックライブラリの読み込み方法が異なるためです。
静的ライブラリ:静的ライブラリがリンクされると、実行可能ファイルに完全にコピーされます。同じ静的ライブラリを使用するアプリケーションが複数ある場合は、各アプリケーションのバイナリファイルに完全な静的ライブラリコードが含まれます。
ダイナミックライブラリ:プログラムがリンクされている場合、ダイナミックライブラリはバイナリファイルにコピーされませんが、プログラムの実行中にプログラムが呼び出すために、システムによってメモリに動的にロードされます。この機能により、ダイナミックライブラリシステムは1回だけロードして、アプリケーションで共有できます。
静的ライブラリの動的ライブラリのロード方法については、さらに一歩進むことができます。まず、静的ライブラリが実行可能ファイルに完全にコピーされます。ここでの完全性は実際には不正確です。サードパーティのライブラリを導入する場合、プロジェクトのその他のリンカーフラグで-Objcオプションを構成する必要があります。この項目の機能リンク最適化を設定します。
デフォルトでは、静的ライブラリがリンクされている場合、すべてのコードが実行可能ファイルにコピーされるのではなく、使用されたコードのみがコピーされるため、最終的なアプリケーションパッケージのサイズが縮小されますが、OC言語のダイナミズムによって決定されます。コードを直接参照する必要はなく、この接続方法は実行時の問題を引き起こすことがよくあります。
-Objcオプションを設定した後、リンカは、コードが使用されているかどうかに関係なく、すべてのOCクラスとそれに対応するカテゴリをロードします。
-all_loadオプションを設定すると、リンカはOCファイルだけでなく、すべてのオブジェクトファイルをロードします。
-force_loadパラメーターを設定すると、静的ライブラリーのすべてのオブジェクト・ファイルを強制的にロードするように指定できます。この静的ライブラリーの場合、効果は-all_loadと同じです。
ダイナミックライブラリの場合、ダイナミックライブラリは実行時に読み込まれ、リンカはどのコードが使用されるかわからないため、このような最適化を行う方法はありません。サイズの最適化はダイナミックライブラリよりも優れているようですが、それはそうです。本当にそうですか?最初に伏線を残し、後で分析します。
Ⅱ.ファイル構造が異なります
静的ライブラリと動的ライブラリの本質的な違いは、ビルドされたファイルの構造が完全に異なることです。ライブラリファイルは、MachOViewツールを使用して表示できます。
最初に静的ライブラリについて説明しましょう。MachOViewによって開かれる静的ライブラリの構造は次のとおりです。
静的ライブラリの構造は実際には比較的単純であることがわかります。ライブラリ自体の一部の記述ファイルとシンボルテーブルを除いて、基本的には他の実行可能ファイルのコレクションです。図からわかるように、各実行可能ファイルいくつかのヘッダーデータがあります。これらのヘッダーデータは、実行可能ファイルの名前、サイズ、およびその他の情報を記録します。さまざまなコードセグメント、データセグメント、および私たちが精通しているその他のデータを含む実行可能ファイルをクリックできます。
ダイナミックライブラリをもう一度見てみましょう。その構造は次のとおりです。
ダイナミックライブラリ自体が実行可能ファイルであり、すべての内部.oファイルの単純なコレクションではなく、最終的にリンクされるイメージファイルであることがわかります。動的ライブラリは実行時にリンクされるため、コンパイル時に最適化できず、アプリケーションパッケージのサイズが大きくなる可能性がありますが、実際のアプリケーションでは、ほとんどの場合、-Objcパラメーターを使用して、静的ライブラリにすべてのOCファイルをリンクさせます。静的ライブラリの各.oファイルにはヘッダー情報がありますが、動的ライブラリでは情報のこの部分が省略されているため、最終的に、静的ライブラリはアプリケーションパッケージのサイズに影響を与えるという点で必ずしも優れているとは限りません。ただし、1つ確かなことは、静的ライブラリはコンパイル時にリンクされるため、アプリケーションの起動時間を節約できることです。多くの場合、最適化プロジェクトを行う場合、決まった計画はありません。実際の状況に応じて最適な計画を選択する必要があります。
5.ダイナミックライブラリとランタイム
Ⅰ。ダイナミックライブラリのロード
ランタイムに関しては、開発者がやるべきことがたくさんあります。まず、前のテストプロジェクトについて考えてみましょう。ダイナミックライブラリファイルをIPAパッケージにコピーしないと、プログラムを実行してもライブラリファイルが見つからないのはなぜですか。また、ダイナミックライブラリをIPAパッケージのFrameworksフォルダーにコピーする必要があるのはなぜですか?他のフォルダはありませんか?
上記の問題を説明するには、ダイナミックライブラリの読み込みの原則を確認する必要があります。次の図に示すように、MachOViewを使用して、テストアプリケーションパッケージの実行可能ファイルを開き、その中の[読み込みコマンド]セクションを見つけることができます。
ダイナミックライブラリのロード手順がいくつかあることがわかります。Foundation、UIKitなどはすべてシステムのダイナミックライブラリです。詳細なロードパスは、次のように詳細に確認できます。
独自のMyDylibライブラリの場合、ロードパスは次のとおりです。
このダイナミックライブラリはパス@rpath/ MyDylib.framework / MyDylibからロードされていることがわかります。このロードパスの設定は、ダイナミックライブラリのコンパイル時に決定されています。コンパイルされたMyDylibプロジェクトを見ることができます。 Xcodeオプションで構成されている場合は、以下に示すように、[ダイナミックライブラリのインストール名]オプションを見つけます。
ここでの@rpathは、実際には環境変数です。アプリケーションプロジェクトで@rpathの値を構成できます。LibDemoプロジェクトのコンパイルオプションでrpathを検索すると、この環境変数の構成を確認できます。
これで、ダイナミックライブラリファイルをFrameworksフォルダに配置する必要がないことがわかりました。@ rpath変数のパスを変更して、ダイナミックライブラリのロードパスを変更します。
ダイナミックライブラリのこのロード方法では、原則として、バイナリファイルのロードパスを変更するか、パッケージ内のダイナミックライブラリファイルを直接置き換えて、いくつかのリバースインジェクション機能を実現できます。これは非常に優れています。
Ⅱ.コードローディングダイナミックライブラリ
ダイナミックライブラリは実行時にロードされます。また、コードを使用して、実行時にダイナミックライブラリのロードを動的に制御することもできます。テストプロジェクト内のMyDylibへのすべての参照を削除し、構成されたヘッダーファイルパスも削除できます。次のように、このダイナミックライブラリをプロジェクトのバンドルにコピーします。
ViewControllerクラスを変更するコードは次のとおりです。
#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
この時点で、プロジェクトをコンパイルして再度実行します。テストプロジェクトのバイナリファイルを確認すると、loadコマンドにMyDylibがロードされていませんが、プログラムは正常に実行できます。dlopen関数の機能はロードです。実行時のダイナミックリンク。ライブラリが正常にロードされたら、OCのランタイムメソッドを使用して、ダイナミックライブラリ内のコードを直接呼び出すことができます。このようにして、プラグインの動的ダウンロードと使用を実際に実現できるため、アプリケーションは非常に高いホットアップデート機能を備えていますが、動的ライブラリを動的にダウンロードする方法は許可されていないことに注意してください。 AppStoreに置くと、アプリをテストするか、企業アプリでのみ使用できます。
さらに一歩進んで、実際、ダイナミックライブラリは必ずしもローカルサンドボックスから読み取られるとは限りません。ローカルでデバッグする場合、任意の場所からダイナミックライブラリファイルを読み取ってロードできます。これにより、インジェクションなどの多くの非常に優れた機能をローカルで実現できます。ツールは、サービスを介してコードファイルの変更を監視し、それをダイナミックライブラリにパッケージ化してプログラムに挿入し、実行時にクラスとメソッドを置き換えて、ローカルで開発されたiOSプロジェクトのホットアップデート効果を実現します。使いやすい。
テクノロジーに焦点を合わせ、愛を理解し、喜んで共有し、友達になりましょう
QQ:316045346