Лучший способ переопределить установщик атомарных свойств для ARC и поточно-ориентированный в Objective-C

У меня есть класс, содержащий свойство enum, и я хочу инициализировать его экземпляры из файла plist, используя метод setValuesForKeysWithDictionary:, Но файл может содержать недопустимые значения. Сценарий таков:

В файле "MyClass.h":

typedef NS_ENUM(NSInteger, FruitTag) {
    Apple   = 0,
    Banana  = 1,
    Orange  = 2,
    Unknown = 3
};

@interface MyClass : NSObject

@property (strong, nonatomic) NSString *name;
@property FruitTag tag;

@end

файл plist:

<plist version="1.0">
<array>
    <dict>
        <key>name</key>
        <string>Apple</string>
        <key>tag</key>
        <integer>0</integer>
    </dict>
    <dict>
        <key>name</key>
        <string>Banana</string>
        <key>tag</key>
        <string>1</string>
    </dict>
    <dict>
        <key>name</key>
        <string>Pineapple</string>
        <key>tag</key>
        <string>5</string>
    </dict>
</array>
</plist>

Как видите, последнее значение ключа "5" недопустимо, так как максимальное значение enum равно "3"! Но метод setValuesForKeysWithDictionary: просто установите значение, независимо от того, является ли оно действительным или нет. Поэтому я хочу переопределить сеттер, но свойство enum является атомарным! Еще одна вещь: я хочу, чтобы его геттер и сеттер были безопасны в многопоточном сценарии. Думая о двух, я изменил код как:

В файле "Myclass.h":

typedef NS_ENUM(NSInteger, FruitTag) {
    Apple   = 0,
    Banana  = 1,
    Orange  = 2,
    Unknown = 3
};

@interface MyClass : NSObject

@property (strong, nonatomic) NSString *name;
//@property FruitTag tag;

- (FruitTag)tag;
- (void)setTag:(NSInteger)aTag;

@end

И в файле "Myclass.m":

#import "MyClass.h"

@implementation MyClass
{
    FruitTag tag;
}

- (FruitTag)tag
{
    @synchronized (self) {
        return tag;
    }
}

- (void)setTag:(NSInteger)aTag
{
    @synchronized (self) {
        if ((aTag < Apple) || (aTag > Unknown)) {
            tag = Unknown;
        } else {
            tag = aTag;
        }
    }
}

- (void)print
{
    @synchronized (self) {
        NSLog(@"the tag is: %d", tag);
    }
}

@end

Хм, это выглядит странно, но работает, когда я тестирую, используя метод setValuesForKeysWithDictionary:, Но я не уверен в этом: это нормально? потокобезопасен достаточно? любой потенциальный риск? или лучший способ переопределить более приветствуется! Спасибо!

1 ответ

Решение

Почему вы думаете, это выглядит странно? Вы только что вручную реализовали "свойство" и добавили простую проверку границ - хороший повод для реализации свойства самостоятельно.

Это потокобезопасный достаточно? Ну, это либо есть, либо нет, и @synchronized конечно делает это так. Могут быть более эффективные, низкоуровневые способы сделать операции потокобезопасными; или вы можете даже обнаружить, что этого "достаточно", чтобы не делать ничего особенного; но если вы не обнаружили проблему с производительностью, вам не нужно идти туда - код прост и ваши намерения ясны.

НТН

Я бы порекомендовал такую ​​реализацию:

@property (nonatomic, strong) dispatch_queue_t atomicSyncQueue;

@synthesize tag = _tag;

// @init time
_atomicSyncQueue = dispatch_queue_create("com.YOUR_DEBUG_INFO", DISPATCH_QUEUE_SERIAL);

- (NSInteger)tag {
    __block NSInteger result = NO;
    
    dispatch_sync(self.atomicSyncQueue, ^{
        result = _tag;
    });
    
    return result;
}

- (void)setTag:(NSInteger)value {
    dispatch_async(self.atomicSyncQueue, ^{
        if (self->_tag != tag) {
            self->_tag = tag;

            // custom code:
        }
    });
}
Другие вопросы по тегам