Можно ли передать пользовательский класс в изолят в Dart?

Ответ

Да, но с важными ограничениями. Поскольку изоляты в Dart не разделяют память и работают в отдельных потоках, передаваемые объекты должны быть сериализуемыми. Это означает, что можно передавать:

  • Примитивные типы (int, double, bool, String, null)
  • Коллекции (List, Map, Set) из сериализуемых элементов
  • Пользовательские классы, которые можно преобразовать в простые структуры данных

Пример передачи класса через сериализацию:

// Класс с методами сериализации/десериализации
class UserData {
  final String name;
  final int id;
  final List<String> tags;

  UserData(this.name, this.id, this.tags);

  // Преобразование в Map для передачи
  Map<String, dynamic> toJson() => {
    'name': name,
    'id': id,
    'tags': tags,
  };

  // Восстановление из Map
  factory UserData.fromJson(Map<String, dynamic> json) => 
      UserData(json['name'], json['id'], List<String>.from(json['tags']));
}

// В основном изоляте
void spawnIsolate() async {
  final receivePort = ReceivePort();
  final isolate = await Isolate.spawn(
    _isolateEntry,
    receivePort.sendPort,
  );

  // Отправляем данные в изолят
  final userData = UserData('John', 1, ['admin', 'premium']);
  receivePort.sendPort.send(userData.toJson());
}

// Функция-точка входа изолята
void _isolateEntry(SendPort mainSendPort) {
  final receivePort = ReceivePort();
  mainSendPort.send(receivePort.sendPort);

  receivePort.listen((message) {
    if (message is Map) {
      // Десериализуем полученные данные
      final data = UserData.fromJson(Map<String, dynamic>.from(message));
      print('Received in isolate: ${data.name}, ${data.id}');
    }
  });
}

Ключевые ограничения:

  1. Нельзя передать объект с методами или замыканиями — передаются только данные
  2. Сложные графы объектов требуют полной сериализации всех связанных объектов
  3. Для автоматической сериализации используйте пакеты типа json_serializable или freezed
  4. Производительность: сериализация/десериализация добавляют накладные расходы

На практике я часто использую freezed для создания иммутабельных классов с автоматической сериализацией, что упрощает работу с изолятами.

Ответ 18+ 🔞

А, ну это про изоляты в Dart! Ёпта, тема интересная, но с подводными камнями, как всегда. Слушай сюда, разжуёшь.

Представь, что твои изоляты — это как два мужика в разных камерах. Они могут перекрикиваться через окошко, но передать друг другу бутерброд или отвёртку — нихуя. Только записочки. Вот эти «записочки» — это и есть сериализуемые данные. Всё, что можно превратить в простой текст или структуру, которую на той стороне можно обратно собрать.

Что пролезет в это окошко? Да похуй, список простой:

  • Примитивы всякие: числа, строки, булево, null (ну, это само собой).
  • Коллекции (List, Map, Set), но только если внутри них тоже сидят такие же приличные, сериализуемые ребята. Не суй туда какую-нибудь свою кастомную хрень с методами — не пролезет.
  • Свои классы, но только если ты их предварительно раздолбил на эти самые примитивные кирпичики.

Вот смотри, как это выглядит на практике, чтобы не быть голословным:

// Допустим, у тебя есть класс пользователя. Нормальный такой чувак.
class UserData {
  final String name;
  final int id;
  final List<String> tags;

  UserData(this.name, this.id, this.tags);

  // Вот магический метод! Берёт этого чувака и делает из него бездушную, но передаваемую мапу.
  Map<String, dynamic> toJson() => {
    'name': name,
    'id': id,
    'tags': tags,
  };

  // А это обратная магия. Пришла мапа с той стороны — и вот тебе снова живой класс.
  factory UserData.fromJson(Map<String, dynamic> json) => 
      UserData(json['name'], json['id'], List<String>.from(json['tags']));
}

// В основном потоке (в первой камере) делаем так:
void spawnIsolate() async {
  final receivePort = ReceivePort(); // Это наше ухо к окошку.
  final isolate = await Isolate.spawn(
    _isolateEntry, // Функция, которая будет работать в соседней камере.
    receivePort.sendPort, // Даём соседу наш адрес для ответок.
  );

  // Готовим данные и отправляем.
  final userData = UserData('Vasya', 1, ['admin', 'premium']);
  receivePort.sendPort.send(userData.toJson()); // Ключевой момент! Не объект, а toJson()!
}

// А это уже функция, которая живёт в той, второй камере.
void _isolateEntry(SendPort mainSendPort) {
  final receivePort = ReceivePort();
  mainSendPort.send(receivePort.sendPort); // Говорим основному потоку: "Я тут, шли на этот адрес!"

  receivePort.listen((message) {
    if (message is Map) {
      // Получили мешок с костями (мапу). Оживляем.
      final data = UserData.fromJson(Map<String, dynamic>.from(message));
      print('Получено в изоляте: ${data.name}, ${data.id}'); // Всё, Вася воскрес!
    }
  });
}

Теперь про ограничения, а их, блядь, овердохуища, и их надо чётко понимать, а то будешь потом головой об стенку биться:

  1. Объект с методами или замыканиями не отправишь. Вообще. Забудь. Это как пытаться передать по радио бутылку пива. Только данные, голые и беспомощные.
  2. Сложные объекты, которые ссылаются друг на друга — это отдельная песня. Их всю эту паутину связей надо аккуратно разобрать и упаковать. Самому это делать — терпения ноль ебать, честно.
  3. Автоматизация — твой друг. Не городи эти toJson/fromJson руками для каждого класса, ёб твою мать. Бери пакеты вроде json_serializable или, что ещё лучше, freezed. Они за тебя всю эту рутину нагенерируют. freezed вообще красава — делает классы неизменяемыми (immutable), что для параллельных вычислений самое то, и сериализацию из коробки даёт.
  4. Производительность. Сериализация и десериализация — это не бесплатно. Если ты гоняешь мегабайты данных туда-сюда по сто раз в секунду, подумай, а надо ли. Может, проще по-другому задачу решить.

Короче, суть в чём: изоляты — мощная хуйня для настоящего параллелизма, но общение между ними — только через «бутылочную почту» с сериализованными данными. Привыкай к этому, и всё будет пизда рулю.