Bagaimana menangani protokol Objective-C yang mengandung properti?

131

Saya telah melihat penggunaan protokol Objective-C digunakan dengan cara seperti berikut:

@protocol MyProtocol <NSObject>

@required

@property (readonly) NSString *title;

@optional

- (void) someMethod;

@end

Saya telah melihat format ini digunakan alih-alih menulis superclass beton yang diperluas subclass. Pertanyaannya adalah, jika Anda mematuhi protokol ini, apakah Anda perlu mensintesis properti sendiri? Jika Anda memperpanjang superclass, jawabannya jelas tidak, Anda tidak perlu melakukannya. Tetapi bagaimana seseorang berurusan dengan properti yang harus dipenuhi oleh suatu protokol?

Untuk pemahaman saya, Anda masih perlu mendeklarasikan variabel instan dalam file header objek yang sesuai dengan protokol yang membutuhkan properti ini. Dalam hal itu, dapatkah kita berasumsi bahwa itu hanya prinsip panduan? Jelas sama bukan kasus untuk metode yang diperlukan. Kompiler akan menampar pergelangan tangan Anda untuk mengecualikan metode yang diperlukan yang tercantum dalam protokol. Apa cerita di balik properti?

Berikut adalah contoh yang menghasilkan kesalahan kompilasi (Catatan: Saya telah memangkas kode yang tidak mencerminkan masalah yang dihadapi):

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     
Coocoo4Cocoa
sumber

Jawaban:

135

Protokol ini hanya memberi tahu semua orang yang tahu tentang kelas Anda melalui protokol, bahwa propertinya anObjectakan ada di sana. Protokol tidak nyata, mereka tidak memiliki variabel atau metode sendiri - mereka hanya menggambarkan sekumpulan atribut spesifik yang benar tentang kelas Anda sehingga objek yang memegang referensi kepada mereka dapat menggunakannya dengan cara tertentu.

Itu berarti di kelas Anda yang sesuai dengan protokol Anda, Anda harus melakukan segalanya untuk memastikan anObject berfungsi.

@propertydan @synthesizepada dasarnya ada dua mekanisme yang menghasilkan kode untuk Anda. @propertyhanya mengatakan akan ada metode pengambil (dan / atau setter) untuk nama properti itu. Hari-hari ini @propertysaja sudah cukup untuk memiliki metode dan variabel penyimpanan yang dibuat untuk Anda oleh sistem (Anda harus menambahkan @sythesize). Tetapi Anda harus memiliki sesuatu untuk mengakses dan menyimpan variabel.

Kendall Helmstetter Gelner
sumber
80
Untuk properti yang didefinisikan dalam protokol, Anda masih memerlukan "@sintesis" bahkan di runtime modern, atau Anda perlu menduplikasi "@ properti" di definisi antarmuka Anda untuk mendapatkan sintesis otomatis.
Jeffrey Harris
@ JeffreyHarris Bagaimana dengan Swift ??
Karan Alangat
@KaranAlangat - tidak ada yang namanya \ @sintesis di Swift, tetapi sama seperti ObjC Anda perlu mendeklarasikan properti di kelas yang mengklaim sesuai dengan protokol. Di Swift Anda dapat membuat kategori yang mendefinisikan implementasi default suatu fungsi, tetapi sejauh yang saya bisa katakan Anda tidak dapat memiliki properti default untuk protokol.
Kendall Helmstetter Gelner
31

Inilah contoh milik saya yang bekerja dengan sempurna, definisi protokol pertama-tama:

@class ExampleClass;

@protocol ExampleProtocol

@required

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

@end

Di bawah ini adalah contoh kerja dari kelas yang mendukung protokol ini:

#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
reddersky
sumber
14

yang harus Anda lakukan adalah menjatuhkan a

@synthesize title;

dalam implementasi Anda dan Anda harus siap. ini bekerja dengan cara yang sama seperti menempatkan properti di antarmuka kelas Anda.

Edit:

Anda mungkin ingin melakukan ini lebih spesifik:

@synthesize title = _title;

Ini akan sejalan dengan bagaimana sintesis otomatis xcode menciptakan properti dan ivars jika Anda menggunakan sintesis otomatis, sehingga jika kelas Anda memiliki properti dari protokol dan kelas, beberapa ivars Anda tidak akan memiliki format berbeda yang dapat berdampak keterbacaan.

Kevlar
sumber
1
Apakah Anda yakin sepenuhnya? Saya memiliki properti opsional yang diatur dalam protokol, dan ketika saya hanya mensintesiskannya dalam kelas konkret yang sesuai dengan protokol itu - saya mendapatkan kesalahan kompiler yang mengklaim itu adalah variabel yang tidak dideklarasikan. Tidak ada kesalahan ketik yang dikonfirmasi.
Coocoo4Cocoa
Saya tidak yakin tentang properti opsional, tetapi satu hal yang saya lupa sebutkan seperti kata mralex adalah bahwa Anda perlu mengikatnya ke variabel anggota, baik dengan memberi nama judul variabel, atau mengatakan @synthesize title = myinstancevar;
Kevlar
2
Jika Anda menggunakan run time modern, hanya perlu @synthesize, ivars yang mendasarinya akan dibuat untuk Anda. Jika Anda menargetkan x86 32-bit, Anda akan mendapatkan kesalahan kompiler yang disebutkan, karena Anda menargetkan runtime lawas.
Jeffrey Harris
1
Sintesis otomatis diperkenalkan di Xcode 4.4, tetapi menurut tweet oleh Graham Lee , itu tidak mencakup properti yang dinyatakan dalam protokol. Jadi, Anda masih perlu mensintesis properti-properti itu secara manual.
cbowns
Ini adalah poin yang bagus, tidak menyadari bahwa menambahkan synthesizesudah cukup. Keren!
Dan Rosenstark
9

Lihatlah artikel saya PROPERTI DALAM PROTOKOL

Misalkan saya memiliki MyProtocol yang mendeklarasikan properti nama, dan MyClass yang sesuai dengan protokol ini

Hal-hal yang patut dicatat

  1. Properti pengidentifikasi di MyClass menyatakan dan menghasilkan variabel getter, setter dan backing _identifier
  2. Properti nama hanya menyatakan bahwa MyClass memiliki pengambil, penyetel di header. Itu tidak menghasilkan getter, implementasi setter dan variabel dukungan.
  3. Saya tidak dapat mendeklarasikan ulang properti nama ini, karena sudah dinyatakan oleh protokol. Lakukan ini akan berteriak kesalahan

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

Cara menggunakan properti dalam protokol

Jadi untuk menggunakan MyClass dengan properti nama itu, kita harus melakukan keduanya

  1. Nyatakan properti itu lagi (AppDelegate.h melakukannya dengan cara ini)

    @interface MyClass : NSObject <MyProtocol>
    
    @property (nonatomic, strong) NSString *name;
    
    @property (nonatomic, strong) NSString *identifier;
    
    @end
  2. Mensintesiskan diri kita sendiri

    @implementation MyClass
    
    @synthesize name;
    
    @end
onmyway133
sumber
Blok kode yang bersarang di dalam daftar harus diindentasi dengan delapan spasi per baris. Ini adalah keanehan relatif dari sintaks Markdown. Saya sudah mengedit jawaban Anda untuk Anda.
BoltClock
1

Arsitektur Protokol

Contoh: 2 kelas (Orang dan Serial) ingin menggunakan layanan Viewer ... dan harus sesuai dengan ViewerProtocol. viewerTypeOfDescription adalah kelas pelanggan properti wajib harus sesuai.

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;
}

Contoh lain dengan pewarisan Protokol atas subklasifikasi

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;
}
Luc-Olivier
sumber
0

Variabel, anObject, perlu didefinisikan dalam definisi kelas TestProtocolsViewController Anda, protokol hanya memberi tahu Anda bahwa itu harus ada di sana.

Kesalahan kompiler mengatakan yang sebenarnya - variabel tidak ada. @ properti sama sekali hanya pembantu.

mralex
sumber