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

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

Мой опыт разработки под 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 { 

}

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

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

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

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

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

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

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

Pipe

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

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

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

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

|{ (location: CLLocation) in

}

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

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

Второй пункт:

✅ Создать запрос

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

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

✅ Profit

Примеры

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

Достаточно описать объект и ждать получения.

Не имеет значения что на другом конце трубы.

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

//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<E>(_ object: E) -> E

//Достать объект
func get<E>() -> E?

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

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

| .every { T in

}

| .one { T in 

}

| .while { T in
	true
}

Объекты реализуют 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

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

Комментарии (2)


  1. YChebotaev
    02.07.2022 17:23
    +1

    Идея классная! Осталось совсем немного до акторов )


  1. george3
    02.07.2022 18:51

    https://github.com/Claus1/unigui оперирует напрямую данными-типами для визуализации.

    Provide a programming technology that does not require front-end programming, for a server written in any language, for displaying on any device, in any resolution, without any tuning.