VSCode 中的所有基础功能都被视为服务 Service,比如:右键菜单 ContextMenuService、剪贴板 ClipboardService,大部分 Service 都继承自一个抽象类 - Disposable,而该抽象类继承自接口 IDisposable。
4.1 Service 的底层基类
Interface Disposable
src/vs/base/common/lifecycle.ts
接口只有一个 dispose 方法,用于对象销毁时取消监听器或清理数据。
1 2 3 4 5 6 7 8 9 10 11 12
/** * An object that performs a cleanup operation when `.dispose()` is called. ** Some examples of how disposables are used: * * - An event listener that removes itself when `.dispose()` is called. * - A resource such as a file system watcher that cleans up the resource when `.dispose()` is called. * - The return value from registering a provider. When `.dispose()` is called, the provider is unregistered. */
exportinterfaceIDisposable { dispose(): void; }
Abstract Class IDisposable
src/vs/base/common/lifecycle.ts
抽象类对接口做了一些公用用方法和属性扩展:
None:一个空的 disposable 对象
_store:管理多个 disposable 对象的存储空间,一个 Service 可能会存放多个 disposable 对象(内部实现的 Emitter 事件触发器也属于 disposable,后面会专门说明 Emitter 的几种实现方式),使用 Store 统一管理起来,当服务实例销毁时会自动调用这些 disposable 对象。_store 自身也是 disposable 的。
/** * Abstract base class for a {@link IDisposable disposable} object. * * Subclasses can {@linkcode _register} disposables that will be automatically cleaned up when this object is disposed of. */ exportabstractclassDisposableimplementsIDisposable {
/** * A disposable that does nothing when it is disposed of. * * TODO: This should not be a static property. */ staticreadonlyNone = Object.freeze<IDisposable>({ dispose() { } });
/** * Adds `o` to the collection of disposables managed by this object. */ protected _register<T extendsIDisposable>(o: T): T { if ((o asunknownasDisposable) === this) { thrownewError('Cannot register a disposable on itself!'); } returnthis._store.add(o); } }
通过这种模式,Client 在使用的时候不需要去自己构造需要的 Service 对象,这样的好处之一就就是将对象的构造和行为分离,在引入接口后,Client 和 Service 的依赖关系只需要接口来定义,Client 在构造函数参数中主需要什么依赖的服务接口,结合注入器,能给客户对象更多的灵活性和解耦。
/** * Synchronously creates an instance that is denoted by the descriptor */ createInstance<T>(descriptor: descriptors.SyncDescriptor0<T>): T; createInstance<Ctorextendsnew (...args: any[]) => any, R extendsInstanceType<Ctor>>(ctor: Ctor, ...args: GetLeadingNonServiceArgs<ConstructorParameters<Ctor>>): R;
/** * Calls a function with a service accessor. */ invokeFunction<R, TSextendsany[] = []>(fn: (accessor: ServicesAccessor, ...args: TS) => R, ...args: TS): R;
/** * Creates a child of this service which inherits all current services * and adds/overwrites the given services. */ createChild(services: ServiceCollection): IInstantiationService; }
ServiceIdentifier 即描述 Service 的标识符对象,对象上有一个 type 字段。其使用方式是作为 Service 构造函数参数的装饰器,TS 解析类定义时(非实例化阶段)会调用 ServiceIdentifier 内部逻辑来收集依赖关系,在类的实例化阶段会使用这些依赖信息来创建依赖图谱进行依赖分析,在 Service 实例化之前确保所有依赖服务已经被创建好了。
let cycleCount = 0; const stack = [{ id, desc, _trace }]; while (stack.length) { const item = stack.pop()!; graph.lookupOrInsertNode(item);
// a weak but working heuristic for cycle checks if (cycleCount++ > 1000) { thrownewCyclicDependencyError(graph); }
// check all dependencies for existence and if they need to be created first for (const dependency of _util.getServiceDependencies(item.desc.ctor)) {
const instanceOrDesc = this._getServiceInstanceOrDescriptor(dependency.id); if (!instanceOrDesc) { this._throwIfStrict(`[createInstance] ${id} depends on ${dependency.id} which is NOT registered.`, true); }
// take note of all service dependencies this._globalGraph?.insertEdge(String(item.id), String(dependency.id));
// if there is no more roots but still // nodes in the graph we have a cycle if (roots.length === 0) { if (!graph.isEmpty()) { thrownewCyclicDependencyError(graph); } break; }
for (const { data } of roots) { // Repeat the check for this still being a service sync descriptor. That's because // instantiating a dependency might have side-effect and recursively trigger instantiation // so that some dependencies are now fullfilled already. const instanceOrDesc = this._getServiceInstanceOrDescriptor(data.id); if (instanceOrDesc instanceofSyncDescriptor) { // create instance and overwrite the service collections const instance = this._createServiceInstanceWithOwner(data.id, data.desc.ctor, data.desc.staticArguments, data.desc.supportsDelayedInstantiation, data._trace); this._setServiceInstance(data.id, instance); } graph.removeNode(data); } } return <T>this._getServiceInstanceOrDescriptor(id); } ...
// arguments defined by service decorators const serviceDependencies = _util.getServiceDependencies(ctor).sort((a, b) => a.index - b.index); constserviceArgs: any[] = []; for (const dependency of serviceDependencies) { const service = this._getOrCreateServiceInstance(dependency.id, _trace); if (!service) { this._throwIfStrict(`[createInstance] ${ctor.name} depends on UNKNOWN service ${dependency.id}.`, false); } serviceArgs.push(service); }
// check for argument mismatches, adjust static args if needed if (args.length !== firstServiceArgPos) { console.trace(`[createInstance] First service dependency of ${ctor.name} at position ${firstServiceArgPos + 1} conflicts with ${args.length} static arguments`);
// Set the error handler early enough so that we are not getting the // default electron error dialog popping up setUnexpectedErrorHandler(err =>console.error(err));
// Write a lockfile to indicate an instance is running FSPromises.writeFile(environmentMainService.mainLockfile, String(process.pid)).catch(err => { logService.warn(`app#startup(): Error writing main lockfile: ${err.stack}`); });
// Try to setup a server for running. If that succeeds it means // we are the first instance to startup. Otherwise it is likely // that another instance is already running. letmainProcessNodeIpcServer: NodeIPCServer; ...
/** * The main VS Code application. There will only ever be one instance, * even if the user starts many instances (e.g. from the command line). */ exportclassCodeApplicationextendsDisposable {
// We handle uncaught exceptions here to prevent electron from opening a dialog to the user process.on('uncaughtException', error => {...}); process.on('unhandledRejection', (reason: unknown) =>onUnexpectedError(reason));
// Dispose on shutdown this.lifecycleMainService.onWillShutdown(() =>this.dispose()); ... // macOS dock activate app.on('activate', async (event, hasVisibleWindows) => { this.logService.trace('app#activate');
// Mac only event: open new window when we get activated if (!hasVisibleWindows) { awaitthis.windowsMainService?.openEmptyWindow({ context: OpenContext.DOCK }); } });
// Signal phase: ready - before opening first window this.lifecycleMainService.phase = LifecycleMainPhase.Ready;
// Open Windows await appInstantiationService.invokeFunction(accessor =>this.openFirstWindow(accessor, initialProtocolUrls));
// Signal phase: after window open this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen;
// Post Open Windows Tasks this.afterWindowOpen();
// Set lifecycle phase to `Eventually` after a short delay and when idle (min 2.5sec, max 5sec) const eventuallyPhaseScheduler = this._register(newRunOnceScheduler(() => { this._register(runWhenIdle(() =>this.lifecycleMainService.phase = LifecycleMainPhase.Eventually, 2500)); }, 2500)); eventuallyPhaseScheduler.schedule(); }