API for Any (thing)

от автора

Возможно ли создать интерфейс для получения любого объекта одинаковым способом?
Это исследование данных: Как в коде программы мы получаем, создаем, трансформируем и передаем объекты.

Мой опыт разработки под iOS с 2008 года ???
Примеры на Swift ?

Идея

Представьте, что любой предмет можно получить. Достаточно точно его описать.
План:

  1. Описать предмет.

  2. Создать запрос.

  3. Получить.

Идеальный вариант

Например, гео

В мире объектно-ориентированного программирования все предметы- это объекты. У каждого объекта есть тип.

no Swift, please

Например, такие объекты из стандартного комплекта разработки:

CLLocation — геопозиция пользователя;

CMPedometerData — данные системного шагомера;

VNHumanHandPoseObservation — данные о положении человеческих рук (на фото, видео и др.) с использованием компьютерного зрения.

Когда в приложении нужно использовать геопозицию пользователя, программист реализует логику в коде вручную или использует библиотеки.
Любым способом получая объект CLLocation.

Самая популярная библиотеĸа для получения гео на GitHub- это SwiftLocation.
Больше 1000 строĸ тольĸо ĸода без тестов. И для ĸаждого типа объеĸта мы должны находить решение или писать свое, используя опыт других.

У всех разработчиков со временем складывается личный набор инструментов для разных нужд (не пропустите холивар). Но как в этом разобраться новому человеку? Человеку не из мобильной разработки.
Эти инструменты абсолютно не стандартизированы.

Единый интерфейс для черных ĸоробоĸ?

Асинхронно

Обычно сейчас используют блоки (блок = лямбда, замыкание) для обработки данных полученных асинхронно:

{ (location: CLLocation) in  }

Первый пункт готов:
✅ Описать предмет

Тип объекта- это и есть точное описание данных, которые ожидаются в рамках блока.

Что там на входе?

Часть объектов может быть создана или запрошена с настройками по умолчанию. Другие зависят от объектов в контексте или должны быть созданы с опциями.

Перед запросом необходимо создать зависимости и добавить в контекст:

//Объект от данных которого зависит предмет запроса Any  //Не упорядоченная куча объектов разного типа Set<Any>  //Объекты с ключами не по умолчанию Dictionary<String, Any> 

Например, гео

При запросе геопозиции пользователя, программист обычно настраивает точность:

//Например, геопозиция пользователя с точностью до ± сотен метров accuracy: CLLocationAccuracy = kCLLocationAccuracyHundredMeters

И дистанцию обновления геопозиции в метрах:

//Обновлять каждые 250 метров filter: CLLocationDistance = 250

Порядок не имеет значения:

["CLLocationDistance": 250,  "CLLocationAccuracy": kCLLocationAccuracyHundredMeters]

Все данные для создания объектов должны быть в контексте до начала запроса.

Запрос

Вход- это данные в контексте, выход- это ожидаемый объект. Создаем запрос.

Функции в Swift

Functions are self-contained chunks of code that perform a specific task. You give a function a name that identifies what it does, and this name is used to “call” the function to perform its task when needed.

//Функция с 2 аргументами типа String, возвращает String func greet(person: String, title: String) -> String {   }  static func | (date: Date, format: String) -> String {  }

Примерная хронология размышлений

— Придумать архитеĸтуру, новые абстраĸции? 

— Коротĸое и емĸое имя?

— В один символ?

— Мы используем всю ĸлавиатуру?

— Каĸ передают данные между программами с теĸстовым интерфейсом?

Примерно два года размышлений и переписывания с нуля превратились в Пайп.

Pipe

англ. pipeline в терминологии операционных систем семейства Unix — некоторое множество процессов, для которых выполнено следующее перенаправление ввода-вывода: то, что выводит на поток стандартного вывода предыдущий процесс, попадает в поток стандартного ввода следующего процесса. Запуск конвейера реализован с помощью системного вызова pipe().

Я представляю этот знак как трубу которая связывает объекты на разных концах:

command1 | command2 | command3 ls -l | grep key | less

Добавьте один знак | к блоку и получайте данные:

|{ (location: CLLocation) in  }

Положите зависимости в пайп, когда они нужны:

["CLLocationDistance": 250,  "CLLocationAccuracy": kCLLocationAccuracyHundredMeters] | { (l: CLLocation) in                                                              }

Второй пункт:
✅ Создать запрос

Остается только ждать вызова блока.
✅ Profit

Покрытие тестами в процессе:
https://github.com/El-Machine/Pipe/tree/main/Tests/PipeTests

Примеры

Представьте трубу, из которой можно получить что угодно.
Достаточно описать объект и ждать получения.
Не имеет значения что на другом конце трубы.

Любой объект можно положить в трубу и получить без изменений в любой другом конце этой трубы.

//Async //Anything from Nothing |{ (anything: Anything) in ?? }  //Determinate user's location |{ (location: CLLocation) in   }  //Receive pedometer data |{ (data: CMPedometerData) in   }  //Find bluetooth peripheral near |{ (peripheral: CBPeripheral) in   }  //Subscribe to notification UIWindow.keyboardWillShowNotification | { (n: Notification) in              }  //Look for a contact CNContact.predicateForContacts(matchingName: "John Appleseed") | .every { (contact: CNContact) in                          }  //Read NFC tag |{ (tag: NFCNDEFTag) in  }  //Recognize bodies in imageURL URL(string: "http://example.com/image.jpg") | { (faces: [VNFaceObservation]) in  }  //Recognize bodies in data data | .while { (bodies: [VNHumanBodyPoseObservation]) in     bodies < 2 }
//Recognize 4 hands on input let pipe = |{ (hands: [VNHumanHandPoseObservation]) in  }  let request: VNDetectHumanHandPoseRequest = pipe.get() request.maximumHandCount = 4  let preview: AVCaptureVideoPreviewLayer? = pipe.get() view.layer.addSublayer(preview!)
//Sync //Anything from Something let anything: Anything = something|  let i: Int = float| let string: String = data|  let front: UIColor = 0x554292| let back: UIColor? = "#554292"|  let message: NFCNDEFMessage? = URL(string: "https://exmaple.com/handle")|

Под капотом

Логика начинается в классе Pipe— это контекст для запросов:

//Положить объект func put<T>(_ object: T) -> T  //Достать объект func get<T>() -> T?

Объекты хранятся по ключу своего типа, опционально использовать любой строковой ключ.

Для ожидания объекта создается экземпляр Expect<T> c условием (every по умолчанию):

//Ожидание Expect.every { T in  }  Expect.while { T in    true }  let expect = Expect.one { T in   }  //Создаем запрос |expect
//Сахар |.every { T in  }  |.one { T in   }  |.while { T in true }

Asking

Объекты реализуют Asking для настройки окружения и запроса экземпляров:

//Например, запрос прав на получение геопозиции у пользователя. //Запрос формируется на основании объекта with extension CLAuthorizationStatus: Asking {      static func ask<E>(with: Any?, in pipe: Pipe, expect: Expect<E>) {         let source = with as? CLLocationManager ?? pipe.get()          switch with as? CLAuthorizationStatus {             case .authorizedAlways:                 source.requestAlwaysAuthorization()              case .authorizedWhenInUse, .none:                 source.requestWhenInUseAuthorization()              default:                 break         }     }    }

Вызовы могут быть связаны в цепочку для использования одного контекста:

| .every { T in  } | .one { U in   } | .while { E in true } 

Плюс уведомления о прогрессе:

| .every { T in  } | .one { U in   } | .any { some in  } | .all { last in    }

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

Исходный код на Github

Приглашаю всех к обсуждению идеи, интерфейса, реализации вне зависимости от платформы.


ссылка на оригинал статьи https://habr.com/ru/articles/674010/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *