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

Delegateの共通処理をMessage Forwardingを利用して任意のNamespaceで隠蔽する

Delegateの共通処理をMessage Forwardingを利用して任意のNamespaceで隠蔽する

Ae276805027a01983503c3edafbdb6b2?s=128

Taiki Suzuki

March 06, 2017
Tweet

More Decks by Taiki Suzuki

Other Decks in Programming

Transcript

  1. Delegateͷڞ௨ॲཧΛ Message ForwardingΛར༻ͯ͠ ೚ҙͷNamespaceͰӅṭ͢Δ AKIBA.swift×SwiftѪ޷ձ vol2 2017/03/06 marty-suzuki

  2. None
  3. NoticeObserveKit https://github.com/marty-suzuki/NoticeObserveKit class ViewController: UIViewController { let pool = NoticeObserverPool()

    override func viewDidLoad() { super.viewDidLoad() //NoticeType͕࠾༻͞ΕͨstructͷInfoTypeΛ΋ͱʹɺ௨஌͞ΕΔΦϒδΣΫτͷܕਪ࿦Λߦ͍ͬͯΔ UIKeyboardWillShow.observe { [unowned self] in self.keyboardAnimation(with: $0) }.addObserverTo(pool) } func keyboardAnimation(with keyboard: UIKeyboardInfo) { //ΩʔϘʔυͷΞχϝʔγϣϯॲཧ } } struct UIKeyboardWillShow: NoticeType { typealias InfoType = UIKeyboardInfo static let name: Notification.Name = .UIKeyboardWillShow }
  4. ͦΕͰ͸ຊ୊ʹ

  5. ͜ͷΑ͏ͳ࣮૷Λߦ͏ͱ͖ Ͳ͏͠·͔͢ʁ

  6. Paging

  7. 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() { //UITableViewͷఈʹ౸ୡͨ͠ࡍͷॲཧ } func scrollViewDidScroll(_ scrollView: UIScrollView) { let maxDistance = max(0, scrollView.contentSize.height - scrollView.bounds.size.height) reachedBottom = maxDistance < scrollView.contentOffset.y } }
  8. Paging Solution

  9. ͜ͷΑ͏ͳܗͰॻ͚ͨΒϕλʔͰ͸ͳ͍Ͱ͠ΐ͏͔ʁ class ViewController: UIViewController { @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() { super.viewDidLoad() //.reΛ௥Ճ͢Δ͚ͩ tableView.re.delegate = self tableView.re.scrollViewDidReachBottom = { scrollView in print("scrollViewDidReachBottom!") } } } extension ViewController: UITableViewDelegate { //ViewController಺ͰDelegateͷϋϯυϦϯά͕Մೳ func scrollViewDidScroll(_ scrollView: UIScrollView) { print("scrollViewDidScroll = \(scrollView.contentOffset.y)") } }
  10. Implementation

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

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

    NS_REFINED_FOR_SWIFT; @end SwiftͰUITableViewDelegate༻ͷDelegateTransporterΛॻ͘ͷ ͰɺNS_REFINED_FOR_SWIFTʹͯ͠.init(__deleate:)Ͱݺͼ ग़͢Α͏ʹ͢Δɻ
  13. DelegateTransporter.m @interface DelegateTransporter () @property (nonnull, nonatomic, strong) NSHashTable<NSObject *>

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

    *delegates; @end ഑ྻͰड͚औͬͨdelegateΛऑࢀরͰอ͍࣋ͨ͠ͷͰɺ NSHashTableΛར༻͢Δɻself.delegates͸[NSHashTable weakObjectsHashTable]ͰॳظԽ͢Δɻ
  15. 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
  16. Message Forwarding https://developer.apple.com/library/content/documentation/ Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ ocrtForwarding.html Apple Sample Code

  17. DelegateTransporter.m - (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]; } } } - (BOOL)respondsToSelector:(SEL)aSelector { for (NSObject *delegate in self.delegates) { if ([delegate isKindOfClass:[NSNull class]]) { continue; } if ([delegate respondsToSelector:aSelector]) { return YES; } } return NO; }
  18. UITableViewDelegateTransporter class UITableViewDelegateTransporter: DelegateTransporter, UITableViewDelegate { @nonobjc convenience init(delegates: [UITableViewDelegate])

    { self.init(__delegates: delegates) } }
  19. 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() } } }
  20. 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 } }
  21. 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 } }
  22. reΛফ͚ͩ͢Ͱ௨ৗͷڍಈʹ tableView.re.delegate = self ↓ tableView.delegate = self

  23. Advanced Usage

  24. None
  25. None
  26. None
  27. None
  28. None
  29. ໰୊఺ • UIViewControllerͳͲͰɺtableView.transform = .init(rotationAngle: .pi)Λهड़ ͠ͳ͚Ε͹ͳΒͳ͍ • UIViewControllerͳͲͰɺUITableViewͷcontentInset΍scrollIndicatorInsetsͳ ͲΛ൓సͤ͞Δهड़Λ͠ͳ͚Ε͹ͳΒͳ͍

    • UITableViewCellͷawakeFromNibʹtransform = .init(rotationAngle: .pi)ΛCell ͝ͱʹهड़͠ͳ͚Ε͹ͳΒͳ͍ ൓స͕ෆཁʹͳͬͨ৔߹ʹɺ্هͷ࣮૷Λ͢΂ͯ࡟আ͠ͳ͚Ε ͹ͳΒͳ͘ͳΔ
  30. 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 //... } }
  31. 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 } }
  32. 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 }
  33. 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) } } }
  34. tableView(_:willDisplay:forRowAt:) ʹUITableViewCellͷ൓సॲཧΛهड़͢Δ͜ͱͰ ֤UITableViewCellͷawakeFromNibʹ هड़͢Δ͜ͱ͕ෆཁʹ

  35. ΋͏Ұ౓ݴ͍·͕͢ɺreΛফ͚ͩ͢Ͱ௨ৗͷڍಈʹ tableView.re.delegate = self ↓ tableView.delegate = self

  36. ϞόΠϧΞϓϦ։ൃΤΩεύʔτཆ੒ಡຊʢ4/11ൃചʣ https://www.amazon.co.jp/dp/4774188638

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