Objective-Cをゼロから

Apple製品のプログラムを自分の人生で書きたいと思う人は誰でも、新しいプログラミング言語であるObjective-Cを学ばなければならないときが来ます。 この幸せな瞬間が私を理解しました。 そして、この言語の主な特徴をよりよく覚えるために、私はあなたと共有するドキュメントを理解しながら私の考えを概説することにしました。

OOPの出現の一般的な理論


記述されたコードとその移植性を再利用する問題により、プログラマーは、それを整理、構造化、および抽象化するこれまでにない方法を常に探しています。 これらの問題を解決するために、新しいプログラミングパラダイム、設計パターン、新しい言語、コンパイラーおよびそれらの標準ライブラリ、ソフトウェアプラットフォーム、フレームワークが作成されます。 これは、サブプログラム(手順)のパラダイムが形成され、CALL \ RETプロセッサ命令とスタックの助けを借りて実装された方法です(実際、現在のコマンドに続くものではなく、実行スレッドを任意のアドレスに転送してからリターンします)。 次に、モジュールパラダイム(各ファイルは個別の翻訳単位)により、モジュールのコンパイル、および実行可能モジュールへのアセンブリ(静的または動的)の2段階の翻訳が行われました。

プロジェクトのコード量の増加とそれをサポートする難しさにより、1960年代に新しいオブジェクト指向プログラミングパラダイムが形成され始め、プログラムはさらに小さなコンポーネント(データ型)に分割されました。 その本質は、相互にメッセージを送信することによるエンティティ(オブジェクト)の相互作用にあります。 各オブジェクトは、プログラマーによって定義されたデータ型(いわゆるクラス)の変数です。 このような特別なユーザーデータ型(クラス)の定義は、データセット(不変式、メンバー)の定義と、それらを提供するルーチンセット(メソッド)の2つで構成されます。


クラスは通常、組み込み(言語)データ型および/または他のクラスに基づいて、プログラマーによって定義された型としてフォーマットされます。 オブジェクト指向のパラダイムをサポートしないC言語の場合、これは構造体である場合があります。 ルーチンのセットは通常の関数として実装され、少なくとも1つのパラメーター(処理されるデータのセットへのポインター)を必ず必要とします。

オブジェクト指向のアプローチの主な利点は、継承と呼ばれる、すでに記述されたクラス(不変式とメソッドの追加、メソッドの再定義、基本クラスで定義されたメソッドの使用)に基づいて新しいクラスを作成できることでした。


メソッドのセットは、不変式と対話するためのインターフェースです。 (インターフェイスを使用せずに)クラスデータを直接変更できないことは、カプセル化の原則を反映しています。 この図は、クラスとそのオブジェクトを示しています。 float型の不変式xと、不変式の値を返すインターフェイス(メソッド)doubleXがあります。

間違いなくそれに応答するオブジェクトにメッセージを送信する必要がある(つまり、クラスオブジェクトに実装したメソッドを呼び出す)必要がありますが、状況によっては、このオブジェクトの特定のクラスは不明です。 たとえば、Autoクラスのオブジェクトへのポインターのリストの各要素にはMoveメッセージを送信する必要がありますが、リストにはAutoクラスだけでなく、派生(継承)FordおよびSubaruクラスへのポインターも含まれていることがわかっています。 これは、すべてのオブジェクトがそのようなメッセージを受信できるクラスの特定の階層から特定のメッセージがオブジェクトに送信されると、このオブジェクトはこの階層の基本クラスではなく、自身のクラスに従って反応するという事実にあるポリモーフィズムの原理のおかげでのみ行うことができます。

オブジェクト指向のアプローチをサポートする最初の言語はSimula67でした。 その後、Smalltalkが登場しました。 そして、80年代にC ++が形になり始めました-現代のシステムプログラミングの主要言語です。 90年代の拡張と改善により、多くのパラダイムとデザインパターンが生成され、Objective-C言語を含むオブジェクト指向アプローチの現代のビジョンに不可逆的な影響を与えました。

ちょっとした歴史


Objective-Cは、80年代にSmalltalkに対するCの修正として生まれました。 さらに、この変更は、新しい構文構造と特別なプリプロセッサ(コードを通過して通常のC関数呼び出しに変換)とランタイムライブラリ(これらの処理呼び出し)を追加することで構成されていました。 したがって、当初、Objective-CはCのアドオンとして認識されていました。ある意味では、これは依然として当てはまります。純粋なCでプログラムを作成し、Objective-Cからいくつかのコンストラクトを(必要に応じて)追加できます。それどころか、Objective-CのプログラムでCを自由に使用できます。 さらに、これはすべてC ++プログラムに適用されます。 1988年、NeXT(およびそれ以降のApple)はObjective-Cのライセンスを取得し、そのためのコンパイラと標準ライブラリ(基本的にSDK)を作成しました。 1992年、GNUプロジェクトの開発者がOpenStepプロジェクトに参加して、言語とコンパイラを改善しました。 それ以来、GCCはObjective-Cをサポートしています。 NeXTを購入した後、AppleはSDK(コンパイラ、ライブラリ、IDE)をさらなる開発の基礎としました。 コードのIDEはXcodeと呼ばれ、GUIの場合はInterface Builderと呼ばれていました。 GUI開発用のCocoaフレームワーク(だけでなく)は、Objective-Cプログラムの最も重要な開発環境です。

Objective-Cの機能


Objective-Cモジュールファイルの拡張子は「.m」です(C ++とObjective-Cの混合が使用された場合、拡張子は「.mm」です)。 ヘッダーファイルは「.h」です。 Objective-Cで作成されたすべてのクラスオブジェクトは、動的メモリに割り当てる必要があります。 したがって、特に重要なのはidタイプであり、これは任意のクラスのオブジェクトへのポインターです(基本的にvoid *)。 NULLポインターは、nil定数と呼ばれます。 したがって、任意のクラスへのポインターをid型にキャストできます。 問題があります:idの下に隠れているオブジェクトがどのクラスに属しているかをどのように知るのですか? これは、特別な基本クラスNSObjectを継承するクラスのオブジェクトに存在するisa不変式のおかげで行われます(NSプレフィックスはNeXT Stepを表します)。 isa不変式は予約型Classに属します。 このタイプのオブジェクトを使用すると、独自の基本クラスの名前、クラス不変式のセット、およびこのオブジェクトが実装したすべてのメソッドのプロトタイプとそのアドレスを(セレクターのローカルリストを介して)見つけることができます。 Cの予約語とは異なるすべてのObjective-Cの予約語は、@で始まります(たとえば、@ protocol、 selectorinterface )。 通常、限定されたスコープ(@ private、 protected )を持つクラスの不変式の名前はアンダースコアで始まります。 Cocoaには、文字列用の非常に便利なNSStringクラスがあります。 このクラスの文字列定数は、Cで通常使用される文字列定数「Hello world」ではなく、@「Hello world」として記述されています。 BOOL型(本質的に符号なしchar)は、YESおよびNOの定数値を取ることができます。 Objective-C固有のすべての予約語(Cとは異なり、objc / objc.hヘッダーファイルにあります)は以下のとおりです。

メッセージング


オブジェクトに何らかのメソッドを強制的に実行させるには、必要なメソッドと同じ名前のメッセージを送信する必要があります。 このようなメッセージはメソッドセレクタと呼ばれます。 パッケージの構文は次のとおりです。

[receiver method]; 


メッセージでは、呼び出されたメソッドにパラメーターを渡すことができます。

 [receiver method: 20.0 : 30.0]; 

各パラメーターの前にはコロンを付ける必要があります。 コロンの数-非常に多くのパラメーター。 メソッド名は、各コロンパラメーターの後に続けることができます。

 [receiver methodWithFirstArgument: 10 andSecondArgument: 20]; 

引数の数に制限のないメソッドは、次の構文で呼び出されます。

 [receiver undefinedNumberParameters: one, two, three, four, five, six, seven]; 

C関数と同様に、メッセージを送信すると、特定の(おそらくvoid)値が返されます。

 BOOL booleanValue; booleanValue = [reveiver method]; 

nilメッセージを送信すると、メッセージは消えます。 順序付けされたメソッドを実装しなかったクラスに属するオブジェクトにメッセージを送信すると、例外が発生し、キャッチされない場合、プログラム全体が計画外の終了につながります。 特定のオブジェクトがメッセージに応答するかどうかを確認するには、次のコードテンプレートを使用できます。

 if ([anObject respondsToSelector: @selector(myMethodWith2Argumets::)]) { //  [anObject myMethodWith2Argumetns: @”first” : @”second”]; } else { //      } 


メッセージングの仕組み


メッセージの送信は、プロトタイプとともにC関数でブロードキャストされます。

 id objc_msgSend(id receiver, SEL method, ...); 

実際、SELタイプはchar const *として定義されていますが、実行時にすべてのセレクターはグローバルセレクターテーブルに従って整数値でインデックス付けされるため、intとして解釈する方が適切です。


レシーバーオブジェクトのisa不変式を使用する(Cocoaの基本であるFoundationフレームワークを使用する場合、すべてのクラスはNSObjectクラスを継承する必要があるため、isaの存在は避けられない)、この関数はクラスセレクターのローカルリストをスキャンして、このクラスのオブジェクトがメソッドメッセージに応答するかどうかを判断します。 そのようなセレクターが見つかった場合、制御は、オブジェクトID(その不変式へのポインター)と、セレクターの後に指定されたobjc_msgSend()関数のパラメーターが渡されるクラスの対応するメソッドに転送されます。 メソッドによって返される値は、メッセージの送信結果として返されます。 レシーバーオブジェクトにこのセレクターがない場合、objc_msgSend()関数はその基本クラスのセレクターのリストをスキャンします。


このスキームでは、呼び出し、たとえば:

 [receiver ddObject: otherObject]; 

放送先:

 objc_msgSend(receiver, 12, otherObject); 

グローバルセレクタテーブル12では、文字列「addObject:」に対応しているためです。 次に、objc_msgSend()関数はレシーバーオブジェクトのセレクターのリストを検索し、それを見つけて(セレクター12でメソッドを実装するNSArrayクラスのオブジェクトとする)、次のタイプの呼び出しを行います。

 addObject(receiver, otherObject); 


メソッド宣言


クラス宣言の前のセクションのaddObjectメソッドのプロトタイプが次のようになったことに注意してください。

 - (void)addObject: (id)otherObject; 

つまり、必要なパラメーターは1つだけでした。 ただし、メソッドは特定のデータセットを処理するルーチンであるというオブジェクト指向のパラダイムの原則に基づいて、メソッドは処理するデータのアドレスを渡す必要があります。 したがって、このようなパラメータは暗黙的にクラスメソッドに渡されます。 この追加パラメーターに関するコンパイラーは、メソッドのプロトタイプの最初のマイナス( "-")をクリアします。 そのようなメソッド(前にマイナス記号が付いている)は、オブジェクト(またはインスタンス)メソッドと呼ばれます。 あるクラスのオブジェクトでのみ呼び出すことができます。 メソッドの本体では、データインスタンス(またはメッセージの送信先のオブジェクトのアドレス)へのこのポインターは予約語self(C ++のこれに類似)からアクセスでき、基本クラスインスタンスへのポインターは予約語superからアクセスできます。 さらに、グローバルセレクタテーブルのこのメソッドのセレクタである暗黙のパラメータ_cmdもオブジェクトメソッドに渡されます。 C ++プログラマーの観点から見ると、Objective-Cのすべてのオブジェクトメソッドは、仮想キーワードで宣言されているように見え、常に動的なポリモーフィズムに従います。

プロトタイプメソッドの先頭にプラス記号(「+」​​)を付けると、そのようなメソッドはクラスメソッドと見なされ、もちろん、暗黙的なパラメーターselfを受け入れません(これはC ++で静的メソッドを宣言するのと似ています)。 そして、selfが指すオブジェクトのisa不変量がなければ、スーパーポインターも確かに機能しません。
したがって、メソッドのプロトタイプは次のように宣言されます。

 -|+ (<  >)  [ : (<  >) [ [] : (<  >)] … ] 

例:

 + (Class)class; + (id)alloc; - (id)init; - (void)addObject: (id)anObject; + (NSString *)stringWithCString: (const char*)aCString usingUncoding: (enum NSStringEncoding)encoding; - (NSString *)initStringWithFormat: (NSString *)format, …; 

メソッドがオブジェクト(idタイプ)またはクラス(Classタイプ)を返す場合、ネストされた呼び出し構文を使用できます。

 [myLabel setText: [[NSString stringWithString: @”Hello”] stringByAppendingString: @” world”]]; 

ここで、UIKitフレームワークのUILabelクラスのオブジェクトは、文字列@” Hello world”に等しい不変テキストの値に設定されます。 この文字列は、文字列@” Hello”と@” world”を連結して形成されます。 1つ目は、@” Hello”定数パラメーターを使用してNSWithクラスにstringWithStringメッセージを送信した結果です。 このような呼び出しは、パラメーター文字列で初期化されたNSStringクラスのオブジェクトを返します。 次に、@” world”パラメーターを含むstringByAppendingStringメッセージがこのオブジェクトに送信されます。 このメッセージを送信した結果は、レシーバオブジェクトの値と文字列引数の連結を含むNSStringクラスのオブジェクトです。 このオブジェクトは、パラメーターとしてmyLabelオブジェクトのsetText:メッセージにも分類されます。

クラス宣言


Complex.hファイルで単純な複素数クラスを宣言します。

 #import <Foundation/Foundation.h> // NSObject   NSString @interface Complex : NSObject { double _re; //    double _im; //    NSString *_format; //    description } - (id)initWithRe: (double)re andIm: (double)im; //  + (Complex *)complexWithRe: (double)re andIm: (double)im; //      - (Complex *)add: (Complex *)other; //   - (Complex *)sub: (Complex *)other; //   - (NSString *)format; //   _format - (void)setFormat: (NSString *)format; //  _format - (double)re; //        - (void)setRe: (double)re; - (double)im; - (void)setIm: (double)im; @end 

ご覧のとおり、広告全体がキーワードインターフェイスendで囲まれています 。 最初のステップは、不変式を中括弧で宣言することです。 中括弧の外側では、メソッドが宣言されます。 説明メソッドは、理由のためにクラス宣言から欠落しています。 実際には、deallocメソッドやinitメソッドと同様に、クラス定義に存在します。 複雑なクラスのオブジェクトに説明メッセージを送信する場合、セレクターのローカルリストが考慮されます。コンパイル後、このオブジェクトのクラスによって実装され、インターフェイス部分で宣言されていないすべてのメソッドのセレクターがそこに到達します。 つまり、init、description、deallocは絶対に正しく呼び出されます。

オブジェクトを作成する


すべてのオブジェクトは動的メモリに分散されるため、オブジェクトの作成は2段階で実行する必要があります。1)メモリ割り当て(allocメッセージ)および2)不変式の初期化(クラスコンストラクター)。

 MyClass *myObject = [[MyClass alloc] init]; //  MyClass alloc          ,   init    myObject 

オブジェクトを作成したら、安全に使用できます。

 NSMutableArray *array = [[NSMutableArray alloc] init]; //   MyClass *myObject = [[MyClass alloc] init]; //  [myObject myMethod]; //   [array addObject: myObject]; //    MyClass *otherObject = [array getLastObject:]; //   ,      [otherObject myOtherMethod: YES]; //       BOOL 

一部のクラスには、独自のインスタンスを(1ステップで)すばやく作成するためのメソッドがあります。 このようなメソッドはクラスのメソッドであり、そのクラスのオブジェクトへのポインターを返します。通常、その名前はクラス自体の名前で始まります。 たとえば、メソッド:

 + (NSString *)stringWithCString: (char const *)string encoding: (NSStringEncoding)encoding; 

allocおよびinit呼び出しを行わずに、対応する行で終了ゼロで初期化された既製の文字列を返します。

 NSString *myString = [NSString stringWithCString: “Bla-bla-bla” encoding: NSASCIIStringEncoding]; 


オブジェクトの寿命


オブジェクトへのポインタがスコープを超えると、割り当てられたメモリは回復不能なほど失われ(もちろん、そのオブジェクトへの最後のポインタでない限り)、リークが発生します。 このような望ましくない結果を回避するために、Objective-Cはリソースへのリンクをカウントするというパラダイムをサポートしています。 したがって、各オブジェクトには、それへのポインターの数を示す整数カウンターがあります。 このカウンターがゼロに達すると、このオブジェクトに割り当てられたメモリがシステムに返されます。 allocクラスのメソッドを呼び出した後、このカウンターは1に等しくなります。 その値を増やすには、オブジェクトにメッセージを送信する必要があり、保持し、削減する必要があります。 これらのメソッドはすべてNSObjectによって実装されており、どのクラスも確実に継承します。 NSStringクラスの静的オブジェクトのカウンター値(@ "I am a string"など)が-1、つまり可能な最大値であることに注意してください。 カウンターの使用例を次に示します。

 id anObject = [SomeClass alloc]; //  == 1 [anObject init]; //    [anObject reatin]; //   (  == 2) [anObject release]; // (  == 1     ) [anObject release]; // ,   1          

initの実装は非常に重要です。 これはクラスコンストラクターです。 コンストラクターは、idを返し、名前が常にinitで始まるという点で異なり、デフォルトのコンストラクターはinitのみです。 コンストラクタのスキームは、ほぼ次のとおりです。

 - (id)init { self = [super init]; //     //   if (self) //        //    ,      nil { //       } return self; //    } 

クラスの型の2つのメンバーと1つの整数不変式を持つクラスの典型的な特殊な(デフォルトではない)コンストラクターを次に示します。

 - (id)initWithInt: (int)number { if (self = [super init]) { _myMember1 = [[SomeClass alloc] init]; //  :  ,    _myMember2 = [[SomeClass alloc] init]; _myIntMember = number; //     //   } return self; } 

NSObjectのリリースおよび保持の実装は、イデオロギー的にほぼ次のとおりであり、参照カウンター不変式へのアクセスがないため、派生クラスで再定義する必要はありません。

 - (void)retain { [_internalLock lock]; //   _referenceCounter++; //  _referenceCounter –    [_internalLock unlock]; } - (void)release { [_internalLock lock]; _referenceCounter--; //  if (!_referenceCounter) //    { [_internalLock unlock]; [self dealloc]; // ,    (  ) } [_internalLock unlock]; } 

つまり、deallocメッセージはオブジェクト自体に送信されます。このメソッドの実装では、必要に応じて、不変式のカウンターを減らし、同様のメッセージを基本クラスのオブジェクトに渡して、同じことができるようにします。 明らかに、NSObjectにdeallocメソッドを実装すると、オブジェクトに割り当てられたメモリが解放されます。 通常、一部のクラスのdeallocは次のようになります。

 - (void)dealloc { [_myMember1 release]; //    [_myMember2 release]; //     //[_myIntMember release];   , ..           [super dealloc]; //c   ,     } 


アクセス方法


メソッドからオブジェクトのアドレスを返すとき、または仮パラメーターを使用して不変式を初期化するとき、参照カウントの正しい作業は非常に重要です。 通常、このようなことは、オブジェクトの不変式を返し、設定する、いわゆるアクセスメソッドによって行われます。 不変式だけでなく不変式の値を返すメソッド、およびその値を設定するメソッドの名前を指定することは慣習です。

 - (void)setRe: (double)re { _re = re; } 

_re不変式は組み込み型に属しているため、値を変更しても問題はありません。 ただし、不変式が特定のクラスのオブジェクトである場合、参照カウンターを考慮する必要があるため、単純な割り当てが不可欠です。 この問題を解決するには、次の3つの方法が使用されます。

 //,      [label setText: @”Hello world”]; //  text // label     NSString * //  setText   UILabel ( №1) - (void)setText: (NSString *)text { [text retain]; //      [_text release]; //       _text _text = text; //    } //  setText   UILabel ( №2) - (void)setText: (NSString *)text { if (_text != text) //c    { [_text release]; //     //  _text _text = [text retain]; //   //       } } //  setText   UILabel ( №3 – ) - (void)setText: (NSString *)text { if (_text != text) { [_text autorelease]; // e   // _text    _text = [text retain]; //   //       } } 

オプション#3は現在のセルフローディングプールを詰まらせるため、あまり成功しませんが、これは通常あまり望ましくありません(次のセクションを参照)。
不変式の値を読み取るためのアクセス方法は、常に非常に簡単です。

 - (NSString *)text { return _text; } 


プログラムスレッドのセルフローディングプール


ここで、メソッド内で作成されたオブジェクトをメソッドから返してみましょう。

 -(NSString *)sayHelloToName: (NSString *)name withSurname: (NSString *)surname { NSString *retString = [[NSString alloc] initWithFormat: @”%@ %@!”, name, surname]; //      return retString; } 

フォーマット文字列はC言語標準に準拠していますが、IDタイプを指定する必要がある場合は、%@フォーマット指定子が使用されます。 フォーマットを解析する方法は、どの文字をidに置き換えるかをどのように理解しますか? 指定されたオブジェクトの説明の説明メソッドを返すものに単純に置き換えます。 このメソッドは、元々NSObjectクラスに対して宣言されていました。 NSStringは、文字列の内容を出力するように再定義しました。 再定義することにより、任意のオブジェクトがその文字列コンテンツを表すことができます。 たとえば、double型の2つの不変式を持つ複素数のクラスはこれを実行できます。

 - (NSString *)description { return [NSString stringWithFormat: @”re: %lf im: %lf”, _re, _im]; //  @“re: 1.0 im: 2.5”  _re == 1.0  _im == 2.5 } 

sayHelloToName:withSurname:メソッドが実行されると、返されたオブジェクトが処理後にリリースメッセージを送信する必要があることを呼び出し側コードが認識していない可能性が高いため、メモリリークが必ず発生します。 彼がそれをすることを推測したとしても、ポインターがオブジェクトの不変式に戻された可能性があります。これは、その破壊が深刻な結果を伴うことを意味します。 ユーザーコードがオブジェクトを解放することをまったく考えないように、将来オブジェクトの自己解放のためのメカニズムが必要です。 この問題は、NSAutoreleasePoolクラスのオブジェクトを使用して解決されます-NS

このクラスのオブジェクトを作成した後、それ以降に作成されたすべてのオブジェクトに自動解放メッセージを送信できます。 この場合、このオブジェクトは現在の(最後に作成された)セルフページプールに配置されます。 特定のプールがリリースメッセージを受信すると、すべてのオブジェクトに同じメッセージを送信し、参照カウントを減らします(基本的には破棄します)。 そのように。 自己ロードプールに配置されたオブジェクトは、プールの存続期間中ずっとメモリを使用し続けます。 これは小さな一時オブジェクトには便利ですが、時間が経つとかなりの量のメモリを消費する可能性があります。 したがって、自己ロードプールに送信される多数の一時オブジェクトを生成できるループは、ローカル(ネストされた)プールでフレーム化することをお勧めします。

Cocoaを使用するプログラムのスレッドは、最初に(他のオブジェクトを作成する前に)NSAutoreleasePoolクラスのオブジェクトを作成し、最後に(他のすべてのオブジェクトを破棄した後)破棄する必要があります。 Cocoaフレームワークを使用する場合、Objective-Cプログラムのメインスレッドであるmain()関数は常に次のようになります。

 int main(int argc, char *argv[]) //    main() { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // ,     int retVal; //    [pool drain]; //    ,     autorelease return retVal; } 

そして、正しいsayHelloToName:withSurname:メソッドは次のようになります。

 -(NSString *)sayHelloToName: (NSString *)name withSurname: (NSString *)surname { NSString *retString = [[NSString alloc] initWithFormat: @”%@ %@!”, name, surname]; //      [retString autorelease]; //  ,  retString     return retString; } 

ちなみに、セルフページプールのドレインメソッドはリリースと似ていますが、唯一の違いは、自己と含まれているすべてのオブジェクトを解放することに加えて、ガベージコレクターにゲームに入るためのヒントを与えることです。 ただし、iOSにはガベージコレクションがないため、これはMac OS 10.4以降にのみ関連します。

クラス定義


次に、Complexクラスのメソッドの定義を含むComplex.mファイルを検討します。

 #import “Complex.h” @implementation Complex - (id)init { return [self initWithRe: 0.0 andIm: 0.0]; } - (id)initWithRe: (double)re andIm: (double)im { if (self = [super init]) { _re = re; _im = im; _format = @”re: %.1lf im: %.1lf”; //    } } + (Complex *)complexWithRe: (double)re andIm: (double)im { return [[[Complex alloc] initWithRe: re andIm: im] autorelease]; } - (Complex *)add: (Complex *)other { return [[Complex alloc] initWithRe: _re + other->_re andIm: _im + other->_im]; } - (Complex *)sub: (Complex *)other { return [[Complex alloc] initWithRe: _re – other->_re andIm: _im – other->_im]; } - (NSString *)format { return _format; } - (void)setFormat: (NSString *)format {//    - [format retain]; [_format release]; _format = format; } - (double)re { return _re; } - (void)setRe: (double)re { _re = re; } - (double)im { return _im; } - (void)setIm: (double)im { _im = im; } - (NSString *)description {//    return [NSString stringWithFormat: _format, _re, _im]; } - (void)dealloc { [_format release]; //    dealloc [super dealloc]; } @end 

デフォルトのコンストラクターは、特定の初期パラメーターで特殊なコンストラクターを呼び出します。 complexWithRe:andIm:メソッドは、現在の自己読み込みプールにあるComplexクラスの初期化されたオブジェクトを返します。 descriptionメソッドも同じことを行い、NSStringクラスのオブジェクトを返します。 以下に、Complexクラスを使用するプログラムの例を示します。

 #import “Complex.h” #import <stdio.h> // printf() int main() { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; Complex *num1 = [[Complex alloc] init]; //0.0+0.0*i Complex *num2 = [[Complex alloc] initWithRe: 1.5 andIm: -2]; //1.5-2.0*i Complex *num3 = [Complex complexWithRe: 5 andIm: 7]; //5.0+7.0*i printf(“%s\n”, [[num2 description] cStringUsingEncoding: NSASCIIStringEncoding]); //> re: 1.5 im: -2.0 printf(“%s\n”, [[[num2 add: num3] description] cStringUsingEncoding: NSASCIIStringEncoding]); //> re: 6.5 im: 5.0 [num1 setRe: [num2 re]]; // _re  num1   num2 [num1 setIm: [num3 im]]; // _im  num1   num3 [num1 setFormat: @”%.2lf+%.2lf*i”]; //    num1 printf(“%s\n”, [[num1 description] cStringUsingEncoding: NSASCIIStringEncoding]); //> 1.50+7.00*i [num1 release]; [num2 release]; //[num3 release];  , ..      [pool drain]; return 0; } 


カテゴリと拡大


すでに記述されている(およびコンパイルされている可能性のある)クラスに継承なしでいくつかのメソッドを再定義する必要がある場合、カテゴリを使用すると、多くの労力なしでこれを実行できます。

 // “CategorizedComplex.h” #import “Complex.h” @interfce Complex (CategorizedComplex) - (Complex *)mul: (Complex *)other; - (Complex *)div: (Complex *)other; @end // “CategorizedComplex.m” #import “CategorizedComplex.h” @implementation Complex (CategorizedComplex) - (Complex *)mul: (Complex *)other { return [Complex complexWithRe: _re * other->_re - _im * other->_im andIm: _re * other->_im + _im * other->_re]; } - (Complex *)div: (Complex *)other { double retRe, retIm, denominator; denominator = other->_re * other->_re + other->_im * other->_im; if (!denominator) return nil; retRe = (_re * other->_re + _im * other->_im) / denominator; retIm = (_im * other->_re - _re * other->_im) / denominator; return [Complex complexWithRe: retRe andIm: retIm]; } @end 

そして、あなたはこのようにそれを使用することができます:

 CategorizdComplex *num1 = [[CategorizedComplex alloc] initWithRe: 1 andIm: 999]; Complex *num2 = [Complex complexWithRe: 0 andIm: 0]; CategorizedComplex *num3 = [num1 div: num2]; //num3 == nil 

拡張機能は、名前のないカテゴリとして優れたサービスを提供します。

 // “CategorizedComplex.m” #import “CategorizedComplex.h” @interface Complex () - (void)zeroComplex; //     @end @implementation Complex - (void)zeroComplex //       { _re = 0; _im = 0; } @end 


プロトコル


Objective-Cプロトコルは、必要に応じて任意のクラス(すべてのメソッドがvirtual ... = 0指定子で宣言されているC ++のクラスのアナログ)によって実装できるメソッドのグループの正式な宣言です。バージョン2.0では、プロトコルメソッド(@required指定子、サイレントと見なされる)および選択(@optional指定子)が必要になる場合があります。クラスが必要なプロトコルメソッドを実装している場合、このプロトコルをサポートするクラスと呼ばれます。プロトコルとそれをサポートするクラスは次のように宣言されます:

 @protocol MyPrinterProtocol @required - (void)print; - (BOOL)switchedOn; @optional - (void)loadPapaer: (int)numberOfPages; @end @interface MyPrinter : NSObject <MyPrinterProtocol> // MyPrinter   MyPrinterProtocol { BOOL _state; int _numberOfPages; } - (id)initWithState: (BOOL)state andPagesCount: (int)pages; - (BOOL)state; @end 

MyPrinterクラスのオブジェクトは、printメッセージとswitchOnメッセージの送信を保証できます。respondsToSelector:を確認した後、loadPaper:メッセージを送信できます。たとえば、同じ名前のメソッドの定義が実装に存在する必要があります。プロトコルをサポートするクラスオブジェクトの宣言は次のとおりです。

 MyPrinter *printer; id anotherPrinter = [[MyPrinter alloc] init]; [anotherPrinter print]; //        

さらに、1つのクラスで複数のプロトコルを満たすことができます。これを行うには、クラス宣言の山括弧内にコンマで区切ってリストします。

 @interface MyPrinter : NSObject <MyPrinterProtocol, OtherProtocol> 

そして、あるプロトコルに対応する未知のクラス(id)のオブジェクトを宣言するには、次のように書きます。

 id <MyPrinterProtocol> somePrinter; 

例外


エラー処理には、主に2つのアプローチがあります。グローバルステータス変数は、その値によって前の操作の成功を通知し、例外を生成します。両方の本質は、エラーが発生したコードが、それを呼び出したコードがそれを解決できることを望んでいるため、可能な限り詳細な状況を報告して制御を返すことです。 Objective-Cは、これらのアプローチの両方をサポートしています。

例外は、あるクラスのオブジェクトです。彼は(彼のタイプによっても)状況に関するいくつかの情報を持っています。便宜上、Cocoaには、2つのNSStringオブジェクトと任意のクラス(id型)の1つのオブジェクトで初期化できるNSExceptionクラスがあります。

 - (id)initWitnName: (NSString *)name reason: (NSString *)reason userInfo: (id)userInfo; 

@throw演算子を使用して、例外をスローして、コールスタックプロモーションメカニズムを開始できます。生成された例外をキャッチするには、コードの生成可能な部分を、tryというタイトルの特別なブロックで囲む必要があります(このようなブロックはネストできます)。そして、このブロックの後に、タイトルcatch()を持つブロックを配置します。括弧内は、申し立てられた例外のタイプを示します。tryブロックの後にいくつかのcatch()ブロックが存在する場合があります。例外がスローされた後、コントロールはスタックをスピンし、tryブロック終了し、すべてのcatch()ブロックを順番チェックして、そのcatchブロックに入ります()、中括弧内に、例外の型が暗黙的にキャストされる型があります(完全一致、基本クラスまたはidへのポインター)。タイプ例外がどのcatch()ブロックとも一致しない場合、コントロールはスタックし続けます。tryヘッダーのあるブロックの後にfinallyヘッダーのあるブロックがある場合tryブロックで例外が発生したか(およびcatch()ブロックが処理されたか)、最後の命令が実行されたかに関係なく、制御が渡されます。以下は、例外が発生するfillメソッドでCupクラスのオブジェクトを操作する例です。

 Cup *cup = [[Cup alloc] init]; @try { [cup fill]; // fill    NSException } @catch (NSException *exception) {//     NSLog NSLog(@"main: Caught %@: %@", [exception name], [exception reason]); } @finally //  @try    { [cup release]; } 

finallyブロックでは、tryブロック割り当てられたリソースを解放すると便利ですが、生成された例外のために解放されません。

プロパティ


Objective-C 2.0の場合、Complexクラスの実装は明らかに冗長です。アクセスメソッドが多すぎるため、その定義は継続的なルーチンです。プロパティを使用して書き換えます:

 // “Complex.h” #import <Foundation/Foundation.h> // NSObject   NSString @interface Complex : NSObject { double _re; //    double _im; //    NSString *_format; //    description } - (id)initWithRe: (double)re andIm: (double)im; + (Complex *)complexWithRe: (double)re andIm: (double)im; - (Complex *)add: (Complex *)other; //   - (Complex *)sub: (Complex *)other; //   @property (nonatomic, retain) NSString *format; //   @property (nonatomic, assign) double re; //   @property (nonatomic, assign) double im; @end // “Complex.m” #import “Complex.h” @implementation Complex @synthesize format = _format; //   @synthesize re = _re; //    @synthesize im = _im; //      - (id)init { return [self initWithRe: 0.0 andIm: 0.0]; } - (id)initWithRe: (double)re andIm: (double)im { if (self = [super init]) { _re = re; _im = im; _format = @”re: %.1lf im: %.1lf”; //    } } + (Complex *)complexWithRe: (double)re andIm: (double)im { return [[[Complex alloc] initWithRe: re andIm: im] autorelease]; } - (Complex *)add: (Complex *)other { return [[Complex alloc] initWithRe: _re + other.re andIm: _im + other.im]; //  re  im } - (Complex *)sub: (Complex *)other { return [[Complex alloc] initWithRe: _re – other.re andIm: _im – other.im]; //  re  im } @end 

プロパティは、ドット演算子「。」を介したオブジェクトへのポインターを介してアクセス可能な名前です。オブジェクトの不変式を取得または設定するには、アクセサメソッドの代わりにプロパティを使用します。プロパティを宣言するとき、プロパティによって生成されたアクセスメソッドの機能を記述するいくつかのパラメーターが指定されます。
Complexクラスの定義では、アクセスメソッドを手動で記述する必要はありません。これらはコンパイラーによって生成され、以前のものと同一になります。

頑張って

Source: https://habr.com/ru/post/J107126/


All Articles