美文网首页
Runtime为分类添加属性和截取代理

Runtime为分类添加属性和截取代理

作者: 傻傻小萝卜 | 来源:发表于2019-10-11 10:14 被阅读0次

    项目中常常需要扩展系统的类添加属性和方法,这个已经很是常见如:
    (1)添加常见系统类型的属性

    // 创建属性

    @property (nonatomic, assign) BOOL isShowServerItem;

    // 创建关联key

    static const void *isShowServerItemKey = @"isShowServerItemKey";

    // 关联对象set和get

    - (void)setIsShowServerItem:(BOOL)isShowServerItem{       objc_setAssociatedObject(self, isShowServerItemKey, [NSNumber numberWithBool:isShowServerItem], OBJC_ASSOCIATION_ASSIGN);    }

    - (BOOL)isShowServerItem{    return [objc_getAssociatedObject(self, isShowServerItemKey) boolValue];;    }

    在使用的时候切记,关联的是对象,如果不是需要将其变为对象

    (2)添加自定义的View

    // 创建属性(CustomerServerItem自定义的View)

    @property (nonatomic, strong) CustomerServerItem *serverItem;

    // 创建关联的key

    static NSString *serverItemKey = @"serverItemKey"; 

    // 关联对象set和get

    - (void)setServerItem:(CustomerServerItem *)serverItem                                                                                                  {    objc_setAssociatedObject(self, &serverItemKey, serverItem, OBJC_ASSOCIATION_RETAIN);}

    - (CustomerServerItem *)serverItem

    {

         CustomerServerItem *serverItem =  (CustomerServerItem *)objc_getAssociatedObject(self, &serverItemKey);

          if (serverItem == nil) {                                                                                                                                                    serverItem = [[CustomerServerItem alloc] init];

                 objc_setAssociatedObject(self, &serverItemKey, serverItem, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

            }

           return serverItem;

    }

    在我的项目中,需要在所有的大部分的tableView的上边添加一个滑动指示的View,当这个滑动到特定地方,比如说是一个屏幕的长度就显示出快速滑动到头部的地方,所以需要截取滑动方法,并且在滑动的时候将特定的view添加到tableView所属的控制器上,当滑动到该显示那个view的时候,将其显示出来

    (1)截取ScrollView的delegate

    + (void)load {

            static dispatch_once_t onceToken;

             dispatch_once(&onceToken, ^{

                 Method originalMethod = class_getInstanceMethod([UIScrollView class], @selector(setDelegate:));

                 Method replaceMethod = class_getInstanceMethod([UIScrollView class], @selector(hook_setDelegate:));

                 method_exchangeImplementations(originalMethod, replaceMethod);

    }

    (2)改变代理方法

    - (void)hook_setDelegate:(id<UIScrollViewDelegate>)delegate {

            [self hook_setDelegate:delegate];

             if ([self isMemberOfClass:[UITableView class]]) {

                //Hook (scrollViewDidScroll:) 方法

                 Hook_Method([delegate class], @selector(scrollViewDidScroll:), [self class], @selector(p_scrollViewDidScroll:), @selector(p_scrollViewDidScroll:));

                // 在这个位置可以将这个View添加上去,你也可以在设置isShowServerItem的时候添加

                 if ([delegate isMemberOfClass:[UIViewController class]]) {

                                UIViewController *delegateVC = (UIViewController *)delegate;

                                 [delegateVC.view addSubview:self.serverItem];

                }

            } else {

                 NSLog(@"不是UIScrollView,不需要hook方法");

            }

    }

    Hook_method这个方法一会在讲解,这个方法中,截取到了scrollView的代理,首先调用了原方法,执行原来代理方法中的内容,然后在这个的基础上,截取和替换了我们要添加的方法

    (3)替换方法的实现

    -(void)p_scrollViewDidScroll:(UIScrollView *)scrollView

    {

    // 调用原代理实现的方法,切记在这个方法中,如果要调用你自己为scrollView添加的View属性,一定要用scorllView点出什么来,不要用self,self为代理(tableView添加到的控制器或者是View)

        [self p_scrollViewDidScroll:scrollView];

         CGFloat offSetY = scrollView.contentOffset.y;                                                                                                               CGFloat alpha =  offSetY/(px_scale(400)- NavBarHeight);                                                                                                alpha = MAX(alpha, 0);                                                                                                                                                        alpha = MIN(alpha, 1);

        if (scrollView.isShowServerItem) {

             if (alpha >= 0.5) {

                 scrollView.serverItem.isShowScrollTop = YES;

             }else{

                scrollView.serverItem.isShowScrollTop = NO;

              }

        }

    }

    这样那,如果想在tableView 上显示那个我们定义的View,只需要这个分类导入,并且将isShowServerItem设为Yes既可

    这个里边最关键的就是Hook_Method,代码如下,这个可以直接复制使用

    static void Hook_Method(Class originalClass, SEL originalSel, Class replacedClass, SEL replacedSel, SEL noneSel){

             // 原实例方法

             Method originalMethod = class_getInstanceMethod(originalClass, originalSel);

             // 替换的实例方法

            Method replacedMethod = class_getInstanceMethod(replacedClass, replacedSel);

            // 如果没有实现 delegate 方法,则手动动态添加

             if (!originalMethod) {

                    Method noneMethod = class_getInstanceMethod(replacedClass, noneSel);

                    BOOL addNoneMethod = class_addMethod(originalClass, originalSel,                             method_getImplementation(noneMethod), method_getTypeEncoding(noneMethod));

                     if (addNoneMethod) {

                                  NSLog(@"******** 没有实现 (%@) 方法,手动添加成功!!",NSStringFromSelector(originalSel));

                       }

                     return;

                    }

                // 向实现 delegate 的类中添加新的方法    // 这里是向 originalClass 的 replaceSel(@selector(p_scrollViewDidEndDecelerating:)) 添加 replaceMethod

                 BOOL addMethod = class_addMethod(originalClass, replacedSel,                         method_getImplementation(replacedMethod), method_getTypeEncoding(replacedMethod));

                   if (addMethod) {

                            // 添加成功

                             NSLog(@"******** 实现了 (%@) 方法并成功 Hook 为 --> (%@)", NSStringFromSelector(originalSel), NSStringFromSelector(replacedSel));

                            // 重新拿到添加被添加的 method,这里是关键(注意这里 originalClass, 不 replacedClass), 因为替换的方法已经添加到原类中了, 应该交换原类中的两个方法

                             Method newMethod = class_getInstanceMethod(originalClass, replacedSel);

                            // 实现交换

                            method_exchangeImplementations(originalMethod, newMethod);

                      }else{

                        // 添加失败,则说明已经 hook 过该类的 delegate 方法,防止多次交换。

                        NSLog(@"******** 已替换过,避免多次替换 --> (%@)",NSStringFromClass(originalClass));

             }

    }

    最终我这方案没有被采纳,项目陈旧,我的这个也不能完全的适应那个项目,只能作为学习的一次机会了。

    相关文章

      网友评论

          本文标题:Runtime为分类添加属性和截取代理

          本文链接:https://www.haomeiwen.com/subject/wjzzpctx.html