Протокол без понятых: ВС напомнил, когда протокол признают недействительным

Подписал протокол без видео: вернут ли права

Иллюстрация: Право.ru/Петр Козлов

Лишенный прав водитель попытался оспорить постановление суда. Главным аргументом было то, что сотрудники ГИБДД не сняли момент составления протоколов на видео. Дело дошло до ВС, который разъяснил обязаны ли инспекторы фиксировать все на видеозапись.

За отказ — лишение прав

На трассе Москва-Уфа сотрудники ГИБДД остановили машину Константина Воробьева*. На тот момент он находился в служебной командировке и направлялся в Санкт-Петербург. Во время проверки документов, инспекторы почувствовали запах алкоголя от водителя и попросили его «дунуть в трубочку». Сделать это автолюбитель отказался, не согласился он пройти и медосведетельстование. В отношении него составили протокол об административном правонарушении по ч. 1 ст. 12.26 КоАП («Невыполнение водителем транспортного средства требования о прохождении медицинского освидетельствования на состояние опьянения»). Водителя отстранили от управления авто, а саму иномарку направили на штрафстоянку. Момент составления протоколов сняли на видео.   

Вопрос о лишении прав водителя рассматривал мировой судья Челябинска, там, где проживал нарушитель. Автолюбитель заявил, что отказался «дунуть в трубочку», так как сотрудники ГИБДД дали ему алкотестер с уже установленным мундштуком, заменить его на новый не согласились. Понятых при этом не было, а когда водитель возмутился, инспекторы начали видеосъемку. По словам Воробьева, его заставили подписать все протоколы. Владелец авто заявил и о том, что машину у него не забрали. Он якобы поехал дальше и  добрался до Санкт-Петербурга. Защитник указал, что сотрудники ГИБДД не задержали иномарку, а значит, нарушили процедуру привлечения к административной ответственности.

С этими аргументами не согласился мировой судья. Он указал, что в материалах дела есть подписанный Воробьевым протокол о задержании его автомобиля. Вину водителя, по мнению суда, доказывают и другие представленные документы, в том числе об отказе водителя проверить содержание алкоголя крови. В результате Воробьева лишили прав на полтора года и назначили штраф в 30 000 руб (№ 3-12/2019). Такого же мнения оказались Калининский районный суд города Челябинска и Седьмой кассационный суд общей юрисдикции, ранее вынесенное решение они оставили без изменений.

Снимать, но не все

Тогда Воробьев направил жалобу в Верховный суд (дело № 48-АД20-4). Он просил прекратить производство по делу, ссылаясь на то, что на предоставленной инспекторами видеозаписи нет момента, когда они составляют протоколы, а он их подписывает. Водитель настаивал, что сделать это его заставили. 

Судья ВС Сергей Никифоров отметил, что отстранение от управления автомобилем должно происходить в присутствии двух понятых либо с применением видеозаписи (ч. 1, 2 ст. 27.12 КоАП РФ). Это требование инспекторы выполнили. На кадрах видно, что водителю разъяснили его права (предусмотренные ст. 25.1 КоАП и ст. 51 Конституции), а после сотрудники ГИБДД рассказали и об ответственности за отказ от медосвидетельствования. При этом давления на водителя не оказывалось.

То, что на видеозаписи нет момента составления протоколов, не влечет безусловную отмену судебных актов, посчитал ВС.

Все бумаги составлены при водителе, копии ему вручены, а замечаний по их составлению от Воробьева не поступало.  Причин удовлетворить жалобу водителя ВС не нашел (№ 48-АД20-4).

Аргумент, что на видео нет момента составления протокола, часто используют водители, которых привлекают к ответственности по ч. 1 ст. 12.26 КоАП, говорит Делгира Ходжаева из ЮФ Надмитов, Иванов и партнеры

Надмитов, Иванов и партнеры

Федеральный рейтинг.

группа
Арбитражное судопроизводство (средние и малые коммерческие споры — mid market)

. Во всех таких спорах, по словам эксперта, суды отклоняют доводы водителей, поскольку  видеозапись нужна для фиксации процессуальных действий: освидетельствования на месте, направление на медосвидетельствование, отстранение от управления автомобилем (в соответствии с ч. 2 ст. 27.12 КоАП). Закон не требует обязательно оформлять протокол с применением средств видеозаписи. В пример можно привести дела № 16-4799/2020 , № 16-93/2020 и № 16-462/2020, по которым суды не приняли доводы водителей в подобных случаях.

Ходжаева отметила, что видеозапись не всегда необходима даже при отстранении водителя от управления машиной. Доказательством вины может служить сам протокол, если будет доказано, что именно указанный водитель находился пьяным за рулем. Как это произошло по делу № 16-1044/2020.

*Имена и фамилии участников спора изменены редакцией

Является ли отсутствие понятых и видеозаписи основанием для признания протоколов недопустимыми? — Адвокат в Самаре и Москве — представительство в суде и юридические услуги

Здравствуйте. Является ли отсутствие понятых и видеозаписи основанием для признания протоколов недопустимыми?

Адвокат Антонов А.П.

Добрый день!

Согласно ст.28.2 Кодекса об административных правонарушениях, о совершении административного правонарушения составляется протокол, за исключением случаев, предусмотренных статьей 28.4, частями 1, 3 и 4 статьи 28.6 настоящего Кодекса.
В протоколе об административном правонарушении указываются дата и место его составления, должность, фамилия и инициалы лица, составившего протокол, сведения о лице, в отношении которого возбуждено дело об административном правонарушении, фамилии, имена, отчества, адреса места жительства свидетелей и потерпевших, если имеются свидетели и потерпевшие, место, время совершения и событие административного правонарушения, статья настоящего Кодекса или закона субъекта Российской Федерации, предусматривающая административную ответственность за данное административное правонарушение, объяснение физического лица или законного представителя юридического лица, в отношении которых возбуждено дело, иные сведения, необходимые для разрешения дела.
При составлении протокола об административном правонарушении физическому лицу или законному представителю юридического лица, в отношении которых возбуждено дело об административном правонарушении, а также иным участникам производства по делу разъясняются их права и обязанности, предусмотренные настоящим Кодексом, о чем делается запись в протоколе.
Физическому лицу или законному представителю юридического лица, в отношении которых возбуждено дело об административном правонарушении, должна быть предоставлена возможность ознакомления с протоколом об административном правонарушении. Указанные лица вправе представить объяснения и замечания по содержанию протокола, которые прилагаются к протоколу.
В случае неявки физического лица, или законного представителя физического лица, или законного представителя юридического лица, в отношении которых ведется производство по делу об административном правонарушении, если они извещены в установленном порядке, протокол об административном правонарушении составляется в их отсутствие. Копия протокола об административном правонарушении направляется лицу, в отношении которого он составлен, в течение трех дней со дня составления указанного протокола.
Протокол об административном правонарушении подписывается должностным лицом, его составившим, физическим лицом или законным представителем юридического лица, в отношении которых возбуждено дело об административном правонарушении. В случае отказа указанных лиц от подписания протокола, а также в случае, предусмотренном частью 4.1 настоящей статьи, в нем делается соответствующая запись.
Физическому лицу или законному представителю юридического лица, в отношении которых возбуждено дело об административном правонарушении, а также потерпевшему вручается под расписку копия протокола об административном правонарушении.
Согласно ст.26.2 Кодекса об административных правонарушениях, доказательствами по делу об административном правонарушении являются любые фактические данные, на основании которых судья, орган, должностное лицо, в производстве которых находится дело, устанавливают наличие или отсутствие события административного правонарушения, виновность лица, привлекаемого к административной ответственности, а также иные обстоятельства, имеющие значение для правильного разрешения дела.
Эти данные устанавливаются протоколом об административном правонарушении, иными протоколами, предусмотренными настоящим Кодексом, объяснениями лица, в отношении которого ведется производство по делу об административном правонарушении, показаниями потерпевшего, свидетелей, заключениями эксперта, иными документами, а также показаниями специальных технических средств, вещественными доказательствами.
Не допускается использование доказательств по делу об административном правонарушении, в том числе результатов проверки, проведенной в ходе осуществления государственного контроля (надзора) и муниципального контроля, если указанные доказательства получены с нарушением закона.
В качестве доказательств допускаются объяснения участников производства по делу об административном правонарушении, полученные путем использования систем видео-конференц-связи.
Таким образом, использование данного протокола в качестве доказательства не допускается.

С уважением, адвокат Анатолий Антонов, управляющий партнер адвокатского бюро «Антонов и партнеры».

Остались вопросы к адвокату?

Задайте их прямо сейчас здесь, или позвоните нам по телефонам в Москве +7 (499) 288-34-32 или в Самаре +7 (846) 212-99-71  (круглосуточно), или приходите к нам в офис на консультацию (по предварительной записи)!

Дата актуальности материала: 09.02.2020

Введение в протокольные свидетели — NSScreencast

Ссылки

  • Люк Редпат в Твиттере
  • https://github.com/lukeredpath/swift-validations
  • https://www.pointfree.co/collections/protocol-witnesses

Немного предыстории

Одним из ограничений протоколов является то, что вы можете иметь только одно соответствие. Возьмем этот пример, где у нас есть тип Discountable , и он реализует метод для возврата суммы скидки:

Протокол

 со скидкой {
    func со скидкой () -> Двойной
}
структура Покупка {
    количество переменных: Двойной
    var shippingAmount: Двойной
    функция со скидкой () -> Двойной {
        сумма * 0,9
    }
}
func printDiscount(_ со скидкой: со скидкой) -> String {
    пусть скидка = скидка.  со скидкой ()
    вернуть "Скидка: \(скидка)"
}
 

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

Преобразование в свидетеля

Сначала мы рассмотрим форму функций протокола. Похоже, у нас есть метод, который ничего не принимает ( Void ) и возвращает Double . Но это будет метод экземпляра, поэтому есть подразумеваемый экземпляр , который можно использовать, когда мы его примем. Таким образом, мы могли бы думать об этой функции как (Self) -> Double .

Затем мы создаем структуру, которая является универсальной для некоторого типа A . Мы создадим для этого типа свойство замыкания, соответствующее форме функций протокола.

 структура Дисконтирование {
    пусть со скидкой: (A) -> Double
}
 

Чтобы воспроизвести поведение, которое мы ранее использовали для нашего типа Покупка , теперь мы можем создать экземпляр этого типа Скидки :

 let PurchaseDiscount = Скидка<Покупка> { покупка в
        возврат покупки. сумма * 0,9
}
 

Это то, что называется «свидетель дисконтного типа» . Несмотря на то, что Discounting является структурой, это, по сути, то, к чему сводятся протоколы в компиляторе Swift.

Использование свидетеля

У нас была функция printDiscount выше, которая использовала скидку. Он был общим по сравнению с исходным протоколом, но теперь мы хотим изменить его, чтобы использовать свидетеля.

 func printDiscount(_ элемент: A, со скидкой: Discounting) -> String {
    // ...
}
 

Обратите внимание, что нас больше не волнует, что представляет собой экземпляр A , пока вы можете предоставить свидетель скидки (или стратегию ) для того же типа. Тело функции изменено незначительно, этот тип вызывает дисконтированный блок с экземпляром элемента .

 пусть скидка = скидка. со скидкой (товар)
    вернуть "Скидка: \(скидка)"
 

Call-сайт выглядит так:

 printDiscount(покупка, с помощью: PurchaseDiscount)
 

Свидетель как расширения

Одна из неприятных вещей заключается в том, что у нас просто есть эта свободная переменная PurchaseDiscount . Мы можем очистить это, переместив его в расширение на Дисконтирование для типа, на котором он работает:

Расширение

 Скидка, где A == Покупка {
    static let tenPercentOff = Discounting { покупка в
        сумма покупки * 0,9
    }
}
 

Теперь, когда у нас есть это, сайт вызова можно изменить на:

 printDiscount(покупка, с: .tenPercentOff)
 

Поскольку компилятор ожидает, что вторым аргументом здесь будет Discounting , мы можем получить хорошее автозаполнение для этого параметра, просто нажав «точку».

Отлично, если у вас более одного свидетеля:

Расширение

 Скидка, где A == Покупка {
    static let tenPercentOff = Discounting { покупка в
        сумма покупки * 0,9
    }
    static let FiveDollarsOff = Discounting { p in
        р.сумма - 5
    }
}
 

Теперь мы можем использовать другую стратегию, если захотим.

 printDiscount(покупка, с: . fiveDollarsOff)
 

Теперь мы начинаем понимать мощь этой идеи.

Откат?!

Голые со мной на имя. В конце концов это будет иметь смысл!

Одна вещь, которую мы здесь замечаем, заключается в том, что обе наши скидки на самом деле не имеют отношения к Покупке , они имеют дело с суммой , которая является Двойной . Что, если вместо этого мы переместим их в качестве свидетелей на тип Double .

Расширение

 Дисконтирование, где A == Double {
    static let tenPercentOff = Self { сумма в
        сумма * 0,9
    }
    static let FiveDollarsOff = Self { сумма в
        количество - 5
    }
}
 

Далее мы хотим использовать их при определении наших свидетелей покупки. Но как мы можем это сделать? В мире функционального программирования есть концепция, которую иногда называют контракартой, так как она чем-то похожа на перевернутую карту.

Форма выглядит так:

 // КАРТА
// дано Что-то и функция A -> B и производит Что-то
// КОНТРАМАП
// дано Что-то и функция B -> A и производит Что-то
 

К сожалению, contramap — это не лучшее имя, когда вы его используете. Другое название этого откат , и это, как правило, лучше читается на месте вызова, поэтому мы будем использовать его.

Определение функции отката не слишком сложно. Поскольку мы делаем это как расширение типа Discounting , у нас есть доступ к блоку, который принимает A , и когда мы возвращаем новый Discounting , мы определяем блок, который дает B , поэтому мы можем использовать предоставленную функцию для преобразования между ними.

Расширение

 Скидка {
    func pullback(_ f: @escaping (B) -> A) -> Дисконтирование {
        .init {другое -> Двойной вход
            self.discounted (f (другое))
        }
    }
}
 

Получив это, мы можем использовать его для превращения одного свидетеля в другого.

Расширение

 Скидка, где A == Покупка {
    static let tenPercentOff: Self = Discounting
        .тенпроцентофф
        .pullback(\.сумма)
    static let tenPercentOffShipping: Self = Discounting
            . тенпроцентофф
            .pullback(\.shippingAmount)
}
 

Эта концепция повторного использования функций и их компоновки действительно эффективна. Таким образом, хотя этот пример надуман, мы можем развить эту концепцию еще дальше, чтобы получить больше пользы от нашей работы.

Использование свидетелей «протокола» в Swift | Июньская вечеринка —

(Примечание: в этом посте используется Swift 5.3)

В последнее время я просматривал бэклог видео PointFree и многому научился. Однако больше всего мне запомнилась концепция использования универсальной структуры в качестве замены протоколов. Если вы когда-либо были разочарованы работой с протоколами, сталкиваясь с проблемами с , связанными с типами и Self и стиранием типов, то вы, возможно, немного взорвались.

Сценарий

Допустим, мы работаем над сетевым классом для приложения для социальных сетей. Наш бэкенд использует GraphQL, но использование фреймворка Apollo было бы излишним для наших целей, поэтому мы сами обрабатываем весь сетевой код. (Мы также будем использовать издатели фреймворка Combine, а не традиционные закрытия завершения; я напишу об этом отдельный пост в ближайшем будущем.)

Наша основная модель Person выглядит так:

 struct Person: Кодируемый {
    пусть идентификатор: UUID?
    имя переменной: строка
    пусть день рождения: Дата
    пусть друзья: [человек]
    init(id: UUID? = nil, имя: строка, дата рождения: дата, друзья: [человек] = []) {
        self.id = идентификатор
        self.name = имя
        self.birthdate = дата рождения
        друзья = друзья
    }
}
 

… и мы хотим, чтобы сигнатура основного метода запроса/мутации выглядела примерно так:

 func query(_ input: Input) -> AnyPublisher
 

…где QueryError — это просто структура, соответствующая Error .

Как сделать этот метод максимально простым в использовании, если сайт вызова может выглядеть так же просто?

 запрос (. addPerson (с именем: "June Bash", withBirthdate: myBirthdate))
    .sink(/* Здесь обрабатывается ответ */)
    .store(в: &cancellables)
 

перечисление?

Было бы неплохо иметь перечисление, в котором хранятся все наши запросы, например:

 перечисление QueryInput: Кодируемый {
    case addPerson(Человек)
    case fetchFriends(UUID)
    переменная строка запроса: строка {
        переключиться на себя {
            // вставляем сюда строку запроса для каждого случая
        }
    }
}
 

(запросы и мутации GraphQL форматируются очень специфическим образом; недавно в нашей работе над приложением Eco-Soap Bank мы справились с этим, чтобы иметь эти статические строки, которые будут отправляться как часть запроса. Обычно код для этих будет генерироваться платформой Apollo, но такого рода генерация кода и использование сторонних библиотек на самом деле не нужны, и это дает нам более детальный контроль над тем, как работает наше приложение.)

Но есть проблема: как метод query узнает, что будет на выходе? Возможно, мы можем добавить вычисляемое свойство в перечисление.

 расширение QueryInput {
    тип вывода вар: ??? {
        переключиться на себя {
        case .addPerson: вернуть UUID.self
        case .fetchFriends: вернуть [Person].self
        }
    }
}
 

… но каков на самом деле тип из outputType ? Ну, это может быть практически любой тип… так что давайте попробуем использовать Any.Type 9.0016 .

 расширение QueryInput {
    var outputType: Any.Type { /*...*/ }
}
 

Это скомпилируется, но на самом деле должно быть Декодируемое , поэтому давайте переключим его на…

 var outputType: Декодируемый.Тип
 

…но когда мы начнем корректировать наш метод запроса, мы столкнемся с дополнительными проблемами…

 func query(_ input: QueryInput) -> AnyPublisher
// Ошибка: "Свойство 'outputType' не является типом члена 'QueryInput'"
 

Компилятор не может сказать, каким должен быть вывод, и у нас нет никакого способа (насколько мне известно) сказать компилятору, чтобы он посмотрел на это вычисляемое свойство, чтобы определить тип. Мы могли бы сделать перечисление универсальным и просто передавать то, что мы ожидаем, каждый раз, но это усложнило бы сайт вызова больше, чем нам хотелось бы, и было бы слишком легко сделать ошибки программиста (например, я мог бы передать в запрос fetchFriends и сказать, что я ожидаю [Double: [UInt8]] назад).

Протокол?

Возможно, мы сможем создать протокол, которому будут соответствовать наши входные данные; что-то вроде этого:

 протокол QueryInput: кодируемый {
    связанный тип Выход: декодируемый
    статическая переменная queryString: строка {получить}
}
 

… и мы можем расширить нашу модель Person как таковую:

 расширение Лицо: QueryInput {
    typealias Выход = UUID
    static var queryString: String { "<вставьте сюда строку запроса GraphQL>" }
}
 

… а также UUID , который будет вводом для поиска друзей.

UUID расширения

: QueryInput {
    typealias Выход = [Человек]
    static var queryString: String { "<вставьте сюда строку запроса GraphQL>" }
}
 

Теперь мы можем настроить наш метод запроса, и, похоже, он может работать!

 func query(_ input: Input) -> AnyPublisher Output, QueryError>
 

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

 запрос (идентификатор человека)
    .раковина // ...и т.д...
 

Нелегко с первого взгляда понять, что мы занимаемся привлечением друзей. Хуже того, что, если бы существовала также модель Event со свойством id типа UUID , и мы хотели бы получить некоторую информацию об этом? Мы уже согласовали UUID с протоколом для использования с поиском друзей, и мы не можем соответствовать протоколу дважды.

Структуры оболочки с пространством имен?

Одним из решений может быть создание структуры-оболочки для каждого возможного ввода и вывода, каждый из которых будет соответствовать QueryInput . Мы могли бы даже «назначить пространство имён» в пустом перечислении (так фреймворк Combine использует пространства имён для некоторых своих издателей):

 перечисление QueryInputs {
    структура AddPerson: QueryInput {
        typealias Выход = UUID
        пусть имя: Строка
        пусть день рождения: Дата
        static let queryString = "<вставьте сюда строку запроса>"
    }
    структура FetchFriends: QueryInput {
        typealias Выход = [Человек]
        пусть идентификатор: UUID
        static let queryString = "<вставьте сюда строку запроса>"
    }
}
 

Теперь сайт вызова выглядит так:

 запрос (QueryInputs. AddPerson (имя: «June Bash», дата рождения: myBirthdate))
   .sink( // и т.д...
 

Думаю, это сработает!

…Но могло быть и лучше. Это немного длиннее, чем наш «идеал», с которого мы начали. Поскольку QueryInputs (множественное число) на самом деле не является типом этого ввода, мы не можем ничего там удалить.

Еще хуже, допустим, мы хотим создать массив из нескольких разных запросов, которые возвращают Лицо . Когда мы переходим к инициализации этого массива, мы сразу же сталкиваемся с проблемой…

 разрешить запросы: [QueryInput] = []
// Ошибка: протокол 'QueryInput' может использоваться только как универсальное ограничение, поскольку он имеет требования к Self или связанному типу
 

Эта ужасная ошибка собственного/связанного типа. Мы также не можем добавить к нему дженерик; протокол с ассоциированным типом технически не является универсальным типом.

Это, насколько я понял, ограничение языка, и поэтому сложно тип стирания существует. О работах по стиранию шрифта я напишу позже отдельный пост, но для наших целей есть гораздо более простое, элегантное и, на мой взгляд, более интересное решение.

Свидетели «Протокола»

Мы можем выполнить то, что хотим, используя одну простую универсальную структуру. И хотя ребята из PointFree любят называть это «свидетелем протокола», несмотря на название, нам вообще не нужен никакой протокол.

 struct QueryInput<Вывод: Декодируемый>: Кодируемый {
    ввод var: кодируемый
    переменная строка запроса: строка
    init(_ input: кодируемый, queryString: String) {
        self.input = ввод
        self.queryString = строка запроса
    }
    func encode (для кодировщика: кодировщик) выдает {
        попробуйте input.encode (в: кодировщик)
    }
}
 

Нашими входными данными могут быть все, что можно закодировать; что имеет значение, так это тип вывода. Это чем-то похоже на то, как работает традиционное стирание шрифта, но требует гораздо меньше усилий.

Одна вещь, которую вы можете заметить, это то, что мы видим тип вывода в сигнатуре... но он нигде не используется (пока). Это «фантомный тип» (подробнее о нем можно прочитать в разделе «Взлом с помощью Swift»). Он нигде не хранится в экземпляре типа, но его можно использовать, когда мы передаем его в наш метод запроса.

Итак, как нам построить один из них? Было бы мучительно давать ему строку запроса каждый раз, когда мы запускаем запрос. К счастью, мы можем воспользоваться преимуществами языка, чтобы добавить несколько удобных «инициализаторов»…!

 расширение QueryInput, где Output == UUID {
    статическая функция addPerson(_ person: Person) -> QueryInput {
        QueryInput(person, queryString: "<вставьте сюда запрос>")
    }
}
 

И мы можем сделать несколько быстрых изменений, чтобы сделать его еще проще, особенно на телефонной станции:

 расширение QueryInput, где Output == UUID {
    статическая функция addPerson(
        именованное имя: Строка,
        withBirthdateдата рождения: Дата
    ) -> Сам {
        Себя(
            Человек(имя: имя, дата рождения: дата рождения),
            queryString: "<вставьте сюда запрос>"
        )
    }
}
 

Мы можем заменить QueryInput на Self , поскольку компилятор может вывести эти особенности из предложения where в подписи расширения. Это упростит задачу, поскольку мы добавим больше статических удобных методов для наших различных запросов.

На первый взгляд все остальное может показаться более сложным, но сравните два callsite для этих реализаций:

 запрос(
    .добавить человека (
        Человек(
            название: "Июнь Баш",
            Дата рождения: Дата()
        )
    )
)
запрос(
    .добавить человека (
        названный: «Июньский Баш»,
        сДата рождения: Дата()
    )
)
 

Первый имеет дополнительный уровень вложенности (которого второй избегает), и второй позволяет нам настроить имена наших параметров, чтобы они читались почти как предложение («запрос: добавить человека по имени 'June Bash' с датой рождения июньноябрь 41nd , 1010220"). Что вы предпочитаете, может зависеть от контекста; Вы могли бы даже оставить оба, если бы это имело смысл!

Кроме того, обратите внимание, что мы наконец-то получили чистый синтаксис для вызова наших запросов, которые мы намеревались достичь в самом начале! И теперь у нас могут быть массивы разных запросов, которые ожидают один и тот же вывод, как и при традиционном стирании типа:

 расширение QueryInput, где Output == Person {
    статическая функция fetchPerson (имя withName: String) -> Self {
        Self(name, queryString: "<вставьте сюда строку запроса>")
    }
    статическая функция fetchOrganizer (идентификатор forEventID: UUID) -> Self {
        Self(id, queryString: "<здесь строка запроса>")
    }
}
var запросы: [QueryInput] = [
    . fetchPerson(withName: «Июньская вечеринка»),
    .fetchOrganizer(forEventID: UUID()),
    .fetchPerson(withName: "Джим Баш")
]
 

Что дает нам мощную гибкость:

 var cancellables = Set()
вар люди = [Человек]()
для q в запросах {
    запрос(к)
        .sink { завершение в
            if case .failure(пусть ошибка) = завершение {
                распечатать (ошибка)
            }
        } receiveValue: {человек в
            люди.добавлять(человек)
        }.store(в: &cancellables)
}
 

… И мы могли бы даже использовать Combine, чтобы сделать это еще чище (подробнее о том, что все это значит позже…):

 запросов.издатель
    .flatMap {$0.run()}
    .собирать()
    .sink { завершение в
        если пусть e = завершение.ошибка { print(e) }
    } receiveValue: { люди в
        // обрабатывать полный массив людей
    }.store(в: &cancellables)
 

На данный момент, в зависимости от сценария, может даже не быть причин для создания отдельного сетевого класса; мы могли бы запустить запрос прямо из этой структуры, возможно, с быстрым переименованием…

 struct Query<Вывод: Декодируемый>: Кодируемый {
    ввод var: кодируемый
    переменная строка запроса: строка
    init(_ input: кодируемый, queryString: String) {
        self. input = ввод
        self.queryString = строка запроса
    }
    func encode (для кодировщика: кодировщик) выдает {
        попробуйте input.encode (в: кодировщик)
    }
    func run() -> AnyPublisher {
        // делаем работу по кодированию данных, делаем запрос, запускаем запрос, расшифровываем данные и т.д.
    }
}
 

Теперь запрос на получение человека с определенным именем выполняется просто:

 Query.fetchPerson(withName: "June Bash").run()
    .sink( // и т.д...
 

Мы могли бы даже передать отдельные кодировщики или декодеры для разных запросов, разных URLSessions (или фиктивных замен), разных URL или конечных точек или даже замыкания вместо run . Все, что нам нужно сделать, это добавить свойство и использовать его в методе run() .

Выводы

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

Leave a Reply