Objective-C、静的ライブラリ、カテゴリ、-ObjCなど...

すべての人がSwiftで完全にアプリケーションを作成できたわけではなく、iOS 8以降でも幸運でした。 Objective-Cには多くの遺産があり、多くの依存関係がすべてペンで静的、またはココアポッド、またはカルタゴを通過します。 私たちはクールな開発者ですので、厳密にDRYに従い、楽しいプロジェクトをすべて別のプロジェクトまたは静的ライブラリに転送します。 ここで、クールなAPIと同等以上のクールな静的ライブラリを作成し、社内のチームメイトと共有したい場合を考えてみましょう-任意のwikiリソース/ gitaに任意のヘッダーと、もちろんAPI全体が記述されているreadmeにアーカイバを配置しますそれを使用します。



たとえば、1つのクラスとそのカテゴリを考えます











スクリーンショットでは、プロジェクトの構造を示しています。クラス+クラスはカテゴリであり、すべてが単純です。 通常の方法でアセンブルし、APIの説明とともにreadme.mdを記述し、ライブラリをアーカイブします。 すべてがクールで、ウィキにアップロードされ、少年たちはスラック/スカイプなどでツイートし、別のコーヒーを飲みに行きました。 brewれたてのコーヒーで座って、マウスカーソルがハブのブックマークに近づいたところです。一部のログはチャットに落ち、問題は新しく凍結されたライブラリにあるため、誰もがすぐに答える必要があります。 テスト範囲は146%であり、すべてが二重にチェックされているため、汗をかかれました。 同時に、チャットでは、同じエラーログがPMに再び書き込まれます。



*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Deadpool guns]: unrecognized selector sent to instance 0x7ffecbc12df0'
      
      







ログに精通した後、静的ライブラリを頻繁に使用する場合、その理由は明確で痛みを覚えています。 問題を理解したら、自信を持って額から汗を拭き取り、以前に送信したreadme.mdを開いて追加します。



Xcodeのスキームのビルド設定で「その他のリンカーフラグ」に「-ObjC」フラグを追加することを忘れないでください。



その後、Wikiを更新し、全員に再度通知したところ、すべてが落ち着いたように見え、メッセンジャーは沈黙し、コーヒーは冷める時間すらありませんでした。 「さて、間違いなく誰も私を置き去りにすることはありません」-あなたの内なる声はささやき、マウスカーソルは再び大切なブックマークに伸びます(nenen、ただのhub声!)。 マウスの左ボタンをクリックするだけで、目的のボタンから分離されますが、「このエラーは回避できましたか、または今後防止する方法はありますか?!」 「はい、すべてを地獄に落とすために!」内なる声を叫び、Terminal.appのカーソルに到達しました。



 otool -tV -arch x86_64 libDeadpool.a
      
      







生成するもの:



 Archive : libDeadpool.a libDeadpool.a(Deadpool+Guns.o): (__TEXT,__text) section -[Deadpool(Guns) guns]: 0000000000000000 pushq %rbp 0000000000000001 movq %rsp, %rbp 0000000000000004 subq $0x10, %rsp 0000000000000008 leaq 0x71(%rip), %rax ## Objc cfstring ref: @"sword 2" 000000000000000f movd %rax, %xmm0 0000000000000014 leaq 0x45(%rip), %rax ## Objc cfstring ref: @"sword 1" 000000000000001b movd %rax, %xmm1 0000000000000020 punpcklqdq %xmm0, %xmm1 ## xmm1 = xmm1[0],xmm0[0] 0000000000000024 movdqa %xmm1, -0x10(%rbp) 0000000000000029 movq 0x70(%rip), %rdi ## Objc class ref: NSArray 0000000000000030 movq 0x91(%rip), %rsi ## Objc selector ref: arrayWithObjects:count: 0000000000000037 leaq -0x10(%rbp), %rdx 000000000000003b movl $0x2, %ecx 0000000000000040 callq *_objc_msgSend(%rip) 0000000000000046 addq $0x10, %rsp 000000000000004a popq %rbp 000000000000004b retq libDeadpool.a(Deadpool.o): (__TEXT,__text) section -[Deadpool name]: 0000000000000000 pushq %rbp 0000000000000001 movq %rsp, %rbp 0000000000000004 movq 0x1d(%rip), %rsi ## Objc selector ref: class 000000000000000b callq *_objc_msgSend(%rip) 0000000000000011 movq %rax, %rdi 0000000000000014 popq %rbp 0000000000000015 jmp _NSStringFromClass
      
      







うーん、lib自体にすべてのメソッドが配置されているので、アプリケーションのソースを見てみましょう。



 otool -tV -arch x86_64 DemoApp.app/DemoApp | grep Deadpool
      
      







生成するもの:



 0000000100001ae8 movq 0x21f1(%rip), %rdi ## Objc class ref: Deadpool 0000000100001af6 movq 0x1513(%rip), %r12 ## Objc message: +[Deadpool new] -[Deadpool name]:
      
      







WTF! OK Google、カテゴリのメソッドはどこにありますか?



Googleは、この非常に問題のあるhttps://developer.apple.com/library/mac/qa/qa1490/_index.htmlについての電子メールのドキュメントへのリンクを巧みに手抜きしています。



リンカー



Cプログラムがコンパイルされると、各ファイル(.c)はいわゆる「オブジェクトファイル」(.o)にコンパイルされます。このファイルには、関数やその他の静的情報の実装が含まれます。 リンカがこれらすべてのファイルを1つの最終ファイル-実行可能ファイルに収集した後。 そして、この実行可能ファイルは、Xcodeを介して.app内に入ります。



しかし、ソースファイル(.c)が何か、たとえば別のファイル(別の.cファイル)で定義されている関数を使用する場合、「未定義のシンボル」がこのコードの.oファイルに書き込まれます。 また、アセンブリ段階では、リンカには「未定義のシンボル」から最終的な実行可能ファイルをアセンブルするために不足しているものを取得する場所を理解するのに十分な情報があります。 この説明は、UNIX静的ライブラリを構築するためのものです。




Objective-c



言語の動的な性質により、Objective-Cのこのプロセスは少し複雑です。メソッドの実装の検索は、このメソッドの呼び出し後にのみ行われるためです。 Objective-Cは、リンカーメソッドの補助シンボルを定義せず、クラスのシンボルのみを定義します。 たとえば、クラス/ファイルmain.oにはコードがあります。



[[FooClass alloc] initWithBar:nil]



つまり、FooClassは別のFooClass.oファイル内の別のクラスであるため、main.oにはFooClass自体の「未定義シンボル」のみが含まれ、このクラスの-initWithBar:メソッドの追加シンボルは含まれません。



カテゴリはメソッドを備えた別個のファイルであるため、リンカには、このファイルをリンクする必要があるという情報がまったくありません。メソッドについては、リンカの「未定義シンボル」に対して補助的なものは作成されません。




そのため、バイトコードをもう一度見てみましょう。



 Archive : libDeadpool.a libDeadpool.a(Deadpool+Guns.o): (__TEXT,__text) section -[Deadpool(Guns) guns]: 0000000000000000 pushq %rbp 0000000000000001 movq %rsp, %rbp 0000000000000004 subq $0x10, %rsp 0000000000000008 leaq 0x71(%rip), %rax ## Objc cfstring ref: @"sword 2" 000000000000000f movd %rax, %xmm0 0000000000000014 leaq 0x45(%rip), %rax ## Objc cfstring ref: @"sword 1" 000000000000001b movd %rax, %xmm1 0000000000000020 punpcklqdq %xmm0, %xmm1 ## xmm1 = xmm1[0],xmm0[0] 0000000000000024 movdqa %xmm1, -0x10(%rbp) 0000000000000029 movq 0x70(%rip), %rdi ## Objc class ref: NSArray 0000000000000030 movq 0x91(%rip), %rsi ## Objc selector ref: arrayWithObjects:count: 0000000000000037 leaq -0x10(%rbp), %rdx 000000000000003b movl $0x2, %ecx 0000000000000040 callq *_objc_msgSend(%rip) 0000000000000046 addq $0x10, %rsp 000000000000004a popq %rbp 000000000000004b retq libDeadpool.a(Deadpool.o): (__TEXT,__text) section -[Deadpool name]: 0000000000000000 pushq %rbp 0000000000000001 movq %rsp, %rbp 0000000000000004 movq 0x1d(%rip), %rsi ## Objc selector ref: class 000000000000000b callq *_objc_msgSend(%rip) 0000000000000011 movq %rax, %rdi 0000000000000014 popq %rbp 0000000000000015 jmp _NSStringFromClass
      
      







実際、2つのファイルDeadpool.oDeadpool + Guns.oをコンパイルしました。2番目のファイルは最初のファイルの単なるセットであるため、リンカはそれについて何も知らず、したがってこのエラーは実行時にのみ発生します。



最初の解決策は、カテゴリをメインクラスファイルに転送することです。 はい、動作します:)が、私たちにとってはあまり便利ではありません。すべてのカテゴリを順番に別々のフォルダに保存するのに慣れているからです。



別の解決策。 libを使用するユーザーは、「その他のリンカーフラグ」で-ObjCフラグを指定する必要があります。このフラグは、静的ライブラリからすべてのものをロードするようリンカーに指示します。 まあ、この決定は私たちに合っています。なぜなら、私たちは私たちの側で何も支配する必要がないからです。 しかし、あなたがそれについて考えると、開発者がたくさんのライブラリを接続し、私たちのためだけにこのフラグを追加する必要がある場合、彼はアプリケーションのかなりの重量増加を得ることができます(私は推測します)。



クラスとそのカテゴリを1つのファイルにまとめるために、リンカーにこのようなことを言うことは可能ですか? そのため、pbxprojファイルには「Perform Single-Object Prelink」または「GENERATE_MASTER_OBJECT_FILE」という名前があります。 真実は、クラスとそのカテゴリを組み合わせて1つのファイルにするだけでなく、すべてのプロジェクトファイルが1つの「オブジェクトファイル」として作成されることです。 この値がtrueに設定されている場合、必要な動作を取得する必要があります。 ご覧ください。



公開します:







 otool -tV -arch x86_64 libDeadpool.a
      
      







私達は得る:



 Archive : libDeadpool.a libDeadpool.a(libDeadpool.a-x86_64-master.o): (__TEXT,__text) section -[Deadpool(Guns) guns]: 0000000000000000 pushq %rbp 0000000000000001 movq %rsp, %rbp 0000000000000004 subq $0x10, %rsp 0000000000000008 leaq 0x149(%rip), %rax ## Objc cfstring ref: @"sword 2" 000000000000000f movd %rax, %xmm0 0000000000000014 leaq 0x11d(%rip), %rax ## Objc cfstring ref: @"sword 1" 000000000000001b movd %rax, %xmm1 0000000000000020 punpcklqdq %xmm0, %xmm1 ## xmm1 = xmm1[0],xmm0[0] 0000000000000024 movdqa %xmm1, -0x10(%rbp) 0000000000000029 movq 0x270(%rip), %rdi ## Objc class ref: NSArray 0000000000000030 movq 0x259(%rip), %rsi ## Objc selector ref: arrayWithObjects:count: 0000000000000037 leaq -0x10(%rbp), %rdx 000000000000003b movl $0x2, %ecx 0000000000000040 callq *_objc_msgSend(%rip) 0000000000000046 addq $0x10, %rsp 000000000000004a popq %rbp 000000000000004b retq -[Deadpool name]: 000000000000004c pushq %rbp 000000000000004d movq %rsp, %rbp 0000000000000050 movq 0x241(%rip), %rsi ## Objc selector ref: class 0000000000000057 callq *_objc_msgSend(%rip) 000000000000005d movq %rax, %rdi 0000000000000060 popq %rbp 0000000000000061 jmp _NSStringFromClass
      
      







彼らが望んだものは、今ではすべてが1つのファイルlibDeadpool.a-x86_64-master.oにあります。 アプリケーションから-ObjCを削除し、新しいバージョンのライブラリで再構築して、次のように表示します。



 otool -tV -arch x86_64 DemoApp.app/DemoApp | grep Deadpool
      
      







結論:



 0000000100001a70 movq 0x22c9(%rip), %rdi ## Objc class ref: Deadpool 0000000100001a7e movq 0x158b(%rip), %r12 ## Objc message: +[Deadpool new] -[Deadpool(Guns) guns]: -[Deadpool name]:
      
      







素晴らしい。 これで、 readme.mdから-ObjCフラグに関する情報を削除し、大胆にハブを開いて、残念ながら既に冷却されたコーヒーを仕上げることができます)



追伸



問題は古く、私はずっと前にそれを解決しました、今、私はより詳細にそれを書き、理解するために私の手を得ました)

理想的な解決策はわかりませんが、この問題の解決に役立ちました。誰かが興味を持つかもしれません。



便利なリンク:



https://developer.apple.com/library/mac/qa/qa1490/_index.html

http://stackoverflow.com/questions/2567498/objective-c-categories-in-static-library



All Articles