ObjC可脚本化应用:多容器共享对象的Object Specifier问题
我正在开发一款可脚本化的Objective-C应用,自定义脚本化类A被多个其他脚本化类(如B、C)作为<element>使用,且部分类会共享A的同一实例。示例代码如下:
@interface A : NSObject … @end @interface B : NSWindow NSArray<A*> *someInstancesOfA; @end @interface C : NSWindow NSArray<A*> *someInstancesOfA; @end
例如,对象B创建并初始持有A的一个实例,之后将该实例共享给C,此时两者持有同一A实例;后续B可能释放该实例,但C仍持有它。
目前我没有存储所有A实例的中心字典,遍历所有窗口收集实例并去重的方式较为繁琐;而创建全局存储记录A实例又需使用弱引用避免内存泄漏,但Objective-C中管理弱引用集合较为复杂且相关讨论较少。
请问如何让AppleScript识别这些被不同类返回的A实例?若使用NSUniqueIDSpecifier,需提供通过ID查找对象的容器,但B或C均不适合作为容器(因它们可能不再持有A实例,导致共享对象的访问失效),仅能使用主应用等单一容器,这又需要管理弱引用集合。
这是多容器共享对象且需兼顾ARC自动释放与无内存泄漏的常见脚本化问题,但相关讨论较少。是否存在无需依赖中心容器的对象指定器方法来传递可脚本化对象?Objective-C层面对象由应用属性持有,但Cocoa脚本接口与AppleScript对象层面无强关联,是否有解决技巧?
1. 重写scriptingUniqueID实现无容器对象识别
直接在类A中重写scriptingUniqueID方法,返回一个稳定的唯一标识(比如基于对象内存地址的字符串,或自定义UUID),让AppleScript通过这个ID直接关联同一实例:
- (NSString *)scriptingUniqueID { // 用内存地址作为唯一ID,确保同一实例始终返回相同值 return [NSString stringWithFormat:@"0x%lx", (unsigned long)self]; }
同时在主应用的脚本化定义中,为A类添加uniqueID属性支持,这样AppleScript可以跳过临时容器B/C,直接通过ID定位对象。
2. 用NSMapTable简化弱引用中心容器
如果必须依赖中心容器,用NSMapTable管理弱引用集合,它原生适配ARC,会自动清理已释放的对象:
// 在AppDelegate中声明全局集合 @property (nonatomic, strong) NSMapTable<NSString*, __weak A*> *aInstanceMap; // 初始化集合 self.aInstanceMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory]; // A实例创建时注册到集合 - (instancetype)init { self = [super init]; if (self) { NSString *uniqueID = [self scriptingUniqueID]; [[AppDelegate sharedDelegate].aInstanceMap setObject:self forKey:uniqueID]; } return self; }
这个集合无需手动维护内存,ARC释放A实例时,对应的条目会自动移除,避免泄漏。
3. 直接返回对象自身的指定器
在B、C类中重写objectSpecifier方法,针对A实例直接返回其基于主应用的指定器,而非依赖B/C容器:
// 以B类为例,处理someInstancesOfA的元素指定器 - (NSScriptObjectSpecifier *)objectSpecifier { if ([self isKindOfClass:[A class]]) { return [[NSUniqueIDSpecifier alloc] initWithContainerClassDescription:[NSClassDescription classDescriptionForClass:[NSApplication class]] containerSpecifier:nil key:@"aInstances" uniqueID:[self scriptingUniqueID]]; } return [super objectSpecifier]; }
这里的key:@"aInstances"对应主应用脚本化定义中暴露的A实例集合属性,配合中心容器的NSMapTable,AppleScript就能通过主应用访问到A实例,不受B是否释放的影响。
4. 用NSHashTable维护存活的A实例
如果不想用中心容器,可以在A类中维护静态弱引用集合,快速获取所有存活实例:
static NSHashTable<A*> *allAInstances; + (void)initialize { if (self == [A class]) { allAInstances = [NSHashTable weakObjectsHashTable]; } } - (instancetype)init { self = [super init]; if (self) { [allAInstances addObject:self]; } return self; }
NSHashTable会自动清理已释放的A实例,无需遍历窗口就能获取所有有效实例,去重也更高效。
内容的提问来源于stack exchange,提问作者Thomas Tempelmann

