在 macOS Objective-C 应用程序中,我将 NSMutableSet 子类化以强制执行不同于 isEqual 的相等性。我的实施没问题吗?
In a macOS Objective-C application, I have subclassed NSMutableSet for enforcing an equality different from isEqual. Is my implementation fine?
在我的 macOS Objective-C 应用程序中,我创建了 NSMutableSet 的子类。我想要实现的是一个不使用 isEqual: 作为比较策略的 NSMutableSet。具体来说,该集合将包含 NSRunningApplication 类型的对象,我希望该集合基于对象包标识符的相等性来工作。以下是我的实现:
头文件:
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface BundleIdentifierAwareMutableSet : NSMutableSet
@property (atomic, strong) NSMutableSet *backStorageMutableSet;
@property (atomic, strong) NSMutableArray *backStorageMutableArray;
@end
NS_ASSUME_NONNULL_END
执行文件:
#import "BundleIdentifierAwareMutableSet.h"
@implementation BundleIdentifierAwareMutableSet
@synthesize backStorageMutableSet;
- (instancetype)init {
self = [super init];
if (self) {
self.backStorageMutableSet = [[NSMutableSet alloc] init];
self.backStorageMutableArray = [[NSMutableArray alloc] init];
}
return self;
}
- (NSUInteger)count {
return [self.backStorageMutableArray count];
}
- (NSRunningApplication *)member:(NSRunningApplication *)object {
__block NSRunningApplication *returnValue = nil;
[self.backStorageMutableArray enumerateObjectsUsingBlock:^(NSRunningApplication * _Nonnull app, NSUInteger __unused idx, BOOL * _Nonnull stop) {
if ([app.bundleIdentifier isEqualToString:[object bundleIdentifier]]) {
returnValue = app;
if (![app isEqual:object]) {
NSLog(@"An ordinary set would have not considered the two objects equal.");
}
*stop = YES;
}
}];
return returnValue;
}
- (NSEnumerator *)objectEnumerator {
self.backStorageMutableSet = [NSMutableSet setWithArray:self.backStorageMutableArray];
return [self.backStorageMutableSet objectEnumerator];
}
- (void)addObject:(NSRunningApplication *)object {
NSRunningApplication *app = [self member:object];
if (app == nil) {
[self.backStorageMutableArray addObject:object];
}
}
- (void)removeObject:(NSRunningApplication *)object {
NSArray *snapShot = [self.backStorageMutableArray copy];
[snapShot enumerateObjectsUsingBlock:^(NSRunningApplication * _Nonnull currentApp, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
if ([[currentApp bundleIdentifier] isEqualToString:[object bundleIdentifier]]) {
[self.backStorageMutableArray removeObject:currentApp];
if (![currentApp isEqual:object]) {
NSLog(@"An ordinary set would have not considered the two objects equal.");
}
}
}];
}
这似乎有效,事实上,在适用时,Xcode 记录了一个普通的 NSMutableSet 不会认为两个成员相等。我想把这个实现带到生产应用程序中,但恐怕我没有考虑重要的事情,因为这是我第一次子类化 NSMutableSet。例如,我担心以下方法:
- (NSEnumerator *)objectEnumerator {
self.backStorageMutableSet = [NSMutableSet setWithArray:self.backStorageMutableArray];
return [self.backStorageMutableSet objectEnumerator];
}
这是我对 backStorageMutableSet 的唯一用途,因为其余的都备份到数组中。这很好还是会带来麻烦?子类的其他部分会不会带来问题?任何帮助将不胜感激。谢谢
不要这样做。子类化集合应该是最后的手段。它可能会对性能产生影响,...尝试使用尽可能高的抽象,如果由于某种原因它对您不起作用,则下降。
包装对象
将 NSRunningApplication
包装在另一个对象中并提供您自己的 hash
和 isEqual:
方法。
Application.h
:
@interface Application: NSObject
@property (nonatomic, strong, readonly, nonnull) NSRunningApplication *application;
@end
Application.m
:
@interface Application ()
@property (nonatomic, strong, nonnull) NSRunningApplication *application;
@end
@implementation Application
- (nonnull instancetype)initWithRunningApplication:(NSRunningApplication *_Nonnull)application {
if ((self = [super init]) == nil) {
// https://developer.apple.com/documentation/objectivec/nsobject/1418641-init?language=objc
//
// The init() method defined in the NSObject class does no initialization; it simply
// returns self. In terms of nullability, callers can assume that the NSObject
// implementation of init() does not return nil.
return nil;
}
self.application = application;
return self;
}
// https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418795-isequal?language=objc
- (BOOL)isEqual:(id)object {
if (![object isKindOfClass:[Application class]]) {
return NO;
}
Application *app = (Application *)object;
return [self.application.bundleIdentifier isEqualToString:app.application.bundleIdentifier];
}
// https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418859-hash?language=objc
- (NSUInteger)hash {
return self.application.bundleIdentifier.hash;
}
@end
免费桥接 & CFMutableSetRef
CFSet
与 NSSet
桥接,CFMutableSet
与 NSMutableSet
桥接,等等。这意味着您可以通过 Core Foundation 创建集合
API 然后将它用作 NSSet
例如。核心基础是强大的
向您展示更多内容的框架。
您可以为 CFSet
.
提供一组自定义回调
/*!
@typedef CFSetCallBacks
Structure containing the callbacks of a CFSet.
@field version The version number of the structure type being passed
in as a parameter to the CFSet creation functions. This
structure is version 0.
@field retain The callback used to add a retain for the set on
values as they are put into the set. This callback returns
the value to store in the set, which is usually the value
parameter passed to this callback, but may be a different
value if a different value should be stored in the set.
The set's allocator is passed as the first argument.
@field release The callback used to remove a retain previously added
for the set from values as they are removed from the
set. The set's allocator is passed as the first
argument.
@field copyDescription The callback used to create a descriptive
string representation of each value in the set. This is
used by the CFCopyDescription() function.
@field equal The callback used to compare values in the set for
equality for some operations.
@field hash The callback used to compare values in the set for
uniqueness for some operations.
*/
typedef struct {
CFIndex version;
CFSetRetainCallBack retain;
CFSetReleaseCallBack release;
CFSetCopyDescriptionCallBack copyDescription;
CFSetEqualCallBack equal;
CFSetHashCallBack hash;
} CFSetCallBacks;
有预定义的回调集,例如:
/*!
@constant kCFTypeSetCallBacks
Predefined CFSetCallBacks structure containing a set of callbacks
appropriate for use when the values in a CFSet are all CFTypes.
*/
CF_EXPORT
const CFSetCallBacks kCFTypeSetCallBacks;
这意味着您不必提供所有这些,但您可以自由修改其中的一部分。让我们准备两个回调函数:
// typedef CFHashCode (*CFSetHashCallBack)(const void *value);
CFHashCode runningApplicationBundleIdentifierHash(const void *value) {
NSRunningApplication *application = (__bridge NSRunningApplication *)value;
return [application.bundleIdentifier hash];
}
// typedef Boolean (*CFSetEqualCallBack)(const void *value1, const void *value2);
Boolean runningApplicationBundleIdentifierEqual(const void *value1, const void *value2) {
NSRunningApplication *application1 = (__bridge NSRunningApplication *)value1;
NSRunningApplication *application2 = (__bridge NSRunningApplication *)value2;
return [application1.bundleIdentifier isEqualToString:application2.bundleIdentifier];
}
您可以这样使用它们:
- (NSMutableSet<NSRunningApplication *> *_Nullable)bundleIdentifierAwareMutableSetWithCapacity:(NSUInteger)capacity {
// > Predefined CFSetCallBacks structure containing a set of callbacks
// > appropriate for use when the values in a CFSet are all CFTypes.
//
// Which means that you shouldn't bother about retain, release, ... callbacks,
// they're already set.
//
// CFSetCallbacks can be on stack, because this structure is copied in the
// CFSetCreateMutable function.
CFSetCallBacks callbacks = kCFTypeSetCallBacks;
// Overwrite just the hash & equal callbacks
callbacks.hash = runningApplicationBundleIdentifierHash;
callbacks.equal = runningApplicationBundleIdentifierEqual;
// Try to create a mutable set.
CFMutableSetRef set = CFSetCreateMutable(kCFAllocatorDefault, capacity, &callbacks);
if (set == NULL) {
// Failed, do some error handling or just return nil
return nil;
}
// Transfer the ownership to the Obj-C & ARC => no need to call CFRelease
return (__bridge_transfer NSMutableSet *)set;
}
&
NSMutableSet<NSRunningApplication *> *set = [self bundleIdentifierAwareMutableSetWithCapacity:50];
[set addObjectsFromArray:[[NSWorkspace sharedWorkspace] runningApplications]];
NSLog(@"%@", set);
在我的 macOS Objective-C 应用程序中,我创建了 NSMutableSet 的子类。我想要实现的是一个不使用 isEqual: 作为比较策略的 NSMutableSet。具体来说,该集合将包含 NSRunningApplication 类型的对象,我希望该集合基于对象包标识符的相等性来工作。以下是我的实现:
头文件:
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface BundleIdentifierAwareMutableSet : NSMutableSet
@property (atomic, strong) NSMutableSet *backStorageMutableSet;
@property (atomic, strong) NSMutableArray *backStorageMutableArray;
@end
NS_ASSUME_NONNULL_END
执行文件:
#import "BundleIdentifierAwareMutableSet.h"
@implementation BundleIdentifierAwareMutableSet
@synthesize backStorageMutableSet;
- (instancetype)init {
self = [super init];
if (self) {
self.backStorageMutableSet = [[NSMutableSet alloc] init];
self.backStorageMutableArray = [[NSMutableArray alloc] init];
}
return self;
}
- (NSUInteger)count {
return [self.backStorageMutableArray count];
}
- (NSRunningApplication *)member:(NSRunningApplication *)object {
__block NSRunningApplication *returnValue = nil;
[self.backStorageMutableArray enumerateObjectsUsingBlock:^(NSRunningApplication * _Nonnull app, NSUInteger __unused idx, BOOL * _Nonnull stop) {
if ([app.bundleIdentifier isEqualToString:[object bundleIdentifier]]) {
returnValue = app;
if (![app isEqual:object]) {
NSLog(@"An ordinary set would have not considered the two objects equal.");
}
*stop = YES;
}
}];
return returnValue;
}
- (NSEnumerator *)objectEnumerator {
self.backStorageMutableSet = [NSMutableSet setWithArray:self.backStorageMutableArray];
return [self.backStorageMutableSet objectEnumerator];
}
- (void)addObject:(NSRunningApplication *)object {
NSRunningApplication *app = [self member:object];
if (app == nil) {
[self.backStorageMutableArray addObject:object];
}
}
- (void)removeObject:(NSRunningApplication *)object {
NSArray *snapShot = [self.backStorageMutableArray copy];
[snapShot enumerateObjectsUsingBlock:^(NSRunningApplication * _Nonnull currentApp, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
if ([[currentApp bundleIdentifier] isEqualToString:[object bundleIdentifier]]) {
[self.backStorageMutableArray removeObject:currentApp];
if (![currentApp isEqual:object]) {
NSLog(@"An ordinary set would have not considered the two objects equal.");
}
}
}];
}
这似乎有效,事实上,在适用时,Xcode 记录了一个普通的 NSMutableSet 不会认为两个成员相等。我想把这个实现带到生产应用程序中,但恐怕我没有考虑重要的事情,因为这是我第一次子类化 NSMutableSet。例如,我担心以下方法:
- (NSEnumerator *)objectEnumerator {
self.backStorageMutableSet = [NSMutableSet setWithArray:self.backStorageMutableArray];
return [self.backStorageMutableSet objectEnumerator];
}
这是我对 backStorageMutableSet 的唯一用途,因为其余的都备份到数组中。这很好还是会带来麻烦?子类的其他部分会不会带来问题?任何帮助将不胜感激。谢谢
不要这样做。子类化集合应该是最后的手段。它可能会对性能产生影响,...尝试使用尽可能高的抽象,如果由于某种原因它对您不起作用,则下降。
包装对象
将 NSRunningApplication
包装在另一个对象中并提供您自己的 hash
和 isEqual:
方法。
Application.h
:
@interface Application: NSObject
@property (nonatomic, strong, readonly, nonnull) NSRunningApplication *application;
@end
Application.m
:
@interface Application ()
@property (nonatomic, strong, nonnull) NSRunningApplication *application;
@end
@implementation Application
- (nonnull instancetype)initWithRunningApplication:(NSRunningApplication *_Nonnull)application {
if ((self = [super init]) == nil) {
// https://developer.apple.com/documentation/objectivec/nsobject/1418641-init?language=objc
//
// The init() method defined in the NSObject class does no initialization; it simply
// returns self. In terms of nullability, callers can assume that the NSObject
// implementation of init() does not return nil.
return nil;
}
self.application = application;
return self;
}
// https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418795-isequal?language=objc
- (BOOL)isEqual:(id)object {
if (![object isKindOfClass:[Application class]]) {
return NO;
}
Application *app = (Application *)object;
return [self.application.bundleIdentifier isEqualToString:app.application.bundleIdentifier];
}
// https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418859-hash?language=objc
- (NSUInteger)hash {
return self.application.bundleIdentifier.hash;
}
@end
免费桥接 & CFMutableSetRef
CFSet
与 NSSet
桥接,CFMutableSet
与 NSMutableSet
桥接,等等。这意味着您可以通过 Core Foundation 创建集合
API 然后将它用作 NSSet
例如。核心基础是强大的
向您展示更多内容的框架。
您可以为 CFSet
.
/*!
@typedef CFSetCallBacks
Structure containing the callbacks of a CFSet.
@field version The version number of the structure type being passed
in as a parameter to the CFSet creation functions. This
structure is version 0.
@field retain The callback used to add a retain for the set on
values as they are put into the set. This callback returns
the value to store in the set, which is usually the value
parameter passed to this callback, but may be a different
value if a different value should be stored in the set.
The set's allocator is passed as the first argument.
@field release The callback used to remove a retain previously added
for the set from values as they are removed from the
set. The set's allocator is passed as the first
argument.
@field copyDescription The callback used to create a descriptive
string representation of each value in the set. This is
used by the CFCopyDescription() function.
@field equal The callback used to compare values in the set for
equality for some operations.
@field hash The callback used to compare values in the set for
uniqueness for some operations.
*/
typedef struct {
CFIndex version;
CFSetRetainCallBack retain;
CFSetReleaseCallBack release;
CFSetCopyDescriptionCallBack copyDescription;
CFSetEqualCallBack equal;
CFSetHashCallBack hash;
} CFSetCallBacks;
有预定义的回调集,例如:
/*!
@constant kCFTypeSetCallBacks
Predefined CFSetCallBacks structure containing a set of callbacks
appropriate for use when the values in a CFSet are all CFTypes.
*/
CF_EXPORT
const CFSetCallBacks kCFTypeSetCallBacks;
这意味着您不必提供所有这些,但您可以自由修改其中的一部分。让我们准备两个回调函数:
// typedef CFHashCode (*CFSetHashCallBack)(const void *value);
CFHashCode runningApplicationBundleIdentifierHash(const void *value) {
NSRunningApplication *application = (__bridge NSRunningApplication *)value;
return [application.bundleIdentifier hash];
}
// typedef Boolean (*CFSetEqualCallBack)(const void *value1, const void *value2);
Boolean runningApplicationBundleIdentifierEqual(const void *value1, const void *value2) {
NSRunningApplication *application1 = (__bridge NSRunningApplication *)value1;
NSRunningApplication *application2 = (__bridge NSRunningApplication *)value2;
return [application1.bundleIdentifier isEqualToString:application2.bundleIdentifier];
}
您可以这样使用它们:
- (NSMutableSet<NSRunningApplication *> *_Nullable)bundleIdentifierAwareMutableSetWithCapacity:(NSUInteger)capacity {
// > Predefined CFSetCallBacks structure containing a set of callbacks
// > appropriate for use when the values in a CFSet are all CFTypes.
//
// Which means that you shouldn't bother about retain, release, ... callbacks,
// they're already set.
//
// CFSetCallbacks can be on stack, because this structure is copied in the
// CFSetCreateMutable function.
CFSetCallBacks callbacks = kCFTypeSetCallBacks;
// Overwrite just the hash & equal callbacks
callbacks.hash = runningApplicationBundleIdentifierHash;
callbacks.equal = runningApplicationBundleIdentifierEqual;
// Try to create a mutable set.
CFMutableSetRef set = CFSetCreateMutable(kCFAllocatorDefault, capacity, &callbacks);
if (set == NULL) {
// Failed, do some error handling or just return nil
return nil;
}
// Transfer the ownership to the Obj-C & ARC => no need to call CFRelease
return (__bridge_transfer NSMutableSet *)set;
}
&
NSMutableSet<NSRunningApplication *> *set = [self bundleIdentifierAwareMutableSetWithCapacity:50];
[set addObjectsFromArray:[[NSWorkspace sharedWorkspace] runningApplications]];
NSLog(@"%@", set);