Objective-C: Biến thuộc tính / thể hiện trong thể loại


122

Vì tôi không thể tạo thuộc tính tổng hợp trong Danh mục trong Mục tiêu-C, tôi không biết cách tối ưu hóa mã sau:

@interface MyClass (Variant)
@property (nonatomic, strong) NSString *test;
@end

@implementation MyClass (Variant)

@dynamic test;

- (NSString *)test {
    NSString *res;
    //do a lot of stuff
    return res;
}

@end

Các thử nghiệm phương pháp được gọi là nhiều lần trên thời gian chạy và tôi đang làm rất nhiều thứ để tính toán kết quả. Thông thường sử dụng thuộc tính tổng hợp, tôi lưu trữ giá trị trong IVar _test lần đầu tiên phương thức được gọi và chỉ trả lại IVar này vào lần tiếp theo. Làm thế nào tôi có thể tối ưu hóa mã trên?


2
Tại sao không làm những gì bạn thường làm, chỉ thay vì một danh mục, thêm thuộc tính vào lớp cơ sở MyClass? Và để đưa nó đi xa hơn, hãy thực hiện công cụ nặng nề của bạn trên nền và xử lý tắt thông báo hoặc gọi một số trình xử lý cho MyClass khi quá trình hoàn tất.
Jeremy

4
MyClass là một lớp được tạo từ Core Data. Nếu tôi nhưng mã đối tượng tùy chỉnh của tôi bên trong lớp được tạo thì nó sẽ biến mất nếu tôi tạo lại lớp từ Dữ liệu cốt lõi của mình. Vì điều này, tôi đang sử dụng một danh mục.
dhrm

1
Có thể chấp nhận câu hỏi áp dụng tốt nhất cho tiêu đề? ("Tài sản trong danh mục")
hfossli

Tại sao không chỉ tạo một lớp con?
Scott Zhu

Câu trả lời:


124

Phương pháp của @ lorean sẽ hoạt động (lưu ý: câu trả lời hiện đã bị xóa) , nhưng bạn chỉ có một khe lưu trữ duy nhất. Vì vậy, nếu bạn muốn sử dụng điều này trên nhiều trường hợp và mỗi trường hợp tính toán một giá trị riêng biệt, nó sẽ không hoạt động.

May mắn thay, thời gian chạy Objective-C có thứ gọi là Đối tượng liên kết có thể thực hiện chính xác những gì bạn muốn:

#import <objc/runtime.h>

static void *MyClassResultKey;
@implementation MyClass

- (NSString *)test {
  NSString *result = objc_getAssociatedObject(self, &MyClassResultKey);
  if (result == nil) {
    // do a lot of stuff
    result = ...;
    objc_setAssociatedObject(self, &MyClassResultKey, result, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  }
  return result;
}

@end

liên kết cố định cho các Đối tượng được liên kết: developer.apple.com/l
Library / ios / # ocumentation / cocoa / Conceptionual / Giả

5
@DaveDeLong Cảm ơn bạn về giải pháp này! Không đẹp lắm nhưng nó hoạt động :)
dhrm

42
"Không đẹp lắm"? Đây là nét đẹp của Objective-C! ;)
Dave DeLong

6
Câu trả lời chính xác! Bạn thậm chí có thể loại bỏ biến tĩnh bằng cách sử dụng @selector(test)làm khóa, như được giải thích ở đây: stackoverflow.com/questions/16020918/ Kẻ
Gabriele Petronella

1
@HotFudgeSunday - nghĩ rằng nó hoạt động nhanh chóng: stackoverflow.com/questions/24133058/
mẹo

173

tập tin .h

@interface NSObject (LaserUnicorn)

@property (nonatomic, strong) LaserUnicorn *laserUnicorn;

@end

.m-file

#import <objc/runtime.h>

static void * LaserUnicornPropertyKey = &LaserUnicornPropertyKey;

@implementation NSObject (LaserUnicorn)

- (LaserUnicorn *)laserUnicorn {
    return objc_getAssociatedObject(self, LaserUnicornPropertyKey);
}

- (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
    objc_setAssociatedObject(self, LaserUnicornPropertyKey, unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}

@end

Giống như một tài sản bình thường - có thể truy cập bằng ký hiệu dấu chấm

NSObject *myObject = [NSObject new];
myObject.laserUnicorn = [LaserUnicorn new];
NSLog(@"Laser unicorn: %@", myObject.laserUnicorn);

Cú pháp dễ dàng hơn

Ngoài ra, bạn có thể sử dụng @selector(nameOfGetter)thay vì tạo một phím con trỏ tĩnh như vậy:

- (LaserUnicorn *)laserUnicorn {
    return objc_getAssociatedObject(self, @selector(laserUnicorn));
}

- (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
    objc_setAssociatedObject(self, @selector(laserUnicorn), unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}

Để biết thêm chi tiết, hãy xem https://stackoverflow.com/a/16020927/202451


4
Bài báo hay. Một điều cần lưu ý là một bản cập nhật trong bài viết. Cập nhật ngày 22 tháng 12 năm 2011: Điều quan trọng cần lưu ý là khóa cho liên kết là một phím con trỏ void void *, không phải là một chuỗi. Điều đó có nghĩa là khi truy xuất một tham chiếu liên quan, bạn phải chuyển chính xác con trỏ tương tự vào thời gian chạy. Nó sẽ không hoạt động như dự định nếu bạn sử dụng chuỗi C làm khóa, sau đó sao chép chuỗi sang một vị trí khác trong bộ nhớ và cố gắng truy cập tham chiếu liên quan bằng cách chuyển con trỏ đến chuỗi đã sao chép làm khóa.
Ông Rogers

4
Bạn thực sự không cần @dynamic objectTag;. @dynamiccó nghĩa là setter & getter sẽ được tạo ở một nơi khác, nhưng trong trường hợp này chúng được triển khai ngay tại đây.
IluTov

@NSAddict đúng! Đã sửa!
hfossli

1
Làm thế nào về dealloc của laserUnicorns để quản lý bộ nhớ thủ công? Đây có phải là một bộ nhớ bị rò rỉ?
Manny

Được phát hành tự động stackoverflow.com/questions/6039309/
Mạnh

32

Câu trả lời đưa ra hoạt động rất tốt và đề xuất của tôi chỉ là một phần mở rộng cho nó mà tránh viết quá nhiều mã soạn sẵn.

Để tránh việc viết nhiều lần phương thức getter và setter cho các thuộc tính thể loại, câu trả lời này giới thiệu các macro. Ngoài ra, các macro này dễ dàng sử dụng các thuộc tính loại nguyên thủy như inthoặc BOOL.

Phương pháp truyền thống không có macro

Theo truyền thống, bạn xác định một thuộc tính danh mục như

@interface MyClass (Category)
@property (strong, nonatomic) NSString *text;
@end

Sau đó, bạn cần triển khai phương thức getter và setter bằng cách sử dụng một đối tượng được liên kếtbộ chọn get làm khóa ( xem câu trả lời gốc ):

#import <objc/runtime.h>

@implementation MyClass (Category)
- (NSString *)text{
    return objc_getAssociatedObject(self, @selector(text));
}

- (void)setText:(NSString *)text{
    objc_setAssociatedObject(self, @selector(text), text, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

Cách tiếp cận được đề xuất của tôi

Bây giờ, sử dụng một macro bạn sẽ viết thay thế:

@implementation MyClass (Category)

CATEGORY_PROPERTY_GET_SET(NSString*, text, setText:)

@end

Các macro được định nghĩa như sau:

#import <objc/runtime.h>

#define CATEGORY_PROPERTY_GET(type, property) - (type) property { return objc_getAssociatedObject(self, @selector(property)); }
#define CATEGORY_PROPERTY_SET(type, property, setter) - (void) setter (type) property { objc_setAssociatedObject(self, @selector(property), property, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }
#define CATEGORY_PROPERTY_GET_SET(type, property, setter) CATEGORY_PROPERTY_GET(type, property) CATEGORY_PROPERTY_SET(type, property, setter)

#define CATEGORY_PROPERTY_GET_NSNUMBER_PRIMITIVE(type, property, valueSelector) - (type) property { return [objc_getAssociatedObject(self, @selector(property)) valueSelector]; }
#define CATEGORY_PROPERTY_SET_NSNUMBER_PRIMITIVE(type, property, setter, numberSelector) - (void) setter (type) property { objc_setAssociatedObject(self, @selector(property), [NSNumber numberSelector: property], OBJC_ASSOCIATION_RETAIN_NONATOMIC); }

#define CATEGORY_PROPERTY_GET_UINT(property) CATEGORY_PROPERTY_GET_NSNUMBER_PRIMITIVE(unsigned int, property, unsignedIntValue)
#define CATEGORY_PROPERTY_SET_UINT(property, setter) CATEGORY_PROPERTY_SET_NSNUMBER_PRIMITIVE(unsigned int, property, setter, numberWithUnsignedInt)
#define CATEGORY_PROPERTY_GET_SET_UINT(property, setter) CATEGORY_PROPERTY_GET_UINT(property) CATEGORY_PROPERTY_SET_UINT(property, setter)

Macro CATEGORY_PROPERTY_GET_SETthêm một getter và setter cho thuộc tính đã cho. Thuộc tính chỉ đọc hoặc chỉ ghi sẽ sử dụng tương ứng CATEGORY_PROPERTY_GETCATEGORY_PROPERTY_SETmacro.

Các kiểu nguyên thủy cần chú ý hơn một chút

Vì các kiểu nguyên thủy không có đối tượng, các macro ở trên chứa một ví dụ để sử dụng unsigned intlàm kiểu của thuộc tính. Nó làm như vậy bằng cách gói giá trị nguyên vào một NSNumberđối tượng. Vì vậy, cách sử dụng của nó là tương tự như ví dụ trước:

@interface ...
@property unsigned int value;
@end

@implementation ...
CATEGORY_PROPERTY_GET_SET_UINT(value, setValue:)
@end

Sau mô hình này, bạn chỉ có thể bổ sung thêm các macro cũng để hỗ trợ signed int, BOOL, vv ...

Hạn chế

  1. Tất cả các macro đang sử dụng OBJC_ASSOCIATION_RETAIN_NONATOMICtheo mặc định.

  2. Các IDE như Mã ứng dụng hiện không nhận ra tên của setter khi tái cấu trúc tên của thuộc tính. Bạn sẽ cần phải đổi tên nó một mình.


1
đừng quên #import <objc/runtime.h>trong mục .m tập tin khác. biên dịch lỗi thời gian: Khai báo ngầm định hàm 'objc_getAssociatedObject' không hợp lệ trong C99 xuất hiện. stackoverflow.com/questions/9408934/
Mạnh

7

Chỉ cần sử dụng thư viện libextobjc :

tập tin h:

@interface MyClass (Variant)
@property (nonatomic, strong) NSString *test;
@end

tập tin m:

#import <extobjc.h>
@implementation MyClass (Variant)

@synthesizeAssociation (MyClass, test);

@end

Tìm hiểu thêm về @synthesizeAssociation


Cách chính xác để ghi đè một người truy cập bằng cái này (ví dụ: lười tải một tài sản)
David James

3

Chỉ được thử nghiệm với iOS 9 Ví dụ: Thêm thuộc tính UIView vào UINavlationBar (Danh mục)

UINavulationBar + Helper.h

#import <UIKit/UIKit.h>

@interface UINavigationBar (Helper)
@property (nonatomic, strong) UIView *tkLogoView;
@end

UINavulationBar + Helper.m

#import "UINavigationBar+Helper.h"
#import <objc/runtime.h>

#define kTKLogoViewKey @"tkLogoView"

@implementation UINavigationBar (Helper)

- (void)setTkLogoView:(UIView *)tkLogoView {
    objc_setAssociatedObject(self, kTKLogoViewKey, tkLogoView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIView *)tkLogoView {
    return objc_getAssociatedObject(self, kTKLogoViewKey);
}

@end

-2

Một giải pháp khả thi khác, có lẽ dễ dàng hơn, không sử dụng Associated Objectslà khai báo một biến trong tệp triển khai danh mục như sau:

@interface UIAlertView (UIAlertViewAdditions)

- (void)setObject:(id)anObject;
- (id)object;

@end


@implementation UIAlertView (UIAlertViewAdditions)

id _object = nil;

- (id)object
{
    return _object;
}

- (void)setObject:(id)anObject
{
    _object = anObject;
}
@end

Nhược điểm của kiểu triển khai này là đối tượng không hoạt động như một biến thể hiện, mà là một biến lớp. Ngoài ra, các thuộc tính thuộc tính không thể được chỉ định (chẳng hạn như được sử dụng trong các Đối tượng được liên kết như OBJC_ASSOCIATION_RETAIN_NONATOMIC)


Câu hỏi hỏi về biến thể hiện. Giải pháp của bạn giống như Lorean
hfossli

1
Có thật không?? Bạn có biết bạn đang làm gì không? Bạn đã tạo một cặp phương thức getter / setter để truy cập một biến nội bộ độc lập với một tệp NHƯNG một đối tượng lớp, nghĩa là, nếu bạn cấp phát hai đối tượng lớp UIAlertView, thì các giá trị đối tượng của chúng là như nhau!
Itachi
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.