Pull to refresh

Comments 17

Я конечно не сварщик не swift программист, но хотел поинтересоваться, тестируют ли программы на Swift?
И как корректно тестировать статик синглтоны? Может на swift есть какая-то магическая изоляция глобального публичного состоятия?
я пока в тестах неочень, возможно смогу чуть позже более развернуто ответить) спустя 3-6 месяцев) но пока такой задачи нет. Но вообще первое что гуглится stackoverflow.com/questions/8256989/singleton-and-unit-testing и medium.com/@martinrybak/how-to-mock-singletons-and-static-methods-in-unit-tests-cbe915933c7d буду смотреть в ту сторону если понадобится
И как корректно тестировать статик синглтоны?
Не очень сварщик в плане того, что нечасто удается писать тесты. Но тестирование синглтона — мне не видится проблемой. Синглтон: обычный класс со статическим полем, в котором лежит предсозданный экземпляр. Можно тестировать этот уже готовый экземпляр, а можно — создать ещё экземпляры, и гонять тесты на них.
Другой вопрос — это тестирование кода, в котором происходит работа с синглтоном. Но опять же, если синглтон инъектить как зависимость закрытую протоколом, то класс, в который синглон инъектирован, тестируется так же как обычно.
То есть с точки зрения тестируемости, синглтон может быть вполне безвреден.
Что касается
изоляция глобального публичного состояния
— тут, да, код выглядит довольно опасным. Все места в программе работающие с синглтоном будут работать с общим проперти dataArray, и если пользователь зачем-то начнёт работать с этим синглтоном из разных потоков, то начнется гонка за dataArray. Результатом станет емнип неопределенное поведение.

По моему мнению работа с веб-сокетами это своего рода компромисс который был создан с прицелом на веб-браузеры. Поэтому для нативных iOS/Android приложений перспективнее использовать транспорт tcp/tsl который поддерживают многие протоколы для работы с сообщениями например amqp, wamp или mqtt.


Единственное что можно поставить плюсом веб-сокетам для нативных приложений — это использование портов и протоколов https, которые обычно доступны и обычно не блокируются разными бранмауэрами или провайдерами сетевых услуг.

Если уж на то пошло — можно и grpc/graphql предложить.

Это все же вопрос-ответные протоколы. А веб-сокеты чаще используются для проталкивания сообщений с сервера клиенту, как собственное и упомянутые amqp, wamp или mqtt

Стриминг в grpc отлично работает.

Можете подсказать зачем импортируется cfnetwork? Сколько не смотрел, не нашел его использование.

Ну и уточняющий момент: webSocketTask доступен начиная с айос13 в urlsession. Вы не поддерживаете предыдущие версии ос?
cfnetwork случайно, удалил, спасибо.
по поводу второго, это просто пример как можно использовать нативно, пока что используем cocoapods решения.
1. Для («wss://ТУТ_ВАШ_АДРЕС») сделать static переменную
2. «shared» менеджера объявить как private а методы менеджера — static; раз уж мы работаем с синглтоном. Меньше потом писать — чище код.
неочень понял зачем так делать, вы попробуйте, будет ли у вас работать?
1) Синглтон и свойство dataArray. Массив никак не освобождается, при каждом запросе данные добавляются и передаются из массива в комплишен. Рано или поздно, приложение ляжет намертво или будет ужасно тормозить. Это первый момент.

2) Второй момент. У вас подписка на тиковую историю, причем подписка не унифицирована и как явное значение передается в каждом методе. К примеру, если получать информацию по сотне инструментов, придется создать сотню методов на подписку и столько же на отписку. Почему не использовать дженерик и не унифицировать запрос?

3) Вы никак не обрабатываете обрыв соединения. С учетом того, что в случае синглтона, менеджер был инициирован однажды, по сути, он не может быть пересоздан. Я бы переписал пример без «вечного» объекта и эту строку вынес бы в конструктор:

let webSocketTask = URLSession(configuration: .default).webSocketTask(with: URL(string: "wss://ТУТ_ВАШ_АДРЕС"*))

4) Метод getData() содержит вот такой гард:

guard let self = self else { return }

Зачем? Вызов произошел после загрузки вью. По сути, контроллер загружен и работает нормально. Внутри метода идет простая передача массива в свойство вашего контроллера.

self?.dataArray = ...
прекрасно отработает внутри метода.
Осмелюсь предположить, что вызов guard let self = self else { return } в данном случае – это чисто синтаксический сахар, визуальный вид и «потому, что так книга пишет». :)
А это под любыми версиями iOS работает? Мы пользовали cocoapods версию для вебсокетов как раз потому, что Apple только недавно добавила поддержку сокетов в свой SDK.
Нет. Только для ios 13. Для предыдущих версий хорошо работает Starscream.
Самое интересное в Эппл реализации это то что надо в рекурсии вызывать receiveData(), но это надо делать только on .success.

Я оставлю здесь пару ссылок где более развёрнуто описано как работать с новыми веб-сокетами:
1. appspector.com/blog/websockets-in-ios-using-urlsessionwebsockettask
2. kristaps.me/websockets-ios-13-swift

И понятно что это только для iOS 13. У меня вопрос, кто-нибудь пробовал использовать комбинацию Starscream подключаемую как динамическую библиотеку для iOS 12- и встроенный для iOS13+. Есть какая-то выгода? По идеи памяти должно меньше кушать, ведь «все» системные либы динамические и скорее всего уже в памяти (случай для iOS13). Сама реализация думаю всем понятна, что надо написать обёртку для вебсокетов и менеджер который выбирает необходимую реализацию в зависимости от версии iOS.
Весь код сюда пихать не стану, лишь часть примера ради
Буквально на днях пилил себе такой менеджер.
Только для нашего сервера пришлось его сделать не на уровне простой URL, а через URL Request. Так появилась возможность использовать хеддер авторизации и дополнительный query в URL:
final class WebSocket: NSObject {
    private var urlSession: URLSession?
    private var webSocketTask: URLSessionWebSocketTask?

    init?(host: String, path: String, headers: [String: String], parameters: [String: String]) {
        super.init()
        urlSession = URLSession(configuration: .default,
                                delegate: self,
                                delegateQueue: nil)
        
        var urlComponents = URLComponents()
        urlComponents.scheme = "wss"
        urlComponents.host = host
        urlComponents.path = path
        
        var items: [URLQueryItem] = []
        
        for (key, value) in parameters {
            let qItem = URLQueryItem(name: key, value: value)
            items.append(qItem)
        }
        
        if items.count > 0  {
            urlComponents.queryItems = items
        }
        
        guard let url = urlComponents.url else {
            log.error("Could not create URL from components")
            return nil
        }
        
        log.info("Socket URL: \(url.absoluteString)")
        
        var request = URLRequest(url: url)
        request.timeoutInterval = 30
        headers.forEach { (key, value) in
            request.addValue(value, forHTTPHeaderField: key)
        }
        
        webSocketTask = urlSession?.webSocketTask(with: request)
    }
}


Плюс, для удобства сделал все на колбэках вместо делегата, в итоге конечное использование выглядит примерно так:
let websocket = WebSocket(host: server,
                              path: "/dash_wss",
                              headers: headers,
                              parameters: parameters)
        
        websocket?
            .connect()
            .onConnected(callback: {
                log.info("Socket connected")
            })
            .onDisconnected(callback: { (error) in
                log.info("Socket disconnected with error: \(error?.localizedDescription ?? "No error")")
            })
            .onError(callback: { (error) in
                log.error("Socket error \(error)")
            })
            .onTextMessage(callack: { (message) in
                log.info("Socket message \(message)")
            })


Делать синглтон из него не стал, так как используется только для одного экрана
Если кому надо полный код, могу запилить в гитхабе.
Если будут замечания, корректировки, буду рад принять
Only those users with full accounts are able to leave comments. Log in, please.