การประกาศ / นิยามตำแหน่งตัวแปรใน ObjectiveC?


113

นับตั้งแต่เริ่มทำงานกับแอป iOS และวัตถุประสงค์ C ฉันรู้สึกงงงวยกับสถานที่ต่างๆที่สามารถประกาศและกำหนดตัวแปรได้ ในอีกด้านหนึ่งเรามีแนวทาง C แบบเดิมในทางกลับกันเรามีคำสั่ง ObjectiveC ใหม่ที่เพิ่ม OO ไว้ด้านบน คุณช่วยให้ฉันเข้าใจแนวทางปฏิบัติที่ดีที่สุดและสถานการณ์ที่ฉันต้องการใช้ตำแหน่งเหล่านี้สำหรับตัวแปรของฉันและอาจแก้ไขความเข้าใจในปัจจุบันของฉันได้ไหม

นี่คือคลาสตัวอย่าง (.h และ. m):

#import <Foundation/Foundation.h>

// 1) What do I declare here?

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

// 3) class-specific method / property declarations

@end

และ

#import "SampleClass.h"

// 4) what goes here?

@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

@implementation SampleClass
{
    // 6) define ivars
}

// 7) define methods and synthesize properties from both public and private
//    interfaces

@end
  • ความเข้าใจของฉันเกี่ยวกับ 1 และ 4 คือการประกาศและคำจำกัดความที่ใช้ไฟล์สไตล์ C ซึ่งไม่มีความเข้าใจใด ๆ เกี่ยวกับแนวคิดของคลาสดังนั้นจึงต้องใช้ว่าจะใช้อย่างไรใน C ฉันเคยเห็นพวกเขา ใช้สำหรับการปรับใช้ singletons แบบคงที่ที่อิงมาก่อน มีการใช้งานสะดวกอื่น ๆ ที่ฉันขาดหายไปหรือไม่?
  • สิ่งที่ฉันใช้จากการทำงานกับ iOS คือ ivars ถูกตัดออกไปอย่างสมบูรณ์นอกคำสั่ง @synthesize ดังนั้นส่วนใหญ่จึงถูกละเลย เป็นอย่างนั้นหรือ?
  • เกี่ยวกับ 5: ทำไมฉันถึงต้องการประกาศเมธอดในอินเทอร์เฟซส่วนตัว? วิธีการคลาสส่วนตัวของฉันดูเหมือนจะคอมไพล์ได้ดีโดยไม่มีการประกาศในอินเทอร์เฟซ ส่วนใหญ่อ่านง่ายหรือไม่?

ขอบคุณหลาย ๆ คน!

คำตอบ:


154

ฉันเข้าใจความสับสนของคุณ โดยเฉพาะอย่างยิ่งตั้งแต่การอัปเดต Xcode ล่าสุดและคอมไพเลอร์ LLVM ใหม่ได้เปลี่ยนวิธีการประกาศ ivars และคุณสมบัติ

ก่อน Objective-C "สมัยใหม่" (ใน Obj-C 2.0 "รุ่นเก่า) คุณไม่มีทางเลือกมากนัก ตัวแปรอินสแตนซ์ที่ใช้ในการประกาศในส่วนหัวระหว่างวงเล็บปีกกา{ }:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@end

คุณสามารถเข้าถึงตัวแปรเหล่านี้ได้เฉพาะในการนำไปใช้งานของคุณ แต่ไม่สามารถเข้าถึงจากคลาสอื่น ๆ ในการทำเช่นนั้นคุณต้องประกาศวิธีการเข้าถึงซึ่งมีลักษณะดังนี้:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}

- (int)myVar;
- (void)setMyVar:(int)newVar;

@end


// MyClass.m
@implementation MyClass

- (int)myVar {
   return myVar;
}

- (void)setMyVar:(int)newVar {
   if (newVar != myVar) {
      myVar = newVar;
   }
}

@end

ด้วยวิธีนี้คุณสามารถรับและตั้งค่าตัวแปรอินสแตนซ์นี้จากคลาสอื่น ๆ ได้เช่นกันโดยใช้ไวยากรณ์วงเล็บเหลี่ยมปกติเพื่อส่งข้อความ (วิธีการโทร):

// OtherClass.m
int v = [myClass myVar];  // assuming myClass is an object of type MyClass.
[myClass setMyVar:v+1];

เนื่องจากการประกาศและใช้วิธีการเข้าถึงด้วยตนเองนั้นค่อนข้างน่ารำคาญ@propertyและ@synthesizeได้รับการแนะนำให้สร้างวิธีการเข้าถึงโดยอัตโนมัติ:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@synthesize myVar;
@end

ผลลัพธ์คือรหัสที่ชัดเจนและสั้นกว่ามาก ระบบจะใช้วิธีการเข้าถึงสำหรับคุณและคุณยังสามารถใช้ไวยากรณ์วงเล็บได้เหมือนเดิม แต่นอกจากนี้คุณยังสามารถใช้ dot syntax เพื่อเข้าถึงคุณสมบัติ:

// OtherClass.m
int v = myClass.myVar;   // assuming myClass is an object of type MyClass.
myClass.myVar = v+1;

เนื่องจาก Xcode 4.4 คุณไม่จำเป็นต้องประกาศตัวแปรอินสแตนซ์ด้วยตัวเองอีกต่อไปและคุณสามารถข้ามได้@synthesizeเช่นกัน หากคุณไม่ได้ประกาศ Ivar @synthesizeคอมไพเลอร์จะเพิ่มให้คุณและยังจะสร้างวิธีการเข้าถึงโดยคุณไม่ต้องใช้

ชื่อเริ่มต้นสำหรับ ivar ที่สร้างขึ้นโดยอัตโนมัติคือชื่อหรือคุณสมบัติของคุณที่เริ่มต้นด้วยเครื่องหมายขีดล่าง คุณสามารถเปลี่ยนชื่อไอวาร์ที่สร้างขึ้นโดยใช้@synthesize myVar = iVarName;

// MyClass.h
@interface MyClass : NSObject 
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@end

สิ่งนี้จะทำงานเหมือนกับโค้ดด้านบน ด้วยเหตุผลด้านความเข้ากันได้คุณยังสามารถประกาศ ivars ในส่วนหัวได้ แต่เนื่องจากเหตุผลเดียวที่คุณต้องการทำเช่นนั้น (และไม่ประกาศคุณสมบัติ) คือการสร้างตัวแปรส่วนตัวตอนนี้คุณสามารถทำได้ในไฟล์การนำไปใช้งานเช่นกันและนี่เป็นวิธีที่ต้องการ

@interfaceกระชากแฟ้มใช้งานเป็นจริงการขยายและสามารถนำมาใช้ในการส่งต่อวิธีการประกาศ (ไม่จำเป็นอีกต่อไป) และ (อีกครั้ง) คุณสมบัติประกาศ ตัวอย่างเช่นคุณสามารถประกาศreadonlyคุณสมบัติในส่วนหัวของคุณ

@property (nonatomic, readonly) myReadOnlyVar;

และประกาศอีกครั้งในไฟล์การใช้งานของคุณreadwriteเพื่อให้สามารถตั้งค่าได้โดยใช้ไวยากรณ์คุณสมบัติและไม่เพียง แต่ผ่านการเข้าถึงโดยตรงไปยัง ivar เท่านั้น

สำหรับการประกาศตัวแปรโดยสมบูรณ์นอกสิ่งใด ๆ@interfaceหรือ@implementationบล็อกใช่ตัวแปรเหล่านี้เป็นตัวแปร C ธรรมดาและทำงานเหมือนกันทุกประการ


2
คำตอบที่ดี! หมายเหตุเพิ่มเติม: stackoverflow.com/questions/9859719/…
nycynik

44

ก่อนอื่นอ่านคำตอบของ @ DrummerB เป็นภาพรวมที่ดีของคนผิวขาวและสิ่งที่คุณควรทำโดยทั่วไป ด้วยเหตุนี้สำหรับคำถามเฉพาะของคุณ:

#import <Foundation/Foundation.h>

// 1) What do I declare here?

ไม่มีคำจำกัดความตัวแปรที่แท้จริงอยู่ที่นี่ (ในทางเทคนิคถูกต้องตามกฎหมายหากคุณรู้แน่ชัดว่าคุณกำลังทำอะไรอยู่ แต่ไม่เคยทำเช่นนี้) คุณสามารถกำหนดสิ่งอื่น ๆ ได้หลายอย่าง:

  • typdefs
  • enums
  • externs

Externs ดูเหมือนการประกาศตัวแปร แต่เป็นเพียงสัญญาว่าจะประกาศที่อื่นจริงๆ ใน ObjC ควรใช้เพื่อประกาศค่าคงที่เท่านั้นและโดยทั่วไปจะใช้เฉพาะค่าคงที่สตริงเท่านั้น ตัวอย่างเช่น:

extern NSString * const MYSomethingHappenedNotification;

จากนั้นคุณจะ.mประกาศค่าคงที่จริงในไฟล์:

NSString * const MYSomethingHappenedNotification = @"MYSomethingHappenedNotification";

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

ตามที่ DrummerB ระบุไว้นี่เป็นมรดกตกทอด อย่าใส่อะไรที่นี่


// 3) class-specific method / property declarations

@end

อ๋อ


#import "SampleClass.h"

// 4) what goes here?

ค่าคงที่ภายนอกตามที่อธิบายไว้ข้างต้น ไฟล์ตัวแปรคงที่สามารถไปที่นี่ สิ่งเหล่านี้เทียบเท่ากับตัวแปรคลาสในภาษาอื่น ๆ


@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

อ๋อ


@implementation SampleClass
{
    // 6) define ivars
}

แต่น้อยครั้งมาก เกือบทุกครั้งคุณควรอนุญาตให้ clang (Xcode) สร้างตัวแปรให้คุณ ข้อยกเว้นมักจะอยู่รอบ ๆ ivars ที่ไม่ใช่ ObjC (เช่นวัตถุ Core Foundation และโดยเฉพาะอย่างยิ่งวัตถุ C ++ หากเป็นคลาส ObjC ++) หรือ ivars ที่มีการจัดเก็บข้อมูลแบบแปลก ๆ (เช่น ivars ที่ไม่ตรงกับคุณสมบัติด้วยเหตุผลบางประการ)


// 7) define methods and synthesize properties from both public and private
//    interfaces

โดยทั่วไปคุณไม่ควร @synthesize อีกต่อไป เสียงดัง (Xcode) จะทำเพื่อคุณและคุณควรปล่อยให้มัน

ในช่วงไม่กี่ปีที่ผ่านมาสิ่งต่างๆง่ายขึ้นอย่างมาก ผลข้างเคียงคือตอนนี้มีสามยุคที่แตกต่างกัน (Fragile ABI, Non-Fragile ABI, Non-Fragile ABI + auto-syntheisze) ดังนั้นเมื่อคุณเห็นรหัสรุ่นเก่าอาจทำให้สับสนเล็กน้อย จึงเกิดความสับสนจากความเรียบง่าย: D


แค่สงสัย แต่ทำไมเราไม่ควรสังเคราะห์อย่างชัดเจน? ฉันทำเพราะฉันพบว่าโค้ดของฉันเข้าใจง่ายขึ้นโดยเฉพาะอย่างยิ่งเมื่อคุณสมบัติบางอย่างมีการสังเคราะห์ตัวเข้าถึงและบางคุณสมบัติมีการใช้งานแบบกำหนดเองเนื่องจากฉันคุ้นเคยกับการสังเคราะห์ มีข้อบกพร่องในการสังเคราะห์อย่างชัดเจนหรือไม่?
Metabble

ปัญหาในการใช้เป็นเอกสารคือมันไม่ได้จัดทำเอกสารอะไรเลย แม้จะใช้การสังเคราะห์ แต่คุณอาจลบล้างตัวเข้าถึงหนึ่งตัวหรือทั้งสองตัวได้ ไม่มีทางที่จะบอกได้ว่าสิ่งใดที่มีประโยชน์จริงๆจากสายการสังเคราะห์ สิ่งเดียวที่แย่กว่าการไม่มีเอกสารคือเอกสารที่ทำให้เข้าใจผิด ปล่อยมันออกไป
Rob Napier

3
ทำไม # 6 ถึงหายาก? นี่เป็นวิธีที่ง่ายที่สุดในการรับตัวแปรส่วนตัวไม่ใช่หรือ
เล่นพิเรน

วิธีที่ง่ายที่สุดและดีที่สุดในการได้รับทรัพย์สินส่วนตัวคือ # 5
Rob Napier

1
@RobNapier ยังคงจำเป็นที่จะต้องใช้ @ สังเคราะห์ในบางครั้ง (เช่นหากคุณสมบัติเป็นแบบอ่านอย่างเดียวจะมีการแทนที่ accessor)
Andy

6

ฉันยังค่อนข้างใหม่ดังนั้นหวังว่าฉันจะไม่ทำอะไรเสียหาย

1 & 4: ตัวแปรส่วนกลางสไตล์ C: มีขอบเขตกว้างของไฟล์ ความแตกต่างระหว่างทั้งสองคือเนื่องจากไฟล์มีความกว้างไฟล์แรกจะพร้อมใช้งานสำหรับทุกคนที่นำเข้าส่วนหัวในขณะที่ส่วนที่สองไม่ใช่

2: ตัวแปรอินสแตนซ์ ตัวแปรอินสแตนซ์ส่วนใหญ่ถูกสังเคราะห์และเรียกใช้ / ตั้งค่าผ่านตัวเข้าถึงโดยใช้คุณสมบัติเนื่องจากทำให้การจัดการหน่วยความจำดีและเรียบง่ายรวมทั้งให้สัญกรณ์จุดที่เข้าใจง่าย

6: การใช้งาน ivars ค่อนข้างใหม่ เป็นสถานที่ที่ดีในการใส่ ivars ส่วนตัวเนื่องจากคุณต้องการแสดงเฉพาะสิ่งที่จำเป็นในส่วนหัวสาธารณะ แต่คลาสย่อยจะไม่สืบทอด AFAIK

3 & 7: วิธีการสาธารณะและการประกาศทรัพย์สินจากนั้นการนำไปใช้

5: อินเทอร์เฟซส่วนตัว ฉันใช้อินเทอร์เฟซส่วนตัวทุกครั้งที่ทำได้เพื่อรักษาความสะอาดและสร้างเอฟเฟกต์กล่องดำ ถ้าพวกเขาไม่จำเป็นต้องรู้เรื่องนี้ให้วางไว้ที่นั่น ฉันทำเพื่อความสามารถในการอ่านเช่นกันไม่รู้ว่ามีเหตุผลอื่นหรือไม่


1
อย่าคิดว่าคุณทำอะไรพลาด :) ความคิดเห็นเล็กน้อย - # 1 & # 4 esp กับ # 4 มักจะเห็นตัวแปรการจัดเก็บแบบคงที่ # 1 บ่อยครั้งคุณจะเห็นที่เก็บข้อมูลภายนอกที่ระบุไว้จากนั้นที่เก็บข้อมูลจริงที่จัดสรรไว้ใน # 4 # 2) โดยปกติแล้วก็ต่อเมื่อคลาสย่อยต้องการไม่ว่าด้วยเหตุผลใดก็ตาม # 5 ไม่จำเป็นต้องส่งต่อประกาศวิธีการส่วนตัวอีกต่อไป.
Carl Veazey

ใช่ฉันเพิ่งตรวจสอบการประกาศล่วงหน้าด้วยตัวเอง มันเคยเตือนถ้าเมธอดส่วนตัวหนึ่งเรียกอีกวิธีหนึ่งที่ถูกกำหนดหลังจากนั้นโดยไม่มีการประกาศไปข้างหน้าใช่ไหม? ฉันรู้สึกประหลาดใจเมื่อมันไม่เตือนฉัน
Metabble

ใช่มันเป็นส่วนใหม่ของคอมไพเลอร์ พวกเขาก้าวหน้าไปมากในช่วงนี้
Carl Veazey

6

นี่คือตัวอย่างของตัวแปรทุกชนิดที่ประกาศใน Objective-C ชื่อตัวแปรระบุการเข้าถึง

ไฟล์: Animal.h

@interface Animal : NSObject
{
    NSObject *iProtected;
@package
    NSObject *iPackage;
@private
    NSObject *iPrivate;
@protected
    NSObject *iProtected2; // default access. Only visible to subclasses.
@public
    NSObject *iPublic;
}

@property (nonatomic,strong) NSObject *iPublic2;

@end

ไฟล์: Animal.m

#import "Animal.h"

// Same behaviour for categories (x) than for class extensions ().
@interface Animal(){
@public
    NSString *iNotVisible;
}
@property (nonatomic,strong) NSObject *iNotVisible2;
@end

@implementation Animal {
@public
    NSString *iNotVisible3;
}

-(id) init {
    self = [super init];
    if (self){
        iProtected  = @"iProtected";
        iPackage    = @"iPackage";
        iPrivate    = @"iPrivate";
        iProtected2 = @"iProtected2";
        iPublic     = @"iPublic";
        _iPublic2    = @"iPublic2";

        iNotVisible   = @"iNotVisible";
        _iNotVisible2 = @"iNotVisible2";
        iNotVisible3  = @"iNotVisible3";
    }
    return self;
}

@end

โปรดทราบว่าตัวแปร iNotVisible ไม่สามารถมองเห็นได้จากคลาสอื่น ๆ นี่เป็นปัญหาด้านการเปิดเผยดังนั้นการประกาศว่ามี@propertyหรือ@publicไม่เปลี่ยนแปลง

ภายในคอนสตรัคเตอร์การเข้าถึงตัวแปรที่ประกาศ@propertyโดยใช้ขีดล่างเป็นแนวทางปฏิบัติที่ดีselfเพื่อหลีกเลี่ยงผลข้างเคียง

มาลองเข้าถึงตัวแปร

ไฟล์: Cow.h

#import "Animal.h"
@interface Cow : Animal
@end

ไฟล์: Cow.m

#import "Cow.h"
#include <objc/runtime.h>

@implementation Cow

-(id)init {
    self=[super init];
    if (self){
        iProtected    = @"iProtected";
        iPackage      = @"iPackage";
        //iPrivate    = @"iPrivate"; // compiler error: variable is private
        iProtected2   = @"iProtected2";
        iPublic       = @"iPublic";
        self.iPublic2 = @"iPublic2"; // using self because the backing ivar is private

        //iNotVisible   = @"iNotVisible";  // compiler error: undeclared identifier
        //_iNotVisible2 = @"iNotVisible2"; // compiler error: undeclared identifier
        //iNotVisible3  = @"iNotVisible3"; // compiler error: undeclared identifier
    }
    return self;
}
@end

เรายังคงสามารถเข้าถึงตัวแปรที่มองไม่เห็นได้โดยใช้รันไทม์

ไฟล์: Cow.m (ตอนที่ 2)

@implementation Cow(blindAcess)

- (void) setIvar:(NSString*)name value:(id)value {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    object_setIvar(self, ivar, value);
}

- (id) getIvar:(NSString*)name {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    id thing = object_getIvar(self, ivar);
    return thing;
}

-(void) blindAccess {
    [self setIvar:@"iNotVisible"  value:@"iMadeVisible"];
    [self setIvar:@"_iNotVisible2" value:@"iMadeVisible2"];
    [self setIvar:@"iNotVisible3" value:@"iMadeVisible3"];
    NSLog(@"\n%@ \n%@ \n%@",
          [self getIvar:@"iNotVisible"],
          [self getIvar:@"_iNotVisible2"],
          [self getIvar:@"iNotVisible3"]);
}

@end

มาลองเข้าถึงตัวแปรที่มองไม่เห็น

ไฟล์: main.m

#import "Cow.h"
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
    @autoreleasepool {
        Cow *cow = [Cow new];
        [cow performSelector:@selector(blindAccess)];
    }
}

นี้พิมพ์

iMadeVisible 
iMadeVisible2 
iMadeVisible3

โปรดทราบว่าฉันสามารถเข้าถึงไอวาร์สำรอง_iNotVisible2ซึ่งเป็นส่วนตัวสำหรับคลาสย่อยได้ ใน Objective-C ตัวแปรทั้งหมดสามารถอ่านหรือตั้งค่าได้แม้กระทั่งตัวแปรที่ถูกทำเครื่องหมายไว้@privateก็ไม่มีข้อยกเว้น

ฉันไม่ได้รวมออบเจ็กต์หรือตัวแปร C ที่เกี่ยวข้องเนื่องจากเป็นนกที่แตกต่างกัน สำหรับตัวแปร C ตัวแปรใด ๆ ที่กำหนดไว้ภายนอก@interface X{}หรือ@implementation X{}เป็นตัวแปร C ที่มีขอบเขตไฟล์และที่จัดเก็บแบบคงที่

ฉันไม่ได้พูดถึงแอตทริบิวต์การจัดการหน่วยความจำหรือแอตทริบิวต์แบบอ่านอย่างเดียว / readwrite, getter / setter

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