You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.11 18:42:36