Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RxSwiftを導入したくても、できなかったら?

 RxSwiftを導入したくても、できなかったら?

Ae276805027a01983503c3edafbdb6b2?s=128

Taiki Suzuki

March 01, 2017
Tweet

More Decks by Taiki Suzuki

Other Decks in Programming

Transcript

  1. RxSwiftΛಋೖͨͯ͘͠΋ Ͱ͖ͳ͔ͬͨΒʁ CA.swift 2017/03/01 marty-suzuki

  2. None
  3. None
  4. ͳͥRxSwiftΛ ಋೖͰ͖ͳ͔ͬͨͷ͔ʁ Why can not we use RxSwift?

  5. Problem 1 App Size 57.2MB (iPhone7)

  6. ࠓޙ΍Δ͜ͱ͕ܾ·͍ͬͯΔ͜ͱ • େܕͷ௥Ճػೳ • Firebaseͷಋೖ

  7. ࠓޙ΍Δ͜ͱ͕ܾ·͍ͬͯΔ͜ͱ • େܕͷ௥Ճػೳ • Firebaseͷಋೖ Wi-Fi؀ڥͰͳͯ͘΋ΞϓϦ͕μ΢ϯϩʔυͰ͖ Δ༰ྔʢ100MBʣΛӽ͑ͯ͠·Θͳ͍͔͕৺഑

  8. Problem 2 Application Sources

  9. Objective-C++ 45.0% Swift 27.6% Objective-C 27.4%

  10. 1155 Compile Sources

  11. Architecture MVC + KVO

  12. • Swift͕3ׂऑͰͳͷͰɺRxSwiftΛద༻Ͱ͖ΔՕॴ͕গͳ͍ • RxSwiftΛ࢖ͬͨMVVMͷઃܭΛہॴతʹ௥Ճͯ͠͠·͏ͱɺ Objective-C / C++ɺSwift͕ࠞࡏ͍ͯ͠Δ࣌఺Ͱෳࡶͳͷʹ ΋ؔΘΒͣ͞Βʹෳࡶ͕͞૿ͯ͠͠·͍ɺҾ͖ܧ͗ʹ͍͘ι ʔεʹͳͬͯ͠·͏

  13. Other Solutions

  14. Text Input

  15. UISearchBar + Debounce searchBar.rx.text.orEmpty.asDriver() .skip(1) .debounce(0.3) .distinctUntilChanged() .drive(onNext: { text

    in print(text) }) .addDisposableTo(disposeBag)
  16. UISearchBar + Timer class ViewController: UIViewController, UISearchBarDelegate { @IBOutlet weak

    var searchBar: UISearchBar! private lazy var timer: Timer = { return self.createTimer() }() override func viewDidLoad() { super.viewDidLoad() searchBar.delegate = self } func createTimer() -> Timer { return Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(ViewController.handleTimer(_:)), userInfo: nil, repeats: false) } func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { timer.invalidate() timer = createTimer() } @objc private func handleTimer(_ timer: Timer) { print(self.searchBar.text) } }
  17. Text Input Solution

  18. UISearchBar + DispatchQueue class ViewController: UIViewController, UISearchBarDelegate { @IBOutlet weak

    var searchBar: UISearchBar! //0.5ඵ͝ͱʹॲཧΛglobalQueueͰߦ͏ClosureΛPropertyͱͯ͠อ࣋ let debounce = DispatchQueue.global().debounce(delay: .milliseconds(500)) override func viewDidLoad() { super.viewDidLoad() searchBar.delegate = self } func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { debounce { print(searchBar.text) } } }
  19. Implementation extension DispatchQueue { func debounce(delay: DispatchTimeInterval) -> (_ action:

    @escaping () -> ()) -> () { var lastFireTime: DispatchTime = .now() return { action in let deadline: DispatchTime = .now() + delay lastFireTime = .now() self.asyncAfter(deadline: deadline) { let now: DispatchTime = .now() let when: DispatchTime = lastFireTime + delay if now < when { return } lastFireTime = .now() action() } } } }
  20. Notification Center

  21. NotificationCenter + RxSwift class ViewController: UIViewController { //some properties private

    var disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.rx.notification(.UIKeyboardWillShow).asDriver(onErrorDriveWith: .empty()) .flatMap { notification -> Driver<UIKeyboardNotificationInfo> in guard let keyboardInfo = UIKeyboardNotificationInfo(from: notification) else { return Driver.empty() } return Driver.just(keyboardInfo) } .drive(onNext: { [unowned self] info in self.keyboardWillAnimate(toDefault: false, with: info) }) .addDisposableTo(disposeBag) } func keyboardWillAnimate(toDefault: Bool, with info: UIKeyboardNotificationInfo) { //Keyboard animation } }
  22. Notification Center Solution

  23. NoticeObserveKit https://github.com/marty-suzuki/NoticeObserveKit class ViewController: UIViewController { //some properties private var

    pool = NoticeObserverPool() override func viewDidLoad() { super.viewDidLoad() UIKeyboardWillShow.observe { [unowned self] in self.keyboardWillAnimate(toDefault: false, with: $0) }.addObserverTo(pool) } }
  24. NoticeObserveKit.NoticeType struct UIKeyboardWillShow: NoticeType { typealias InfoType = UIKeyboardInfo static

    let name: Notification.Name = .UIKeyboardWillShow }
  25. NoticeObserveKit.NoticeType struct TalkObjectDidUpdate: NoticeType { typealias InfoType = TalkObject static

    let name: Notification.Name = Notification.Name("TalkObjectDidUpdate") } let talk: TalkObject = //... TalkObjectDidUpdate.post(info: talk)
  26. Paging

  27. RxPagination https://github.com/tryswift/RxPagination extension Reactive where Base: UIScrollView { var reachedBottom:

    ControlEvent<Void> { let observable = contentOffset .flatMap { [weak base] contentOffset -> Observable<Void> in guard let scrollView = base else { return Observable.empty() } let visibleHeight = scrollView.frame.height - scrollView.contentInset.top - scrollView.contentInset.bottom let y = contentOffset.y + scrollView.contentInset.top let threshold = max(0.0, scrollView.contentSize.height - visibleHeight) return y > threshold ? Observable.just() : Observable.empty() } return ControlEvent(events: observable) } }
  28. Paging + UITableViewDelegate class ViewController: UIViewController, UITableViewDelegate { @IBOutlet weak

    var tableView: UITableView! private var reachedBottom: Bool = false { didSet { if reachedBottom == oldValue { return } guard reachedBottom else { return } didReachBottom() } } override func viewDidLoad() { super.viewDidLoad() tableView.delegate = self } private func didReachBottom() { print("scrollViewDidReachBottom!") } func scrollViewDidScroll(_ scrollView: UIScrollView) { let maxDistance = max(0, scrollView.contentSize.height - scrollView.bounds.size.height) reachedBottom = maxDistance < scrollView.contentOffset.y } }
  29. Paging Solution

  30. Paging + ReachExtension class ViewController: UIViewController { @IBOutlet weak var

    tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() tableView.re.delegate = self tableView.re.scrollViewDidReachBottom = { scrollView in print("scrollViewDidReachBottom!") } } } extension ViewController: UITableViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { print("scrollViewDidScroll = \(scrollView.contentOffset.y)") } }
  31. Implementation

  32. DelegateTransporter.h @interface DelegateTransporter : NSObject - (nonnull instancetype)initWithDelegates:(NSArray<id> * __nonnull)delegates

    NS_REFINED_FOR_SWIFT; @end
  33. DelegateTransporter.m @interface DelegateTransporter () @property (nonnull, nonatomic, strong) NSHashTable<NSObject *>

    *delegates; @end @implementation DelegateTransporter - (instancetype)initWithDelegates:(NSArray<id> *)delegates { self = [super init]; if (self != nil) { self.delegates = [NSHashTable weakObjectsHashTable]; for (id delegate in delegates) { if (![delegates isKindOfClass:[NSObject class]]) { continue; } [self.delegates addObject:delegate]; } } return self; } //...etc @end
  34. DelegateTransporter.m - (BOOL)respondsToSelector:(SEL)aSelector { for (NSObject *delegate in self.delegates) {

    if ([delegate isKindOfClass:[NSNull class]]) { continue; } if ([delegate respondsToSelector:aSelector]) { return YES; } } return NO; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { for (NSObject *delegate in self.delegates) { if ([delegate isKindOfClass:[NSNull class]]) { continue; } if ([delegate respondsToSelector:aSelector]) { return [delegate methodSignatureForSelector:aSelector]; } } return nil; } - (void)forwardInvocation:(NSInvocation *)anInvocation { for (NSObject *delegate in self.delegates) { if ([delegate isKindOfClass:[NSNull class]]) { continue; } if ([delegate respondsToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:delegate]; } } }
  35. UITableViewDelegateTransporter class UITableViewDelegateTransporter: DelegateTransporter, UITableViewDelegate { @nonobjc convenience init(delegates: [UITableViewDelegate])

    { self.init(__delegates: delegates) } }
  36. UITableView.ReachExtension extension UITableView { final class ReachExtension: NSObject { private

    var delegateTransporter: UITableViewDelegateTransporter? { didSet { base?.delegate = delegateTransporter } } weak var delegate: UITableViewDelegate? { didSet { guard let delegate = delegate else { delegateTransporter = nil return } delegateTransporter = UITableViewDelegateTransporter(delegates: [delegate, self]) } } fileprivate var reachedBottom: Bool = false { didSet { if reachedBottom == oldValue { return } guard let base = base, reachedBottom else { return } scrollViewDidReachBottom?(base) } } var scrollViewDidReachBottom: ((UIScrollView) -> ())? private weak var base: UITableView? init(_ base: UITableView) { self.base = base super.init() } } }
  37. UITableView.ReachExtension extension UITableView.ReachExtension: UITableViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) {

    let maxDistance = max(0, scrollView.contentSize.height - scrollView.bounds.size.height) reachedBottom = maxDistance < scrollView.contentOffset.y } }
  38. UITableView extension UITableView { private struct AssociatedKey { static var

    re: UInt8 = 0 } var re: ReachExtension { guard let re = objc_getAssociatedObject(self, &AssociatedKey.re) as? ReachExtension else { let re = ReachExtension(self) objc_setAssociatedObject(self, &AssociatedKey.re, re, .OBJC_ASSOCIATION_RETAIN) return re } return re } }
  39. Advanced Usage

  40. None
  41. None
  42. None
  43. None
  44. None
  45. UITableView.ReverseExtension https://github.com/marty-suzuki/ReverseExtension extension UITableView { final class ReverseExtension: NSObject {

    //some implementation //... private var lastScrollIndicatorInsets: UIEdgeInsets? private var lastContentInset: UIEdgeInsets? private var mutex = pthread_mutex_t() init(_ base: UITableView) { self.base = base super.init() configureTableView(base) } //some implementation //... } }
  46. UITableView.ReverseExtension private func configureTableView(_ tableView: UITableView) { guard let base

    = self.base else { return } if base.transform == CGAffineTransform.identity { UIView.setAnimationsEnabled(false) base.transform = CGAffineTransform.identity.rotated(by: .pi) UIView.setAnimationsEnabled(true) } tableView.addObserver(self, forKeyPath: #keyPath(UITableView.contentInset), options: [.new, .old], context: nil) } public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { switch keyPath { case (#keyPath(UITableView.contentInset))?: DispatchQueue.global().async { [weak self] in DispatchQueue.main.async { self?.configureTableViewInsets() } } default: break } }
  47. UITableView.ReverseExtension private func configureTableViewInsets() { defer { pthread_mutex_unlock(&mutex) } pthread_mutex_lock(&mutex)

    guard let base = base else { return } if let _ = self.lastContentInset, let _ = self.lastScrollIndicatorInsets { return } let contentInset = base.contentInset base.contentInset.bottom = contentInset.top base.contentInset.top = contentInset.bottom self.lastContentInset = base.contentInset let scrollIndicatorInsets = base.scrollIndicatorInsets base.scrollIndicatorInsets.bottom = scrollIndicatorInsets.top base.scrollIndicatorInsets.top = scrollIndicatorInsets.bottom base.scrollIndicatorInsets.right = base.bounds.size.width - 8 self.lastScrollIndicatorInsets = base.scrollIndicatorInsets }
  48. UITableView.ReverseExtension extension UITableView.ReverseExtension: UITableViewDelegate { func tableView(_ tableView: UITableView, willDisplay

    cell: UITableViewCell, forRowAt indexPath: IndexPath) { if cell.transform == CGAffineTransform.identity { UIView.setAnimationsEnabled(false) cell.transform = CGAffineTransform.identity.rotated(by: .pi) UIView.setAnimationsEnabled(true) } } }
  49. ϞόΠϧΞϓϦ։ൃΤΩεύʔτཆ੒ಡຊʢ4/11ൃചʣ https://www.amazon.co.jp/dp/4774188638

  50. ͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ marty-suzuki