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

Delegateの共通処理をMessage Forwardingを利用して任意のNamespa...

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

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

Avatar for Taiki Suzuki

Taiki Suzuki

March 06, 2017
Tweet

More Decks by Taiki Suzuki

Other Decks in Programming

Transcript

  1. 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 }
  2. 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 } }
  3. ͜ͷΑ͏ͳܗͰॻ͚ͨΒϕλʔͰ͸ͳ͍Ͱ͠ΐ͏͔ʁ 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)") } }
  4. 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:)Ͱݺͼ ग़͢Α͏ʹ͢Δɻ
  5. DelegateTransporter.m @interface DelegateTransporter () @property (nonnull, nonatomic, strong) NSHashTable<NSObject *>

    *delegates; @end ഑ྻͰड͚औͬͨdelegateΛऑࢀরͰอ͍࣋ͨ͠ͷͰɺ NSHashTableΛར༻͢Δɻself.delegates͸[NSHashTable weakObjectsHashTable]ͰॳظԽ͢Δɻ
  6. 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
  7. 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; }
  8. 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() } } }
  9. 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 } }
  10. 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 } }
  11. ໰୊఺ • UIViewControllerͳͲͰɺtableView.transform = .init(rotationAngle: .pi)Λهड़ ͠ͳ͚Ε͹ͳΒͳ͍ • UIViewControllerͳͲͰɺUITableViewͷcontentInset΍scrollIndicatorInsetsͳ ͲΛ൓సͤ͞Δهड़Λ͠ͳ͚Ε͹ͳΒͳ͍

    • UITableViewCellͷawakeFromNibʹtransform = .init(rotationAngle: .pi)ΛCell ͝ͱʹهड़͠ͳ͚Ε͹ͳΒͳ͍ ൓స͕ෆཁʹͳͬͨ৔߹ʹɺ্هͷ࣮૷Λ͢΂ͯ࡟আ͠ͳ͚Ε ͹ͳΒͳ͘ͳΔ
  12. 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 //... } }
  13. 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 } }
  14. 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 }
  15. 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) } } }