プロパティを含むObjective-Cプロトコルの処理方法


131

Objective-Cプロトコルの使用が次のような方法で使用されるのを見てきました。

@protocol MyProtocol <NSObject>

@required

@property (readonly) NSString *title;

@optional

- (void) someMethod;

@end

サブクラスが拡張する具体的なスーパークラスを作成する代わりに、このフォーマットが使用されるのを見てきました。問題は、このプロトコルに準拠している場合、自分でプロパティを合成する必要があるかどうかです。スーパークラスを拡張している場合、答えは明らかにノーです。そうする必要はありません。しかし、プロトコルが準拠するために必要なプロパティをどのように処理しますか?

私の理解では、これらのプロパティを必要とするプロトコルに準拠するオブジェクトのヘッダーファイルでインスタンス変数を宣言する必要があります。その場合、それらは単なる指針であると想定できますか?明らかに同じことは必要なメソッドの場合ではありません。コンパイラーは、プロトコルがリストする必要なメソッドを除外するために手首を叩きます。プロパティの背後にある物語は何ですか?

コンパイルエラーが発生する例を次に示します(注:目の前の問題を反映しないコードをトリミングしました)。

MyProtocol.h

@protocol MyProtocol <NSObject>

@required
@property (nonatomic, retain) id anObject;

@optional

TestProtocolsViewController.h

- (void)iDoCoolStuff;

@end

#import <MyProtocol.h>

@interface TestProtocolsViewController : UIViewController <MyProtocol> {

}

@end

TestProtocolsViewController.m

#import "TestProtocolsViewController.h"

@implementation TestProtocolsViewController
@synthesize anObject; // anObject doesn't exist, even though we conform to MyProtocol.

- (void)dealloc {
    [anObject release]; //anObject doesn't exist, even though we conform to MyProtocol.
    [super dealloc];
}

@end     

回答:


135

プロトコルは、プロトコルを通じてクラスを知っているすべての人に、プロパティanObjectが存在することを伝えているだけです。プロトコルは実際のものではなく、それら自体には変数やメソッドがありません。それらは、クラスへの参照を保持するオブジェクトが特定の方法でそれらを使用できるように、クラスに当てはまる特定の属性セットのみを記述します。

つまり、プロトコルに準拠するクラスでは、anObjectが機能することを確認するためにあらゆることを行う必要があります。

@propertyそして@synthesizeあなたのためのコードを生成する2つのメカニズムが中心です。 @propertyそのプロパティ名のゲッター(またはセッター)メソッドがあると言っているだけです。最近@propertyでは、システムによって作成されたメソッドとストレージ変数(これまではを追加する必要がありました@sythesize)も作成するには十分です。ただし、変数にアクセスして格納するためのものが必要です。


80
プロトコルで定義されたプロパティの場合、最新のランタイムでも「@synthesize」が必要です。または、自動定義を取得するには、インターフェース定義で「@property」を複製する必要があります。
ジェフリーハリス

@JeffreyHarris Swiftでも同じですか?
Karan Alangat 2016年

@KaranAlangat-Swiftには\ @synthesizeのようなものはありませんが、ObjCと同様に、プロトコルに準拠すると主張するクラスでプロパティを宣言する必要があります。Swiftでは、関数のデフォルト実装を定義するカテゴリを作成できますが、私が知る限りでは、プロトコルのデフォルトプロパティを設定することはできません。
Kendall Helmstetter Gelner、2016

31

これは完璧に機能する私の例です、まずプロトコル定義:

@class ExampleClass;

@protocol ExampleProtocol

@required

// Properties
@property (nonatomic, retain) ExampleClass *item;

@end

以下は、このプロトコルをサポートするクラスの実際の例です。

#import <UIKit/UIKit.h>
#import "Protocols.h"

@class ExampleClass;

@interface MyObject : NSObject <ExampleProtocol> {

    // Property backing store
    ExampleClass        *item;

}


@implementation MyObject

// Synthesize properties
@synthesize item;

@end

14

あなたがしなければならないすべては、ドロップすることです

@synthesize title;

あなたの実装では、あなたはすべて設定する必要があります。これは、クラスインターフェイスにプロパティを配置するのと同じように機能します。

編集:

より具体的にこれを行うことができます。

@synthesize title = _title;

これは、自動合成を使用する場合にxcodeの自動合成がプロパティとivarを作成する方法と一致するため、クラスにプロトコルとクラスのプロパティが含まれている場合、一部のivarには異なる形式がなく、影響を与える可能性があります読みやすさ。


1
本当によろしいですか?プロトコルにオプションのプロパティセットがあり、そのプロトコルに準拠する具象クラスでのみ@synthesizeに設定すると、宣言されていない変数であると主張してコンパイラエラーが発生します。タイプミスは確認されていません。
Coocoo4Cocoa 09年

オプションのプロパティについてはわかりませんが、mralexが言ったように、私が言及し忘れたことの1つは、変数のタイトルに名前を付けるか、@ synthesize title = myinstancevar;と言って、メンバー変数に関連付ける必要があることです。
ケブラー

2
最新のランタイムを使用している場合は、@ synthesizeで十分です。基盤となるivarが作成されます。32ビットx86をターゲットにしている場合、レガシーランタイムをターゲットにしているため、前述のコンパイラエラーが発生します。
ジェフリーハリス

1
自動合成はXcode 4.4で導入されましたが、Graham Leeのツイートによると、プロトコルで宣言されたプロパティはカバーされていません。したがって、これらのプロパティを手動で合成する必要があります。
2013

これは素晴らしい点ですが、を追加するsynthesizeだけで十分だとは思いませんでした。涼しい!
Dan Rosenstark

9

私の記事を見てください PROPERTY IN PROTOCOL」をご覧ください

名前のプロパティを宣言するMyProtocolと、このプロトコルに準拠するMyClassがあるとします

注目に値すること

  1. MyClassのidentifierプロパティは、ゲッター、セッター、およびバッキング_identifier変数を宣言および生成します
  2. nameプロパティは、MyClassがヘッダーにゲッター、セッターを持つことのみを宣言します。ゲッター、セッターの実装およびバッキング変数は生成しません。
  3. この名前プロパティは、プロトコルですでに宣言されているため、再宣言できません。これを行うとエラーが発生します

    @interface MyClass () // Class extension
    
    @property (nonatomic, strong) NSString *name;
    
    @end

プロトコルでプロパティを使用する方法

そのため、その名前プロパティでMyClassを使用するには、次のいずれかを実行する必要があります。

  1. プロパティを再度宣言します(AppDelegate.hはこのようにします)

    @interface MyClass : NSObject <MyProtocol>
    
    @property (nonatomic, strong) NSString *name;
    
    @property (nonatomic, strong) NSString *identifier;
    
    @end
  2. 自分を合成する

    @implementation MyClass
    
    @synthesize name;
    
    @end

リスト内にネストされたコードブロックは、1行あたり8つのスペースでインデントする必要があります。Markdown構文の比較的未知の奇妙さです。回答を編集しました。
BoltClock

1

プロトコルアーキテクチャ

例:2つのクラス(PersonとSerial)はViewerのサービスを使用したい... ViewerProtocolに準拠する必要があります。viewerTypeOfDescriptionは、サブスクライバークラスが準拠しなければならない必須のプロパティです。

typedef enum ViewerTypeOfDescription {
    ViewerDataType_NSString,
    ViewerDataType_NSNumber,
} ViewerTypeOfDescription;

@protocol ViewerProtocol
@property ViewerTypeOfDescription viewerTypeOfDescription;
- (id)initConforming;
- (NSString*)nameOfClass;
- (id)dataRepresentation;
@end

@interface Viewer : NSObject
+ (void) printLargeDescription:(id <ViewerProtocol>)object;
@end

@implementation Viewer
+ (void) printLargeDescription:(id <ViewerProtocol>)object {
    NSString *data;
    NSString *type;
    switch ([object viewerTypeOfDescription]) {
        case ViewerDataType_NSString: {
            data=[object dataRepresentation];
            type=@"String";
            break;
        }
        case ViewerDataType_NSNumber: {
            data=[(NSNumber*)[object dataRepresentation] stringValue];
            type=@"Number";
            break;
        }
        default: {
            data=@"";
            type=@"Undefined";
            break;
        }
    }
    printf("%s [%s(%s)]\n",[data cStringUsingEncoding:NSUTF8StringEncoding],
           [[object nameOfClass] cStringUsingEncoding:NSUTF8StringEncoding],
           [type cStringUsingEncoding:NSUTF8StringEncoding]);
}
@end


/* A Class Person */

@interface Person : NSObject <ViewerProtocol>
@property NSString *firstname;
@property NSString *lastname;
@end

@implementation Person
// >>
@synthesize viewerTypeOfDescription;
// <<
@synthesize firstname;
@synthesize lastname;
// >>
- (id)initConforming {
    if (self=[super init]) {
        viewerTypeOfDescription=ViewerDataType_NSString;
    }
    return self;
}
- (NSString*)nameOfClass {
    return [self className];
}
- (NSString*) dataRepresentation {
    if (firstname!=nil && lastname!=nil) {
        return [NSString stringWithFormat:@"%@ %@", firstname, lastname];
    } else if (firstname!=nil) {
        return [NSString stringWithFormat:@"%@", firstname];
    }
    return [NSString stringWithFormat:@"%@", lastname];
}
// <<
@end



/* A Class Serial */

@interface Serial : NSObject <ViewerProtocol>
@property NSInteger amount;
@property NSInteger factor;
@end

@implementation Serial
// >>
@synthesize viewerTypeOfDescription;
// <<
@synthesize amount;
@synthesize factor;
// >>
- (id)initConforming {
    if (self=[super init]) {
        amount=0; factor=0;
        viewerTypeOfDescription=ViewerDataType_NSNumber;
    }
    return self;
}
- (NSString*)nameOfClass {
    return [self className];
}
- (NSNumber*) dataRepresentation {
    if (factor==0) {
        return [NSNumber numberWithInteger:amount];
    } else if (amount==0) {
        return [NSNumber numberWithInteger:0];
    }
    return [NSNumber numberWithInteger:(factor*amount)];
}
// <<
@end




int main(int argc, const char * argv[])
{

    @autoreleasepool {

        Person *duncan=[[Person alloc]initConforming];
        duncan.firstname=@"Duncan";
        duncan.lastname=@"Smith";

        [Viewer printLargeDescription:duncan];

        Serial *x890tyu=[[Serial alloc]initConforming];
        x890tyu.amount=1564;

        [Viewer printLargeDescription:x890tyu];

        NSObject *anobject=[[NSObject alloc]init];

        //[Viewer printLargeDescription:anobject];
        //<< compilator claim an issue the object does not conform to protocol

    }
    return 0;
}

サブクラス化でのプロトコル継承を使用した別の例

typedef enum {
    LogerDataType_null,
    LogerDataType_int,
    LogerDataType_string,
} LogerDataType;

@protocol LogerProtocol
@property size_t numberOfDataItems;
@property LogerDataType dataType;
@property void** data;
@end

@interface Loger : NSObject
+ (void) print:(id<LogerProtocol>)object;
@end

@implementation Loger
+ (void) print:(id<LogerProtocol>)object {
    if ([object numberOfDataItems]==0) return;
    void **data=[object data];
    for (size_t i=0; i<[object numberOfDataItems]; i++) {
        switch ([object dataType]) {
            case LogerDataType_int: {
                printf("%d\n",(int)data[i]);
            break;
            }
            case LogerDataType_string: {
                printf("%s\n",(char*)data[i]);
                break;
            }
            default:
            break;
        }
    }
}
@end


// A Master Class

@interface ArrayOfItems : NSObject  <LogerProtocol>
@end

@implementation ArrayOfItems
@synthesize dataType;
@synthesize numberOfDataItems;
@synthesize data;
- (id)init {
    if (self=[super init]) {
        dataType=LogerDataType_null;
        numberOfDataItems=0;
    }
    return self;
}
@end

// A SubClass

@interface ArrayOfInts : ArrayOfItems
@end

@implementation ArrayOfInts
- (id)init {
    if (self=[super init]) {
        self.dataType=LogerDataType_int;
    }
    return self;
}
@end

// An other SubClass

@interface ArrayOfStrings : ArrayOfItems
@end

@implementation ArrayOfStrings
- (id)init {
    if (self=[super init]) {
        self.dataType=LogerDataType_string;
    }
    return self;
}
@end


int main(int argc, const char * argv[])
{

    @autoreleasepool {

        ArrayOfInts *arr=[[ArrayOfInts alloc]init];
        arr.data=(void*[]){(int*)14,(int*)25,(int*)74};
        arr.numberOfDataItems=3;

        [Loger print:arr];

        ArrayOfStrings *arrstr=[[ArrayOfStrings alloc]init];
        arrstr.data=(void*[]){(char*)"string1",(char*)"string2"};
        arrstr.numberOfDataItems=2;

        [Loger print:arrstr];

    }
    return 0;
}

0

変数anObjectはTestProtocolsViewControllerクラス定義で定義する必要があります。プロトコルは、そこにあることを通知しているだけです。

コンパイラエラーは真実を示しています-変数は存在しません。結局のところ、@ propertiesは単なるヘルパーです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.