วิธีจัดการโปรโตคอล 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เป็นหัวใจสำคัญของกลไกสองประการที่สร้างรหัสให้คุณ @propertyก็แค่บอกว่าจะมีเมธอด getter (และ / หรือ setter) สำหรับชื่อคุณสมบัตินั้น ทุกวันนี้@propertyเพียงอย่างเดียวก็เพียงพอแล้วที่จะมีวิธีการและตัวแปรการจัดเก็บที่สร้างขึ้นสำหรับคุณโดยระบบ (คุณเคยต้องเพิ่ม@sythesize) แต่คุณต้องมีบางอย่างเพื่อเข้าถึงและจัดเก็บตัวแปร


80
สำหรับคุณสมบัติที่กำหนดไว้ในโปรโตคอลคุณยังคงต้องมี "@synthesize" แม้ในรันไทม์สมัยใหม่หรือคุณต้องทำซ้ำ "@property" ในนิยามอินเทอร์เฟซของคุณเพื่อให้ได้การสังเคราะห์อัตโนมัติ
Jeffrey Harris

@JeffreyHarris สิ่งเดียวกันใน Swift ??
Karan Alangat

@KaranAlangat - ไม่มีสิ่งที่เรียกว่า \ @synthesize ใน Swift แต่เช่นเดียวกับ ObjC คุณต้องประกาศคุณสมบัติในคลาสที่อ้างว่าเป็นไปตามโปรโตคอล ใน Swift คุณสามารถสร้างหมวดหมู่ที่กำหนดการใช้งานเริ่มต้นของฟังก์ชันได้ แต่เท่าที่ฉันสามารถบอกได้ว่าคุณไม่สามารถมีคุณสมบัติเริ่มต้นสำหรับโปรโตคอลได้
Kendall Helmstetter Gelner

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 สร้างคุณสมบัติและ ivars หากคุณใช้การสังเคราะห์อัตโนมัติดังนั้นหากคลาสของคุณมีคุณสมบัติจากโปรโตคอลและคลาสหนึ่ง ivars ของคุณจะไม่มีรูปแบบที่แตกต่างกันซึ่งอาจส่งผลกระทบ การอ่าน


1
คุณแน่ใจหรือไม่? ฉันมีคุณสมบัติที่เป็นทางเลือกที่ตั้งไว้ในโปรโตคอลและเมื่อฉันทำการสังเคราะห์ @ ในคลาสคอนกรีตที่เป็นไปตามโปรโตคอลนั้นเท่านั้นฉันได้รับข้อผิดพลาดของคอมไพเลอร์ที่อ้างว่าเป็นตัวแปรที่ไม่ได้ประกาศ ไม่มีการยืนยันการพิมพ์ผิด
Coocoo4Cocoa

ฉันไม่แน่ใจเกี่ยวกับคุณสมบัติที่เป็นทางเลือก แต่สิ่งหนึ่งที่ฉันลืมพูดถึงเช่น mralex กล่าวคือคุณต้องผูกมันเข้ากับตัวแปรสมาชิกไม่ว่าจะโดยการตั้งชื่อตัวแปรนั้นหรือพูดว่า @synthesize title = myinstancevar;
Kevlar

2
หากคุณใช้เวลาทำงานที่ทันสมัย ​​@synthesize คือทั้งหมดที่คุณต้องการไอวาร์พื้นฐานจะถูกสร้างขึ้นสำหรับคุณ หากคุณกำหนดเป้าหมายเป็น 32 บิต x86 คุณจะได้รับข้อผิดพลาดของคอมไพเลอร์ที่กล่าวถึงเนื่องจากคุณกำหนดเป้าหมายไปยังรันไทม์เดิม
Jeffrey Harris

1
การสังเคราะห์อัตโนมัติได้รับการแนะนำใน Xcode 4.4 แต่ตามทวีตของ Graham Leeไม่ครอบคลุมคุณสมบัติที่ประกาศในโปรโตคอล ดังนั้นคุณจะต้องสังเคราะห์คุณสมบัติเหล่านั้นด้วยตนเอง
cbowns

นี่เป็นจุดที่ดีไม่ทราบว่าการเพิ่มsynthesizeเพียงพอ เย็น!
Dan Rosenstark

9

ลองดูบทความของฉันคุณสมบัติในโปรโตคอล

สมมติว่าฉันมี MyProtocol ที่ประกาศคุณสมบัติชื่อและ MyClass ที่สอดคล้องกับโปรโตคอลนี้

สิ่งที่ควรค่าแก่การสังเกต

  1. คุณสมบัติตัวระบุใน MyClass ประกาศและสร้างตัวแปร getter, setter และ backing _identifier
  2. คุณสมบัติ name เท่านั้นประกาศว่า MyClass มี getter, setter ในส่วนหัว มันไม่สร้าง getter, setter operation และ backing variable
  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

โค้ดบล็อกที่ซ้อนอยู่ภายในรายการต้องเยื้องแปดช่องว่างต่อบรรทัด มันเป็นความแปลกประหลาดของไวยากรณ์ Markdown ฉันแก้ไขคำตอบให้คุณแล้ว
BoltClock

1

สถาปัตยกรรมโปรโตคอล

ตัวอย่าง: 2 คลาส (บุคคลและอนุกรม) ต้องการใช้บริการของ 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 ของคุณโปรโตคอลกำลังแจ้งให้คุณทราบว่าควรจะอยู่ที่นั่น

ข้อผิดพลาดของคอมไพเลอร์กำลังบอกความจริงกับคุณ - ไม่มีตัวแปร @ คุณสมบัติเป็นเพียงตัวช่วยหลังจากทั้งหมด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.