无法调用存储在对象中的回调

问题描述 投票:0回答:1

我创建了一个内存通知服务,供在我的 Dart/Flutter 代码库中使用。我开始使用观察者模式,但很快发现订阅深度嵌套视图模型中的更改变得痛苦且令人费解。

目的是创建一个可以以强类型方式发布通知的单例服务。消费者可以订阅有针对性的通知,还可以以强类型的方式接收有效负载。

我编写了两个不同的通知,它们有效地提供了相同的有效负载。第一个有效,第二个抛出以下运行时错误。

Unhandled exception:
type '(ExampleNotification) => void' is not a subtype of type '(PublishedNotification) => dynamic'
#0      NotificationService.publish (package:console/notification_service.dart:42:24)
#1      main (package:console/console.dart:15:23)
#2      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:297:19)
#3      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)

每个通知都扩展一个基类

PublishedNotification

abstract class PublishedNotification {
  String qualifier;

  PublishedNotification(this.qualifier);

  @override
  String toString() {
    return 'PublishedNotification{qualifier: $qualifier}';
  }
}
import 'package:console/published_notification.dart';

class HelloWorldNotification extends PublishedNotification {
  HelloWorldNotification() : super('Hello World Message');
}
import 'package:console/published_notification.dart';

class ExampleNotification extends PublishedNotification {
  ExampleNotification() : super('This is an example');
}

每个通知都可以由任何人发布,并且只有订阅者才会收到通知。当对象订阅通知时,会创建一个订阅,其中包含要在订阅者上调用的回调以及订阅所针对的通知类型。

import 'dart:mirrors';

import 'package:console/published_notification.dart';

class Subscription<T extends PublishedNotification> {
  final Function(T) subscriberCallback;
  Type targetNotification = reflectClass(T).reflectedType;

  Subscription(this.subscriberCallback);
}

订阅、发布和取消订阅是通过

NotificationService
完成的。调用
subscribe
方法会让订阅者传递一个回调,每次发布通知时都会调用该回调。它还返回
Subscription
本身,以便调用者可以在处置过程中取消订阅和清理。

当调用

publish
时,它需要一个扩展
PublishedNotification
的参数。使用一些反射来确定类型,然后我从订阅者集合中提取所有订阅并通过通知调用它们的回调。

import 'dart:mirrors';

import 'package:console/published_notification.dart';
import 'package:console/subscription.dart';

class NotificationService {
  // List of subscribers grouped by notification type.
  final Map<Type, List<Subscription>> subscriptions = <Type, List<Subscription>>{};

  // Subscribe to a notification.
  // notification is a required named parameter. 
  // A secondary 'condition' parameter will be added to support conditional pushing a notification to a subscriber based on if the condition passes or not.
  Subscription<T> subscribe<T extends PublishedNotification>({required Function(T payload) notification}) {
    // Wrap notification callback into a subscriber.
    final newSubscriber = Subscription(notification);

    ClassMirror notificationClass = reflectClass(T);
    Type notificationType = notificationClass.reflectedType;

    // Store the subscriber into our subscriptions collection.
    late List<Subscription> subscribers;
    if (subscriptions[notificationType] == null) {
      subscribers = List.empty(growable: true);
      subscriptions[notificationType] = subscribers;
    }

    subscribers.add(newSubscriber);

    // Return so that it can be provided during an unsubscribe request.
    return newSubscriber;
  }

  // Publish a notification to all subscribers
  void publish<T extends PublishedNotification>(T notification) {
    Type notificationType = notification.runtimeType;
    if (subscriptions[notificationType] == null) {
      return;
    }

    List<Subscription> subscribers = subscriptions[notificationType]!;

    // Invoke the callback delegate we have stored within each subscriber.
    for (Subscription targetSubscriber in subscribers) {
      targetSubscriber.subscriberCallback(notification);
    }
  }

  // Unsubscribe the
  void unsubscribe(Subscription subscriber) {
    subscriptions[subscriber.targetNotification]?.remove(subscriber);
  }
}

我创建了一个示例控制台 Dart 应用程序来重现我所看到的问题。我订阅了两个不同的通知,发布通知,然后取消订阅。当

HelloWorldNotification
发布时,我的回调被调用,没有任何问题。当
ExampleNotification
发布时,我收到了上面显示的异常。

import 'package:console/example_notification.dart';
import 'package:console/hello_world_notification.dart';
import 'package:console/notification_service.dart';
import 'package:console/published_notification.dart';
import 'package:console/subscription.dart';

void main() {
  var notificationService = NotificationService();
  Subscription helloWorldSubcription =
      notificationService.subscribe<HelloWorldNotification>(notification: handleHelloWorldNotification);
  Subscription exampleSubscription =
      notificationService.subscribe<ExampleNotification>(notification: handleExampleNotification);

  notificationService.publish(HelloWorldNotification());
  notificationService.publish(ExampleNotification());

  notificationService.unsubscribe(helloWorldSubcription);
  notificationService.unsubscribe(exampleSubscription);

  // These should not hit the handlers below.
  notificationService.publish(HelloWorldNotification());
  notificationService.publish(ExampleNotification());
}

void handleHelloWorldNotification(PublishedNotification payload) {
  print(payload.qualifier);
}

void handleExampleNotification(ExampleNotification payload) {
  print(payload.qualifier);
}

我不明白为什么第一个通知可以正常工作,而第二个通知却不能。从实现的角度来看,它们是相同的,并且回调没有任何不同。我假设这是我如何推断

NotificationService
中的泛型的问题,但我不能 100% 确定为什么会出现这种情况。

我可以在 for 循环中为每个

Subscription
进行强制转换,问题就会消失,一切都会按预期进行。

  // Publish a notification to all subscribers
  void publish<T extends PublishedNotification>(T notification) {
    Type notificationType = notification.runtimeType;
    if (subscriptions[notificationType] == null) {
      return;
    }

    List<Subscription> subscribers = subscriptions[notificationType]!;

    // Invoke the callback delegate we have stored within each subscriber.
    for (Subscription targetSubscriber in subscribers) {
      var s = targetSubscriber as Subscription<T>; // Cast here
      s.subscriberCallback(notification);
    }
  }

我的

publish
方法的实现以及我存储订阅回调的方式是否有明显的错误?我已经修复了,但我想了解为什么第一个发布有效而第二个发布无效,除非我强制强制转换。最终是否需要强制转换,或者是否有办法进行一些重构,以便不需要强制转换?

谢谢!

编辑

好吧,看来 Flutter 不支持

dart:mirrors
。我不得不寻找一种不同的方式来捕获类型信息,并发现我实际上不需要使用
reflactClass
。当我更新
NotificationService
以删除
dart:mirrors
的使用时,异常停止发生。

import 'dart:mirrors';

import 'package:console/published_notification.dart';
import 'package:console/subscription.dart';

class NotificationService {
  // List of subscribers grouped by notification type.
  final Map<Type, List<Subscription>> subscriptions = <Type, List<Subscription>>{};

  // Subscribe to a notification.
  @override
  Subscription<T> subscribe<T extends PublishedNotification>({required Function(T payload) notification}) {
    // Wrap notification callback into a subscriber.
    final newSubscriber = Subscription(notification);

    // Store the subscriber into our subscriptions collection.
    late List<Subscription> subscribers;
    if (subscriptions[T] == null) {
      subscribers = List.empty(growable: true);
      subscriptions[T] = subscribers;
    }

    subscribers.add(newSubscriber);

    // Return so that it can be provided during an unsubscribe request.
    return newSubscriber;
  }

  // Publish a notification to all subscribers
  @override
  void publish<T extends PublishedNotification>(T notification) {
    if (subscriptions[T] == null) {
      return;
    }

    List<Subscription> subscribers = subscriptions[T]!;

    // Invoke the callback delegate we have stored within each subscriber.
    for (Subscription targetSubscriber in subscribers) {
      targetSubscriber.notify(notification);
    }
  }

  // Unsubscribe the
  @override
  void unsubscribe(Subscription subscriber) {
    subscriptions[subscriber.targetNotification]?.remove(subscriber);
  }
}

此更改也适用于我的

Subscription
课程。

class Subscription<T extends PublishedNotification> {
  final Function(T) _subscriberCallback;
  Type targetNotification = T;

  Subscription(this._subscriberCallback);

  void notify(T notification) {
    _subscriberCallback(notification);
  }
}

我不再需要演员阵容,一切似乎都按预期进行。我仍然有兴趣了解为什么

reflectClass.reflectedType
会导致问题,但只是将
T
直接传递到所有内容中却不会。

flutter dart generics
1个回答
0
投票

考虑以下代码:

class PublishedNotification {}
class Subscription<T extends PublishedNotification> {  }
class GenericClass<T> {}

void main() {
  print(GenericClass().runtimeType); // GenericClass<dynamic>
  print(Subscription().runtimeType); // Subscription<PublishedNotification>
}

正如您在上面所看到的,由于您使用

T extends PublishedNotification
子句约束泛型类型,因此当您使用
Subscription
作为原始类型时,它现在相当于
Subscription<PublishedNotification>
,而不是您可能期望的
Subscription<dynamic>

考虑到这一点,以下代码

for (Subscription targetSubscriber in subscribers) {
  targetSubscriber.subscriberCallback(notification);
}

确实是,

for (Subscription<PublishedNotification> targetSubscriber in subscribers) {
  targetSubscriber.subscriberCallback(notification);
}

现在您正在使用上述内容和以下回调:

void handleHelloWorldNotification(PublishedNotification payload) {
  print(payload.qualifier);
}

void handleExampleNotification(ExampleNotification payload) {
  print(payload.qualifier);
}

第一个回调没有问题,因为类型与

PublishedNotification
匹配,但在第二个回调中出现问题。
Subscription<PublishedNotification>
应该能够对任何
PublishedNotification
使用回调,但实际回调仅限于仅接受
ExampleNotification
,这就是发生异常的原因。

© www.soinside.com 2019 - 2024. All rights reserved.