Tại sao bạn sẽ sử dụng một chiếc ngà?


153

Tôi thường thấy câu hỏi này hỏi theo cách khác, chẳng hạn như Mỗi chiếc ngà phải là một tài sản? (và tôi thích câu trả lời của bbum cho Q này).

Tôi sử dụng các thuộc tính gần như độc quyền trong mã của tôi. Tuy nhiên, thường xuyên, tôi làm việc với một nhà thầu đã phát triển trên iOS trong một thời gian dài và là một lập trình viên trò chơi truyền thống. Anh ta viết mã tuyên bố hầu như không có thuộc tính nào và dựa vào ngà voi. Tôi giả sử anh ta làm điều này bởi vì 1.) anh ta đã quen với nó vì các thuộc tính không luôn tồn tại cho đến khi Objective C 2.0 (tháng 10 năm 2012) và 2.) đạt được hiệu suất tối thiểu khi không trải qua getter / setter.

Trong khi anh ta viết mã không bị rò rỉ, tôi vẫn thích anh ta sử dụng các thuộc tính hơn ngà. Chúng tôi đã nói về nó và anh ấy ít nhiều thấy không có lý do để sử dụng các thuộc tính vì chúng tôi không sử dụng KVO và anh ấy có kinh nghiệm trong việc xử lý các vấn đề về bộ nhớ.

Câu hỏi của tôi là nhiều hơn ... Tại sao bạn muốn sử dụng thời kỳ ngà - có kinh nghiệm hay không. Có thực sự có sự khác biệt lớn về hiệu suất mà việc sử dụng ngà voi sẽ được biện minh?

Cũng như một điểm để làm rõ, tôi ghi đè lên setters và getters khi cần thiết và sử dụng ivar tương quan với thuộc tính đó bên trong getter / setter. Tuy nhiên, bên ngoài getter / setter hoặc init, tôi luôn sử dụng self.myPropertycú pháp.


Chỉnh sửa 1

Tôi đánh giá cao tất cả các phản ứng tốt. Một điều mà tôi muốn giải quyết có vẻ không chính xác là với một chiếc ngà bạn có được đóng gói trong đó với một tài sản bạn không có. Chỉ cần xác định tài sản trong một lớp tiếp tục. Điều này sẽ che giấu tài sản từ người ngoài. Bạn cũng có thể khai báo thuộc tính chỉ đọc trong giao diện và xác định lại thuộc tính đó là readwrite trong quá trình thực hiện như:

// readonly for outsiders
@property (nonatomic, copy, readonly) NSString * name;

và có trong lớp tiếp tục:

// readwrite within this file
@property (nonatomic, copy) NSString * name;

Để có nó hoàn toàn "riêng tư" chỉ khai báo nó trong phần tiếp theo của lớp.


2
upvote cho câu hỏi thú vị - cũng là một câu hỏi mà tôi muốn nghe trường hợp cho ngà vì nghe có vẻ như tôi đã được dạy để làm theo cách của Sam.
Damo

2
Lưu ý rằng Đếm tham chiếu tự động (ARC) áp dụng các lợi ích quản lý bộ nhớ tương tự cho ngà như các thuộc tính, vì vậy trong mã ARC, sự khác biệt thực sự là về đóng gói.
benzado

1
Câu hỏi của bạn và đặc biệt là phần Chỉnh sửa 1 thực sự nhiều thông tin hơn sau đó là câu trả lời được chọn.
dùng523234

1
Để chỉnh sửa1: Tôi nghĩ rằng có thể đọc VÀ VIẾT mọi thuộc tính, ngay cả khi chỉ có một khai báo chỉ đọc trong .h, với Mã hóa giá trị khóa, ví dụ: [object setValue: [NSNumber numberWithInt: 20] forKey: @ "propertyname "];
Binary

1
@Sam vào Chỉnh sửa 1 của bạn: Nếu bạn sử dụng thuộc tính riêng tư và sử dụng phần mở rộng / tiếp tục lớp trong tệp .m thì nó không hiển thị cho các lớp con. Bạn cần viết lại mã hoặc sử dụng .h khác với phần mở rộng lớp. Dễ dàng hơn với @ bảo vệ / mặc định.
Binary

Câu trả lời:


100

Đóng gói

Nếu ngà là riêng tư, các phần khác của chương trình không thể truy cập dễ dàng như vậy. Với một tài sản được khai báo, những người thông minh có thể truy cập và biến đổi khá dễ dàng thông qua những người truy cập.

Hiệu suất

Vâng, điều này có thể làm cho một sự khác biệt trong một số trường hợp. Một số chương trình có các ràng buộc trong đó chúng không thể sử dụng bất kỳ tin nhắn objc nào trong các phần nhất định của chương trình (nghĩ thời gian thực). Trong các trường hợp khác, bạn có thể muốn truy cập trực tiếp vào tốc độ. Trong các trường hợp khác, đó là vì nhắn tin objc hoạt động như một tường lửa tối ưu hóa. Cuối cùng, nó có thể giảm các hoạt động đếm tham chiếu của bạn và giảm thiểu việc sử dụng bộ nhớ tối đa (nếu được thực hiện đúng).

Các loại không cần thiết

Ví dụ: Nếu bạn có loại C ++, đôi khi truy cập trực tiếp chỉ là cách tiếp cận tốt hơn. Loại có thể không thể sao chép, hoặc nó có thể không tầm thường để sao chép.

Đa luồng

Nhiều ngà của bạn là tiền mã hóa. Bạn phải đảm bảo tính toàn vẹn dữ liệu của bạn trong bối cảnh đa luồng. Vì vậy, bạn có thể ủng hộ truy cập trực tiếp đến nhiều thành viên trong các phần quan trọng. Nếu bạn gắn bó với người truy cập dữ liệu tiền mã hóa, khóa của bạn thường phải được cấp lại và bạn thường sẽ thực hiện nhiều lần mua lại (nhiều lần hơn đáng kể).

Chương trình chính xác

Vì các lớp con có thể ghi đè bất kỳ phương thức nào, cuối cùng bạn có thể thấy có sự khác biệt về ngữ nghĩa giữa việc ghi vào giao diện so với việc quản lý trạng thái của bạn một cách thích hợp. Truy cập trực tiếp cho tính chính xác của chương trình đặc biệt phổ biến ở các trạng thái được xây dựng một phần - trong phần khởi tạo của bạn và trong dealloc, tốt nhất là sử dụng truy cập trực tiếp. Bạn cũng có thể tìm thấy thông thường này trong việc triển khai của một accessor, một constructor tiện nghi, copy, mutableCopytriển khai, và lưu trữ / serialization.

Nó cũng thường xuyên hơn khi một người chuyển từ mọi thứ có tư duy truy cập đọc công khai sang một người che giấu chi tiết thực hiện / dữ liệu của nó. Đôi khi bạn cần phải bước chính xác xung quanh các hiệu ứng phụ, ghi đè của lớp con có thể giới thiệu để thực hiện đúng.

Kích thước nhị phân

Khai báo mọi thứ được ghi theo mặc định thường dẫn đến nhiều phương thức truy cập mà bạn không bao giờ cần, khi bạn xem xét việc thực hiện chương trình của mình trong giây lát. Vì vậy, nó sẽ thêm một số chất béo vào chương trình của bạn và thời gian tải là tốt.

Giảm thiểu độ phức tạp

Trong một số trường hợp, việc thêm + type + duy trì tất cả các giàn giáo bổ sung đó cho một biến đơn giản, chẳng hạn như một bool riêng được viết theo một phương thức và đọc theo phương thức khác.


Điều đó hoàn toàn không phải để nói rằng việc sử dụng các thuộc tính hoặc các phụ kiện là xấu - mỗi cái đều có những lợi ích và hạn chế quan trọng. Giống như nhiều ngôn ngữ và phương pháp tiếp cận OO để thiết kế, bạn cũng nên ưu tiên những người truy cập có khả năng hiển thị phù hợp trong ObjC. Sẽ có lúc bạn cần đi chệch hướng. Vì lý do đó, tôi nghĩ rằng tốt nhất là hạn chế truy cập trực tiếp vào việc triển khai khai báo ivar (ví dụ: khai báo @private).


Chỉnh sửa lại 1:

Hầu hết chúng ta đều ghi nhớ cách gọi một người truy cập ẩn một cách linh hoạt (miễn là chúng ta biết tên tên). Trong khi đó, hầu hết chúng ta đã không ghi nhớ làm thế nào để truy cập đúng cách những chiếc ngà không nhìn thấy được (ngoài KVC). Việc tiếp tục lớp học giúp , nhưng nó giới thiệu các lỗ hổng.

Cách giải quyết này rõ ràng:

if ([obj respondsToSelector:(@selector(setName:)])
  [(id)obj setName:@"Al Paca"];

Bây giờ hãy thử nó chỉ với một chiếc ngà và không có KVC.


@Sam cảm ơn, và câu hỏi hay! re phức tạp: nó chắc chắn đi cả hai cách. đóng gói lại - cập nhật
justin

@bbum RE: Ví dụ đặc biệt Mặc dù tôi đồng ý với bạn rằng đó là giải pháp sai, tôi không thể tưởng tượng được có nhiều nhà phát triển objc có kinh nghiệm tin rằng điều đó không xảy ra; Tôi đã thấy nó trong các chương trình của người khác và Cửa hàng ứng dụng đã đi xa tới mức cấm sử dụng API riêng của Apple.
justin

1
Bạn không thể truy cập một ivar riêng với đối tượng-> foo? Không khó để nhớ.
Nick Lockwood

1
Tôi có nghĩa là bạn có thể truy cập nó bằng cách sử dụng một con trỏ từ đối tượng bằng cách sử dụng cú pháp C ->. Các lớp Objective-C về cơ bản chỉ là các cấu trúc dưới mui xe và được đưa ra một con trỏ tới một cấu trúc, cú pháp C để truy cập các thành viên là ->, cũng hoạt động cho ivars trong các lớp C mục tiêu.
Nick Lockwood

1
@NickLockwood nếu ivar là @private, trình biên dịch nên cấm truy cập thành viên bên ngoài các phương thức lớp và thể hiện - đó có phải là những gì bạn thấy không?
justin

76

Đối với tôi nó thường là hiệu suất. Truy cập một ngà của một đối tượng cũng nhanh như truy cập một thành viên cấu trúc trong C bằng cách sử dụng một con trỏ tới bộ nhớ có chứa cấu trúc như vậy. Trong thực tế, các đối tượng Objective-C về cơ bản là các cấu trúc C nằm trong bộ nhớ được phân bổ động. Điều này thường nhanh như mã của bạn có thể nhận được, thậm chí mã lắp ráp được tối ưu hóa bằng tay thậm chí không thể nhanh hơn thế.

Truy cập một ivar thông qua một getter / cài đặt liên quan đến một cuộc gọi phương thức Objective-C, chậm hơn nhiều (ít nhất 3-4 lần) so với một cuộc gọi chức năng C "bình thường" và thậm chí một cuộc gọi chức năng C bình thường sẽ chậm hơn nhiều lần so với truy cập một thành viên cấu trúc. Tùy thuộc vào các thuộc tính của thuộc tính của bạn, việc triển khai setter / getter do trình biên dịch tạo ra có thể liên quan đến một lệnh gọi hàm C khác đến các hàm objc_getProperty/ objc_setProperty, vì chúng sẽ phải retain/ copy/ autoreleasecác đối tượng khi cần và tiếp tục thực hiện spinlock cho các thuộc tính nguyên tử khi cần thiết. Điều này có thể dễ dàng trở nên rất tốn kém và tôi không nói về việc chậm hơn 50%.

Chúng ta hãy cố gắng này:

CFAbsoluteTime cft;
unsigned const kRuns = 1000 * 1000 * 1000;

cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
    testIVar = i;
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"1: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);

cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
    [self setTestIVar:i];
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"2: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);

Đầu ra:

1: 23.0 picoseconds/run
2: 98.4 picoseconds/run

Đây là chậm hơn 4,28 lần và đây là một int nguyên thủy không nguyên tử, khá nhiều trường hợp tốt nhất ; hầu hết các trường hợp khác thậm chí còn tồi tệ hơn (thử một thuộc tính nguyên tử NSString *!). Vì vậy, nếu bạn có thể sống với thực tế là mỗi lần truy cập ivar chậm hơn 4-5 lần, thì việc sử dụng các thuộc tính là tốt (ít nhất là khi nói đến hiệu suất), tuy nhiên, có rất nhiều tình huống giảm hiệu suất như vậy hoàn toàn không thể chấp nhận được

Cập nhật 2015-10-20

Một số người tranh luận rằng đây không phải là vấn đề của thế giới thực, mã ở trên hoàn toàn là tổng hợp và bạn sẽ không bao giờ nhận thấy điều đó trong một ứng dụng thực. Được rồi, chúng ta hãy thử một mẫu thế giới thực.

Đoạn mã dưới đây định nghĩa Accountcác đối tượng. Tài khoản có các thuộc tính mô tả tên ( NSString *), giới tính ( enum) và tuổi ( unsigned) của chủ sở hữu, cũng như số dư ( int64_t). Một đối tượng tài khoản có một initphương thức và một compare:phương thức. Các compare:phương pháp được định nghĩa là: đơn đặt hàng trước khi Nữ nam, tên đặt theo thứ tự abc, đơn đặt hàng trẻ trước tuổi, đơn đặt hàng số dư thấp đến cao.

Trên thực tế tồn tại hai lớp tài khoản, AccountAAccountB. Nếu bạn xem triển khai của họ, bạn sẽ nhận thấy rằng chúng gần như hoàn toàn giống nhau, với một ngoại lệ: compare:Phương thức. AccountAcác đối tượng truy cập các thuộc tính của riêng chúng bằng phương thức (getter), trong khi AccountBcác đối tượng truy cập các thuộc tính của riêng chúng bằng ivar. Đó thực sự là sự khác biệt duy nhất! Cả hai đều truy cập các thuộc tính của đối tượng khác để so sánh với getter (truy cập bằng ivar sẽ không an toàn! Điều gì sẽ xảy ra nếu đối tượng kia là lớp con và đã ghi đè lên getter?). Cũng lưu ý rằng việc truy cập các thuộc tính của riêng bạn dưới dạng ivars không phá vỡ đóng gói (các ivars vẫn chưa được công khai).

Việc thiết lập thử nghiệm rất đơn giản: Tạo 1 Mio tài khoản ngẫu nhiên, thêm chúng vào một mảng và sắp xếp mảng đó. Đó là nó. Tất nhiên, có hai mảng, một cho AccountAcác đối tượng và một cho AccountBcác đối tượng và cả hai mảng được điền với các tài khoản giống hệt nhau (cùng một nguồn dữ liệu). Chúng tôi mất bao lâu để sắp xếp các mảng.

Đây là đầu ra của một số lần chạy tôi đã làm ngày hôm qua:

runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076

Như bạn có thể thấy, việc sắp xếp mảng các AccountBđối tượng luôn nhanh hơn đáng kể so với việc sắp xếp mảng các AccountAđối tượng.

Bất cứ ai tuyên bố rằng sự khác biệt thời gian chạy lên tới 1,32 giây sẽ không có sự khác biệt nào tốt hơn là không bao giờ nên lập trình UI. Ví dụ, nếu tôi muốn thay đổi thứ tự sắp xếp của một bảng lớn, sự khác biệt về thời gian như thế này sẽ tạo ra sự khác biệt lớn cho người dùng (sự khác biệt giữa giao diện người dùng chậm và chấp nhận được).

Ngoài ra trong trường hợp này, mã mẫu là công việc thực sự duy nhất được thực hiện ở đây, nhưng tần suất mã của bạn chỉ là một thiết bị nhỏ của đồng hồ phức tạp? Và nếu mọi thiết bị làm chậm toàn bộ quá trình như thế này, điều đó có nghĩa gì cho tốc độ của toàn bộ đồng hồ cuối cùng? Đặc biệt nếu một bước làm việc phụ thuộc vào đầu ra của một bước khác, điều đó có nghĩa là tất cả sự thiếu hiệu quả sẽ được tổng hợp. Hầu hết các sự thiếu hiệu quả không phải là vấn đề của riêng họ, đó là tổng số tuyệt đối của họ trở thành một vấn đề cho toàn bộ quá trình. Và một vấn đề như vậy không có gì là một trình hồ sơ sẽ dễ dàng hiển thị bởi vì một trình hồ sơ là về việc tìm kiếm các điểm nóng quan trọng, nhưng không có sự thiếu hiệu quả nào trong số đó là các điểm nóng. Thời gian CPU chỉ được phân bổ trung bình giữa chúng, nhưng mỗi trong số chúng chỉ có một phần rất nhỏ như vậy, có vẻ như hoàn toàn lãng phí thời gian để tối ưu hóa nó. Và đó là sự thật,

Và ngay cả khi bạn không nghĩ về thời gian của CPU, bởi vì bạn tin rằng việc lãng phí thời gian của CPU là hoàn toàn có thể chấp nhận được, sau tất cả "nó là miễn phí", vậy còn chi phí lưu trữ máy chủ gây ra do tiêu thụ điện năng thì sao? Điều gì về thời gian chạy pin của thiết bị di động? Nếu bạn sẽ viết cùng một ứng dụng di động hai lần (ví dụ: trình duyệt web dành cho thiết bị di động), một lần phiên bản mà tất cả các lớp chỉ truy cập các thuộc tính của riêng chúng bằng getters và một lần khi tất cả các lớp chỉ truy cập chúng bằng ngà, thì việc sử dụng lần đầu tiên sẽ liên tục thoát Pin nhanh hơn nhiều so với sử dụng pin thứ hai, mặc dù chúng có chức năng tương đương và với người dùng, pin thứ hai thậm chí có thể cảm thấy một chút thay đổi.

Bây giờ đây là mã cho main.mtệp của bạn (mã dựa trên ARC được bật và chắc chắn sử dụng tối ưu hóa khi biên dịch để xem hiệu ứng đầy đủ):

#import <Foundation/Foundation.h>

typedef NS_ENUM(int, Gender) {
    GenderMale,
    GenderFemale
};


@interface AccountA : NSObject
    @property (nonatomic) unsigned age;
    @property (nonatomic) Gender gender;
    @property (nonatomic) int64_t balance;
    @property (nonatomic,nonnull,copy) NSString * name;

    - (NSComparisonResult)compare:(nonnull AccountA *const)account;

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance;
@end


@interface AccountB : NSObject
    @property (nonatomic) unsigned age;
    @property (nonatomic) Gender gender;
    @property (nonatomic) int64_t balance;
    @property (nonatomic,nonnull,copy) NSString * name;

    - (NSComparisonResult)compare:(nonnull AccountB *const)account;

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance;
@end


static
NSMutableArray * allAcocuntsA;

static
NSMutableArray * allAccountsB;

static
int64_t getRandom ( const uint64_t min, const uint64_t max ) {
    assert(min <= max);
    uint64_t rnd = arc4random(); // arc4random() returns a 32 bit value only
    rnd = (rnd << 32) | arc4random();
    rnd = rnd % ((max + 1) - min); // Trim it to range
    return (rnd + min); // Lift it up to min value
}

static
void createAccounts ( const NSUInteger ammount ) {
    NSArray *const maleNames = @[
        @"Noah", @"Liam", @"Mason", @"Jacob", @"William",
        @"Ethan", @"Michael", @"Alexander", @"James", @"Daniel"
    ];
    NSArray *const femaleNames = @[
        @"Emma", @"Olivia", @"Sophia", @"Isabella", @"Ava",
        @"Mia", @"Emily", @"Abigail", @"Madison", @"Charlotte"
    ];
    const NSUInteger nameCount = maleNames.count;
    assert(maleNames.count == femaleNames.count); // Better be safe than sorry

    allAcocuntsA = [NSMutableArray arrayWithCapacity:ammount];
    allAccountsB = [NSMutableArray arrayWithCapacity:ammount];

    for (uint64_t i = 0; i < ammount; i++) {
        const Gender g = (getRandom(0, 1) == 0 ? GenderMale : GenderFemale);
        const unsigned age = (unsigned)getRandom(18, 120);
        const int64_t balance = (int64_t)getRandom(0, 200000000) - 100000000;

        NSArray *const nameArray = (g == GenderMale ? maleNames : femaleNames);
        const NSUInteger nameIndex = (NSUInteger)getRandom(0, nameCount - 1);
        NSString *const name = nameArray[nameIndex];

        AccountA *const accountA = [[AccountA alloc]
            initWithName:name age:age gender:g balance:balance
        ];
        AccountB *const accountB = [[AccountB alloc]
            initWithName:name age:age gender:g balance:balance
        ];

        [allAcocuntsA addObject:accountA];
        [allAccountsB addObject:accountB];
    }
}


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        @autoreleasepool {
            NSUInteger ammount = 1000000; // 1 Million;
            if (argc > 1) {
                unsigned long long temp = 0;
                if (1 == sscanf(argv[1], "%llu", &temp)) {
                    // NSUIntegerMax may just be UINT32_MAX!
                    ammount = (NSUInteger)MIN(temp, NSUIntegerMax);
                }
            }
            createAccounts(ammount);
        }

        // Sort A and take time
        const CFAbsoluteTime startTime1 = CFAbsoluteTimeGetCurrent();
        @autoreleasepool {
            [allAcocuntsA sortedArrayUsingSelector:@selector(compare:)];
        }
        const CFAbsoluteTime runTime1 = CFAbsoluteTimeGetCurrent() - startTime1;

        // Sort B and take time
        const CFAbsoluteTime startTime2 = CFAbsoluteTimeGetCurrent();
        @autoreleasepool {
            [allAccountsB sortedArrayUsingSelector:@selector(compare:)];
        }
        const CFAbsoluteTime runTime2 = CFAbsoluteTimeGetCurrent() - startTime2;

        NSLog(@"runTime 1: %f", runTime1);
        NSLog(@"runTime 2: %f", runTime2);
    }
    return 0;
}



@implementation AccountA
    - (NSComparisonResult)compare:(nonnull AccountA *const)account {
        // Sort by gender first! Females prior to males.
        if (self.gender != account.gender) {
            if (self.gender == GenderFemale) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Otherwise sort by name
        if (![self.name isEqualToString:account.name]) {
            return [self.name compare:account.name];
        }

        // Otherwise sort by age, young to old
        if (self.age != account.age) {
            if (self.age < account.age) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Last ressort, sort by balance, low to high
        if (self.balance != account.balance) {
            if (self.balance < account.balance) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // If we get here, the are really equal!
        return NSOrderedSame;
    }

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance
    {
        self = [super init];
        assert(self); // We promissed to never return nil!

        _age = age;
        _gender = gender;
        _balance = balance;
        _name = [name copy];

        return self;
    }
@end


@implementation AccountB
    - (NSComparisonResult)compare:(nonnull AccountA *const)account {
        // Sort by gender first! Females prior to males.
        if (_gender != account.gender) {
            if (_gender == GenderFemale) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Otherwise sort by name
        if (![_name isEqualToString:account.name]) {
            return [_name compare:account.name];
        }

        // Otherwise sort by age, young to old
        if (_age != account.age) {
            if (_age < account.age) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Last ressort, sort by balance, low to high
        if (_balance != account.balance) {
            if (_balance < account.balance) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // If we get here, the are really equal!
        return NSOrderedSame;
    }

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance
    {
        self = [super init];
        assert(self); // We promissed to never return nil!

        _age = age;
        _gender = gender;
        _balance = balance;
        _name = [name copy];

        return self;
    }
@end

3
Vô cùng nhiều thông tin và giải thích thực tế. Upvote cho mẫu mã
Philip007

1
Một trong những vòng loại quan trọng tôi thấy trong bài viết của bạn là "... từ các đường dẫn mã quan trọng". Vấn đề là sử dụng những gì làm cho mã dễ đọc / ghi hơn và sau đó tối ưu hóa những gì bạn thấy là các đường dẫn quan trọng. Điều này sẽ thêm sự phức tạp khi cần thiết.
Sandy Chapman

1
@ViktorLexington Trong mã của tôi, tôi đã thiết lập một unsigned intcái không bao giờ được giữ lại / phát hành, cho dù bạn có sử dụng ARC hay không. Bản thân việc giữ lại / phát hành rất tốn kém, do đó, sự khác biệt sẽ ít hơn khi quản lý lưu giữ thêm một chi phí tĩnh luôn tồn tại, sử dụng setter / getter hoặc ivar trực tiếp; tuy nhiên, bạn vẫn sẽ tiết kiệm được chi phí của một cuộc gọi phương thức bổ sung nếu bạn truy cập trực tiếp vào ivar. Không phải là một vấn đề lớn trong hầu hết các trường hợp, trừ khi bạn đang làm điều đó vài nghìn lần một giây. Apple cho biết sử dụng getters / setters theo mặc định, trừ khi bạn đang sử dụng phương thức init / dealloc hoặc phát hiện ra nút cổ chai.
Mecki

1
@Fogmeister Đã thêm một mẫu mã cho thấy việc này có thể tạo ra sự khác biệt lớn như thế nào trong một ví dụ thực tế rất đơn giản. Và ví dụ này không liên quan gì đến một siêu máy tính thực hiện hàng nghìn tỷ phép tính, đó là về cách sắp xếp một bảng dữ liệu thực sự đơn giản (một trường hợp khá phổ biến trong số hàng triệu ứng dụng).
Mecki

2
@malhal Một thuộc tính được đánh dấu là copysẽ KHÔNG tạo một bản sao giá trị của nó mỗi khi bạn truy cập nó. Người nhận copytài sản giống như người nhận tài sản strong/ retaintài sản. Đó là mã cơ bản return [[self->value retain] autorelease];. Chỉ bộ setter sao chép giá trị và nó sẽ trông giống như thế này [self->value autorelease]; self->value = [newValue copy];, trong khi đó strong/ retainsetter trông như thế này:[self->value autorelease]; self->value = [newValue retain];
Mecki

9

Lý do quan trọng nhất là khái niệm OOP về ẩn thông tin : Nếu bạn phơi bày mọi thứ thông qua các thuộc tính và do đó cho phép các đối tượng bên ngoài nhìn trộm nội bộ của đối tượng khác thì bạn sẽ sử dụng các nội bộ này và do đó làm phức tạp việc thay đổi triển khai.

Việc đạt được "hiệu suất tối thiểu" có thể nhanh chóng tổng hợp và sau đó trở thành một vấn đề. Tôi biết từ kinh nghiệm; Tôi làm việc trên một ứng dụng thực sự đưa iDevices đến giới hạn của họ và do đó chúng tôi cần tránh các cuộc gọi phương thức không cần thiết (tất nhiên chỉ khi có thể hợp lý). Để hỗ trợ cho mục tiêu này, chúng tôi cũng tránh cú pháp dấu chấm vì nó khó thấy số lần gọi phương thức ngay từ cái nhìn đầu tiên: ví dụ: có bao nhiêu cuộc gọi phương thức self.image.size.widthkích hoạt biểu thức ? Ngược lại, bạn có thể nói ngay với [[self image] size].width.

Ngoài ra, với cách đặt tên ivar chính xác, KVO có thể không có thuộc tính (IIRC, tôi không phải là chuyên gia KVO).


3
+1 Phản hồi tốt về "hiệu suất tối thiểu" tăng thêm và muốn xem tất cả các cuộc gọi phương thức một cách rõ ràng. Sử dụng cú pháp dấu chấm với các thuộc tính chắc chắn che giấu rất nhiều công việc diễn ra trong getters / setters tùy chỉnh (đặc biệt là nếu getter đó trả về một bản sao của một cái gì đó mỗi khi nó được gọi).
Sam

1
KVO không hoạt động với tôi mà không sử dụng setter. Thay đổi ngà trực tiếp không gọi cho người quan sát rằng giá trị đã thay đổi!
Binary

2
KVC có thể truy cập ivars. KVO không thể phát hiện các thay đổi đối với ngà voi (và thay vào đó phụ thuộc vào các phụ kiện được gọi).
Nikolai Ruhe

9

Ngữ nghĩa

  • Điều gì @propertycó thể diễn tả rằng ngà không thể: nonatomiccopy.
  • Những gì ngà có thể diễn tả mà @propertykhông thể:
    • @protected: công khai trên các lớp con, bên ngoài riêng tư.
    • @package: công khai trên các khung trên 64 bit, riêng tư bên ngoài. Tương tự như @publictrên 32 bit. Xem Điều khiển truy cập biến thể lớp và bit 64 bit của Apple .
    • Vòng loại. Ví dụ, mảng tham chiếu đối tượng mạnh : id __strong *_objs.

Hiệu suất

Truyện ngắn: ivars nhanh hơn, nhưng nó không quan trọng đối với hầu hết các mục đích sử dụng. nonatomiccác thuộc tính không sử dụng khóa, nhưng ivar trực tiếp nhanh hơn vì nó bỏ qua lệnh gọi của người truy cập. Để biết chi tiết đọc email sau từ danh sách.apple.com.

Subject: Re: when do you use properties vs. ivars?
From: John McCall <email@hidden>
Date: Sun, 17 Mar 2013 15:10:46 -0700

Thuộc tính ảnh hưởng đến hiệu suất theo nhiều cách:

  1. Như đã thảo luận, việc gửi tin nhắn để thực hiện tải / lưu trữ chậm hơn so với chỉ thực hiện tải / lưu trữ nội tuyến .

  2. Gửi tin nhắn để thực hiện tải / lưu trữ cũng cần thêm một chút mã cần lưu trong i-cache: ngay cả khi getter / setter thêm không có hướng dẫn nào ngoài chỉ tải / lưu trữ, sẽ có một nửa chắc chắn -dozen hướng dẫn thêm trong người gọi để thiết lập gửi tin nhắn và xử lý kết quả.

  3. Gửi một thông báo buộc một mục nhập cho bộ chọn đó được giữ trong bộ đệm của phương thức và bộ nhớ đó thường nằm xung quanh trong bộ đệm d. Điều này làm tăng thời gian khởi chạy, tăng mức sử dụng bộ nhớ tĩnh cho ứng dụng của bạn và làm cho việc chuyển đổi ngữ cảnh trở nên đau đớn hơn. Vì bộ đệm phương thức dành riêng cho lớp động cho một đối tượng, nên vấn đề này càng tăng khi bạn sử dụng KVO trên nó.

  4. Gửi một tin nhắn buộc tất cả các giá trị trong hàm phải được đổ vào ngăn xếp (hoặc được giữ trong các thanh ghi callee-save, điều này chỉ có nghĩa là tràn vào một thời điểm khác).

  5. Gửi tin nhắn có thể có tác dụng phụ tùy ý và do đó

    • buộc trình biên dịch thiết lập lại tất cả các giả định của nó về bộ nhớ không cục bộ
    • không thể được nâng lên, chìm, sắp xếp lại, kết hợp hoặc loại bỏ.

  6. Trong ARC, kết quả của việc gửi tin nhắn sẽ luôn được giữ lại , bởi người gọi hoặc người gọi, ngay cả khi trả về +0: ngay cả khi phương thức không giữ lại / tự động kết quả, người gọi không biết điều đó và có cố gắng hành động để ngăn chặn kết quả nhận được tự động. Điều này không bao giờ có thể được loại bỏ vì tin nhắn gửi không thể phân tích tĩnh.

  7. Trong ARC, do phương thức setter thường lấy đối số của nó ở +0, nên không có cách nào để "chuyển" một phần giữ lại của đối tượng đó (như đã thảo luận ở trên, ARC thường có) vào ivar, do đó , giá trị thường phải nhận giữ lại / phát hành hai lần .

Tất nhiên, điều này không có nghĩa là chúng luôn xấu, dĩ nhiên - có rất nhiều lý do tốt để sử dụng tài sản. Chỉ cần nhớ rằng, giống như nhiều tính năng ngôn ngữ khác, chúng không miễn phí.


John.


6

Các thuộc tính so với các biến thể hiện là một sự đánh đổi, cuối cùng, sự lựa chọn sẽ đến với ứng dụng.

Đóng gói / ẩn thông tin Đây là một điều tốt (TM) từ góc độ thiết kế, giao diện hẹp và liên kết tối thiểu là những gì làm cho phần mềm có thể duy trì và dễ hiểu. Obj-C khá khó khăn để che giấu bất cứ điều gì, nhưng các biến thể hiện được khai báo trong quá trình triển khai đến gần như bạn sẽ nhận được.

Hiệu suất Trong khi "tối ưu hóa sớm" là một điều xấu (TM), viết mã hiệu suất kém chỉ vì bạn có thể ít nhất là xấu. Thật khó để tranh luận về một cuộc gọi phương thức đắt hơn tải hoặc lưu trữ, và trong mã chuyên sâu tính toán, chi phí sẽ sớm tăng lên.

Trong một ngôn ngữ tĩnh với các thuộc tính, chẳng hạn như C #, các lệnh gọi đến setters / getters thường có thể được tối ưu hóa bởi trình biên dịch. Tuy nhiên Obj-C rất năng động và việc loại bỏ các cuộc gọi như vậy khó hơn nhiều.

Trừu tượng Một đối số chống lại các biến thể hiện trong Obj-C theo truyền thống là quản lý bộ nhớ. Với các biến đối tượng MRC yêu cầu các lệnh gọi giữ lại / giải phóng / tự động phát tán trong toàn bộ mã, các thuộc tính (được tổng hợp hoặc không) giữ mã MRC ở một nơi - nguyên tắc trừu tượng là Điều tốt (TM). Tuy nhiên, với GC hoặc ARC, đối số này biến mất, do đó, sự trừu tượng hóa trong quản lý bộ nhớ không còn là đối số chống lại các biến thể hiện.


5

Các thuộc tính phơi bày các biến của bạn cho các lớp khác. Nếu bạn chỉ cần một biến chỉ liên quan đến lớp bạn đang tạo, hãy sử dụng biến thể hiện. Đây là một ví dụ nhỏ: các lớp XML để phân tích cú pháp RSS và chu trình tương tự thông qua một loạt các phương thức ủy nhiệm và như vậy. Thật là thiết thực khi có một phiên bản NSMutableString để lưu trữ kết quả của mỗi lần phân tích cú pháp khác nhau. Không có lý do tại sao một lớp bên ngoài sẽ cần phải truy cập hoặc thao tác chuỗi đó. Vì vậy, bạn chỉ cần khai báo nó trong tiêu đề hoặc riêng tư và truy cập nó trong suốt lớp. Đặt thuộc tính cho nó chỉ có thể hữu ích để đảm bảo không có vấn đề về bộ nhớ, sử dụng self.mutableString để gọi getter / setters.


5

Khả năng tương thích ngược là một yếu tố đối với tôi. Tôi không thể sử dụng bất kỳ tính năng Objective-C 2.0 nào vì tôi đang phát triển trình điều khiển phần mềm và máy in phải hoạt động trên Mac OS X 10.3 như một phần của yêu cầu. Tôi biết câu hỏi của bạn dường như được nhắm mục tiêu trên iOS, nhưng tôi nghĩ tôi vẫn chia sẻ lý do không sử dụng tài sản.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.