Как подружиться с UIKit

Автор: admin от 2-11-2017, 15:00, посмотрело: 363

Как подружиться с UIKit

Привет, Хабр! Меня зовут Богдан, в Badoo я работаю в мобильной команде iOS-разработчиком. Мы достаточно редко рассказываем что-либо о нашей мобильной разработке, хотя статьи – один из лучших способов документировать хорошие практики. Эта статья статья расскажет о нескольких полезных подходах которые мы используем в нашей работе.



Уже на протяжении нескольких лет iOS-сообщество сражается с UIKit. Кто-то придумывает сложные способы «погребения» внутренностей UIKit под слоями абстракций в своих выдуманных архитектурах, другие команды переписывают его, теша своё эго, но оставляя за собой дикое количество кода, который нужно поддерживать.

распределения работы между маленькими контроллерами представлений.



Можно пойти ещё дальше и применить универсальный способ подключения к жизненному циклу контроллера представлений – ViewControllerLifecycleBehaviour.



public protocol ViewControllerLifecycleBehaviour {
    func afterLoading(_ viewController: UIViewController)
    func beforeAppearing(_ viewController: UIViewController)
    func afterAppearing(_ viewController: UIViewController)
    func beforeDisappearing(_ viewController: UIViewController)
    func afterDisappearing(_ viewController: UIViewController)
    func beforeLayingOutSubviews(_ viewController: UIViewController)
    func afterLayingOutSubviews(_ viewController: UIViewController)
}


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



open override func viewDidLoad() {
    let screenshotDetector =  ScreenshotDetector(notificationCenter:
                                  NotificationCenter.default) {
    // Screenshot was detected
}
self.add(behaviours: [screenshotDetector])}


В реализации поведения тоже ничего сложного:



public final class ScreenshotDetector: NSObject,  
    ViewControllerLifecycleBehaviour {
    public init(notificationCenter: NotificationCenter,    
               didDetectScreenshot: @escaping ()  Void) {     
       self.didDetectScreenshot = didDetectScreenshot                
       self.notificationCenter = notificationCenter
    }
    deinit {
       self.notificationCenter.removeObserver(self)
    }
    public func afterAppearing(_ viewController: UIViewController) {
       self.notificationCenter.addObserver(self, selector:
           #selector(userDidTakeScreenshot), 
           name: .UIApplicationUserDidTakeScreenshot, object: nil)
    }
    public func afterDisappearing(_ viewController:
        UIViewController) {    
       self.notificationCenter.removeObserver(self)
    }
    @objc private func userDidTakeScreenshot() {
        self.didDetectScreenshot()
    }
    private let didDetectScreenshot: ()  Void
    private let notificationCenter: NotificationCenter
}


Поведение также можно тестировать изолированно, поскольку оно закрыто нашим протоколом ViewControllerLifecycleBehaviour.



Подробности реализации: здесь.



Поведение можно использовать в задачах, зависящих от VLC, например, в аналитике.



Использование цепочки ответчиков



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



Как подружиться с UIKit

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



Например:



public extension UIView {
    public func viewControllerForPresentation() 
         UIViewController? {
        var next = self.next
        while let nextResponder = next {
            if let viewController = next as? UIViewController,
                   viewController.presentedViewController == nil,
                   !viewController.isDetached {
                return viewController
            }
        next = nextResponder.next
        }
        return nil
    }
}
public extension UIViewController {
    public var isDetached: Bool {
        if self.viewIfLoaded?.window?.rootViewController == self
            return false
        }
        return self.parent == nil && 
            self.presentingViewController == nil
    }
}


Использование иерархии представлений



Шаблон Entity–component–system (сущность–компонент–система) – это прекрасный способ внедрения аналитики в приложение. Мой коллега реализовал такую систему и это оказалось очень удобно.



Здесь «сущность» – это UIView, «компонент» – часть данных отслеживания, «система» – сервис отслеживания аналитики.



Идея в том, чтобы дополнить UI-представления соответствующими данными отслеживания. Затем сервис отслеживания аналитики сканирует N раз/ секунд видимую часть иерархии представлений и записывает данные отслеживания, которые ещё не были записаны.



Как подружиться с UIKit

При использовании такой системы от разработчика требуется только добавить данные отслеживания вроде имён экранов и элементов:



class EditProfileViewController: UIViewController {
   override func viewDidLoad() {
       ...
       self.trackingScreen =   
           TrackingScreen(screenName:.screenNameMyProfile)
   }
}
class SparkUIButton: UIButton {
    public override func awakeFromNib() {
        ...
        self.trackingElement = 
            TrackingElement(elementType: .elementSparkButton)
    }
}


Обход иерархии представлений – это BFS, при котором игнорируются представления, которые не видны:



let visibleElements = Class.visibleElements(inView: window)
for view in visibleElements {
    guard let trackingElement = view.trackingElement else { 
        continue 
    }
    self.trackViewElement(view)
}


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




  • Не слишком часто сканировать иерархию представлений.

  • Не сканировать иерархию представлений при прокрутке (используйте более подходящий режим цикла исполнения (run loop mode)).

  • Сканируйте иерархию только тогда, когда уведомление публикуется в NSNotificationQueue с помощью NSPostWhenIdle.



  • P. S.



    Надеюсь, мне удалось показать, как можно «ужиться» с UIKit, и вы нашли что-то полезное для своей повседневной работы. Или по крайней мере получили пищу для размышлений.



    Источник: Хабрахабр

    Категория: Веб-разработка / iOS

    Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
    Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

    Добавление комментария

    Имя:*
    E-Mail:
    Комментарий:
    • bowtiesmilelaughingblushsmileyrelaxedsmirk
      heart_eyeskissing_heartkissing_closed_eyesflushedrelievedsatisfiedgrin
      winkstuck_out_tongue_winking_eyestuck_out_tongue_closed_eyesgrinningkissingstuck_out_tonguesleeping
      worriedfrowninganguishedopen_mouthgrimacingconfusedhushed
      expressionlessunamusedsweat_smilesweatdisappointed_relievedwearypensive
      disappointedconfoundedfearfulcold_sweatperseverecrysob
      joyastonishedscreamtired_faceangryragetriumph
      sleepyyummasksunglassesdizzy_faceimpsmiling_imp
      neutral_faceno_mouthinnocent