美文网首页OC基础
iOS开发runtime的使用场景

iOS开发runtime的使用场景

作者: 心成则玲 | 来源:发表于2022-07-21 10:59 被阅读0次

    1、Method Swizzling(动态方法交换)
    Method Swizzling用于改变一个已存在的selector实现。我们可以在程序运行时,通过改变selector所在Class(类)的method list(方法列表)的映射从而改变方法的调用。其实质就是交换两个方法的IMP(方法实现)。

    2、Method Swizzling应用场景
    2.1、全局页面统计功能

    #import "UIViewController+Swizzling.h"
    #import <objc/runtime.h>
    
    @implementation UIViewController (Swizzling)
    
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class class = [self class];
            
            SEL originalSelector = @selector(viewWillAppear:);
            SEL swizzledSelector = @selector(xxx_viewWillAppear:);
            
            Method originalMethod = class_getInstanceMethod(class, originalSelector);
            Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
            
            BOOL didAddMethod = class_addMethod(class,
                                                originalSelector,
                                                method_getImplementation(swizzledMethod),
                                                method_getTypeEncoding(swizzledMethod));
            
            if (didAddMethod) {
                class_replaceMethod(class,
                                    swizzledSelector,
                                    method_getImplementation(originalMethod),
                                    method_getTypeEncoding(originalMethod));
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        });
    }
    
    #pragma mark - Method Swizzling
    
    - (void)xxx_viewWillAppear:(BOOL)animated {
        
        if (![self isKindOfClass:[UIViewController class]]) {  // 剔除系统 UIViewController
            // 添加统计代码
            NSLog(@"进入页面:%@", [self class]);
        }
        
        [self xxx_viewWillAppear:animated];
    }
    
    @end
    

    2.2字体根据屏幕尺寸适配

    #import "UIFont+AdjustSwizzling.h"
    #import <objc/runtime.h>
    
    #define XXX_UISCREEN_WIDTH  375
    
    @implementation UIFont (AdjustSwizzling)
    
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class class = [self class];
    
            SEL originalSelector = @selector(systemFontOfSize:);
            SEL swizzledSelector = @selector(xxx_systemFontOfSize:);
    
            Method originalMethod = class_getInstanceMethod(class, originalSelector);
            Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
            BOOL didAddMethod = class_addMethod(class,
                                                originalSelector,
                                                method_getImplementation(swizzledMethod),
                                                method_getTypeEncoding(swizzledMethod));
    
            if (didAddMethod) {
                class_replaceMethod(class,
                                    swizzledSelector,
                                    method_getImplementation(originalMethod),
                                    method_getTypeEncoding(originalMethod));
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        });
    }
    
    + (UIFont *)xxx_systemFontOfSize:(CGFloat)fontSize {
        UIFont *newFont = nil;
        newFont = [UIFont xxx_systemFontOfSize:fontSize * [UIScreen mainScreen].bounds.size.width / XXX_UISCREEN_WIDTH];
        
        return newFont;
    }
    
    @end
    

    2.3处理按钮重复点击

    #import "UIButton+DelaySwizzling.h"
    #import <objc/runtime.h>
    
    @interface UIButton()
    
    // 重复点击间隔
    @property (nonatomic, assign) NSTimeInterval xxx_acceptEventInterval;
    
    @end
    
    
    @implementation UIButton (DelaySwizzling)
    
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class class = [self class];
    
            SEL originalSelector = @selector(sendAction:to:forEvent:);
            SEL swizzledSelector = @selector(xxx_sendAction:to:forEvent:);
    
            Method originalMethod = class_getInstanceMethod(class, originalSelector);
            Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
            BOOL didAddMethod = class_addMethod(class,
                                                originalSelector,
                                                method_getImplementation(swizzledMethod),
                                                method_getTypeEncoding(swizzledMethod));
    
            if (didAddMethod) {
                class_replaceMethod(class,
                                    swizzledSelector,
                                    method_getImplementation(originalMethod),
                                    method_getTypeEncoding(originalMethod));
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        });
    }
    
    - (void)xxx_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
        
        // 如果想要设置统一的间隔时间,可以在此处加上以下几句
        if (self.xxx_acceptEventInterval <= 0) {
            // 如果没有自定义时间间隔,则默认为 0.4 秒
            self.xxx_acceptEventInterval = 0.4;
        }
        
        // 是否小于设定的时间间隔
        BOOL needSendAction = (NSDate.date.timeIntervalSince1970 - self.xxx_acceptEventTime >= self.xxx_acceptEventInterval);
        
        // 更新上一次点击时间戳
        if (self.xxx_acceptEventInterval > 0) {
            self.xxx_acceptEventTime = NSDate.date.timeIntervalSince1970;
        }
        
        // 两次点击的时间间隔小于设定的时间间隔时,才执行响应事件
        if (needSendAction) {
            [self xxx_sendAction:action to:target forEvent:event];
        }
    }
    
    - (NSTimeInterval )xxx_acceptEventInterval{
        return [objc_getAssociatedObject(self, "UIControl_acceptEventInterval") doubleValue];
    }
    
    - (void)setXxx_acceptEventInterval:(NSTimeInterval)xxx_acceptEventInterval{
        objc_setAssociatedObject(self, "UIControl_acceptEventInterval", @(xxx_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (NSTimeInterval )xxx_acceptEventTime{
        return [objc_getAssociatedObject(self, "UIControl_acceptEventTime") doubleValue];
    }
    
    - (void)setXxx_acceptEventTime:(NSTimeInterval)xxx_acceptEventTime{
        objc_setAssociatedObject(self, "UIControl_acceptEventTime", @(xxx_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    @end
    

    2.4、TableView、CollectionView异常加载占位图

    #import <UIKit/UIKit.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface UITableView (ReloadDataSwizzling)
    
    @property (nonatomic, assign) BOOL firstReload;
    @property (nonatomic, strong) UIView *placeholderView;
    @property (nonatomic,   copy) void(^reloadBlock)(void);
    
    @end
    
    /*--------------------------------------*/
    
    #import "UITableView+ReloadDataSwizzling.h"
    #import "XXXPlaceholderView.h"
    #import <objc/runtime.h>
    
    @implementation UITableView (ReloadDataSwizzling)
    
    
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class class = [self class];
    
            SEL originalSelector = @selector(reloadData);
            SEL swizzledSelector = @selector(xxx_reloadData);
    
            Method originalMethod = class_getInstanceMethod(class, originalSelector);
            Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
            BOOL didAddMethod = class_addMethod(class,
                                                originalSelector,
                                                method_getImplementation(swizzledMethod),
                                                method_getTypeEncoding(swizzledMethod));
    
            if (didAddMethod) {
                class_replaceMethod(class,
                                    swizzledSelector,
                                    method_getImplementation(originalMethod),
                                    method_getTypeEncoding(originalMethod));
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        });
    }
    
    - (void)xxx_reloadData {
        if (!self.firstReload) {
            [self checkEmpty];
        }
        self.firstReload = NO;
        
        [self xxx_reloadData];
    }
    
    
    - (void)checkEmpty {
        BOOL isEmpty = YES; // 判空 flag 标示
        
        id <UITableViewDataSource> dataSource = self.dataSource;
        NSInteger sections = 1; // 默认TableView 只有一组
        if ([dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
            sections = [dataSource numberOfSectionsInTableView:self] - 1; // 获取当前TableView 组数
        }
        
        for (NSInteger i = 0; i <= sections; i++) {
            NSInteger rows = [dataSource tableView:self numberOfRowsInSection:i]; // 获取当前TableView各组行数
            if (rows) {
                isEmpty = NO; // 若行数存在,不为空
            }
        }
        if (isEmpty) { // 若为空,加载占位图
            if (!self.placeholderView) { // 若未自定义,加载默认占位图
                [self makeDefaultPlaceholderView];
            }
            self.placeholderView.hidden = NO;
            [self addSubview:self.placeholderView];
        } else { // 不为空,隐藏占位图
            self.placeholderView.hidden = YES;
        }
    }
    
    - (void)makeDefaultPlaceholderView {
        self.bounds = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
        XXXPlaceholderView *placeholderView = [[XXXPlaceholderView alloc] initWithFrame:self.bounds];
        __weak typeof(self) weakSelf = self;
        [placeholderView setReloadClickBlock:^{
            if (weakSelf.reloadBlock) {
                weakSelf.reloadBlock();
            }
        }];
        self.placeholderView = placeholderView;
    }
    
    - (BOOL)firstReload {
        return [objc_getAssociatedObject(self, @selector(firstReload)) boolValue];
    }
    
    - (void)setFirstReload:(BOOL)firstReload {
        objc_setAssociatedObject(self, @selector(firstReload), @(firstReload), OBJC_ASSOCIATION_ASSIGN);
    }
    
    - (UIView *)placeholderView {
        return objc_getAssociatedObject(self, @selector(placeholderView));
    }
    
    - (void)setPlaceholderView:(UIView *)placeholderView {
        objc_setAssociatedObject(self, @selector(placeholderView), placeholderView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (void (^)(void))reloadBlock {
        return objc_getAssociatedObject(self, @selector(reloadBlock));
    }
    
    - (void)setReloadBlock:(void (^)(void))reloadBlock {
        objc_setAssociatedObject(self, @selector(reloadBlock), reloadBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    @end
    

    2.5、数组,字典赋值重写拦截防止nil奔溃
    dic

    #import "NSDictionary+Safe.h"
    #import <objc/runtime.h>
     
    @implementation NSDictionary (Safe)
     
    + (void)load {
        Method originalMethod = class_getClassMethod(self, @selector(dictionaryWithObjects:forKeys:count:));
        Method swizzledMethod = class_getClassMethod(self, @selector(na_dictionaryWithObjects:forKeys:count:));
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
     
    + (instancetype)na_dictionaryWithObjects:(const id [])objects forKeys:(const id <NSCopying> [])keys count:(NSUInteger)cnt {
        id nObjects[cnt];
        id nKeys[cnt];
        int i=0, j=0;
        for (; i<cnt && j<cnt; i++) {
            if (objects[i] && keys[i]) {
                nObjects[j] = objects[i];
                nKeys[j] = keys[i];
                j++;
            }
        }
        
        return [self na_dictionaryWithObjects:nObjects forKeys:nKeys count:j];
    }
     
    @end
     
    @implementation NSMutableDictionary (Safe)
     
    + (void)load {
        Class dictCls = NSClassFromString(@"__NSDictionaryM");
        Method originalMethod = class_getInstanceMethod(dictCls, @selector(setObject:forKey:));
        Method swizzledMethod = class_getInstanceMethod(dictCls, @selector(na_setObject:forKey:));
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
     
    - (void)na_setObject:(id)anObject forKey:(id<NSCopying>)aKey {
        if (!anObject || !aKey)
            return;
        [self na_setObject:anObject forKey:aKey];
    }
     
    @end
    
    

    array

    #import "NSArray+Safe.h"
    #import <objc/runtime.h>
     
    @implementation NSArray (Safe)
     
    + (void)load {
        Method originalMethod = class_getClassMethod(self, @selector(arrayWithObjects:count:));
        Method swizzledMethod = class_getClassMethod(self, @selector(na_arrayWithObjects:count:));
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
     
    + (instancetype)na_arrayWithObjects:(const id [])objects count:(NSUInteger)cnt {
        id nObjects[cnt];
        int i=0, j=0;
        for (; i<cnt && j<cnt; i++) {
            if (objects[i]) {
                nObjects[j] = objects[i];
                j++;
            }
        }
        
        return [self na_arrayWithObjects:nObjects count:j];
    }
    @end
     
    @implementation NSMutableArray (Safe)
     
    + (void)load {
        Class arrayCls = NSClassFromString(@"__NSArrayM");
        
        Method originalMethod1 = class_getInstanceMethod(arrayCls, @selector(insertObject:atIndex:));
        Method swizzledMethod1 = class_getInstanceMethod(arrayCls, @selector(na_insertObject:atIndex:));
        method_exchangeImplementations(originalMethod1, swizzledMethod1);
        
        Method originalMethod2 = class_getInstanceMethod(arrayCls, @selector(setObject:atIndex:));
        Method swizzledMethod2 = class_getInstanceMethod(arrayCls, @selector(na_setObject:atIndex:));
        method_exchangeImplementations(originalMethod2, swizzledMethod2);
    }
     
    - (void)na_insertObject:(id)anObject atIndex:(NSUInteger)index {
        if (!anObject)
            return;
        [self na_insertObject:anObject atIndex:index];
    }
     
    - (void)na_setObject:(id)anObject atIndex:(NSUInteger)index {
        if (!anObject)
            return;
        [self na_setObject:anObject atIndex:index];
    }
     
    @end
    

    3、修改私有属性
    3.1、更改UITextField占位文字的颜色和字号

    // 打印 UITextfield 的所有属性和成员变量
    - (void)printUITextFieldList {
        unsigned int count;
        
        Ivar *ivarList = class_copyIvarList([UITextField class], &count);
        for (unsigned int i = 0; i < count; i++) {
            Ivar myIvar = ivarList[i];
            const char *ivarName = ivar_getName(myIvar);
            NSLog(@"ivar(%d) : %@", i, [NSString stringWithUTF8String:ivarName]);
        }
        
        free(ivarList);
        
        objc_property_t *propertyList = class_copyPropertyList([UITextField class], &count);
        for (unsigned int i = 0; i < count; i++) {
            const char *propertyName = property_getName(propertyList[i]);
            NSLog(@"propertyName(%d) : %@", i, [NSString stringWithUTF8String:propertyName]);
        }
        
        free(propertyList);
    }
    
    // 通过修改 UITextfield 的私有属性更改占位颜色和字体
    - (void)createLoginTextField {
        UITextField *loginTextField = [[UITextField alloc] init];
        loginTextField.frame = CGRectMake(15,(self.view.bounds.size.height-52-50)/2, self.view.bounds.size.width-60-18,52);
        loginTextField.delegate = self;
        loginTextField.font = [UIFont systemFontOfSize:14];
        loginTextField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
        loginTextField.textColor = [UIColor blackColor];
        
        loginTextField.placeholder = @"用户名/邮箱";
        [loginTextField setValue:[UIFont systemFontOfSize:15] forKeyPath:@"_placeholderLabel.font"];
        [loginTextField setValue:[UIColor lightGrayColor]forKeyPath:@"_placeholderLabel.textColor"];
        
        [self.view addSubview:loginTextField];
    }
    

    3.2、万能控制器跳转

    // 定义的规则
    NSDictionary *params = @{
                             @"class" : @"XXViewController",
                             @"property" : @{
                                     @"ID" : @"123",
                                     @"type" : @"XXViewController1"
                                     }
                             };
    

    然后,添加一个工具类 XXJumpControllerTool,添加跳转相关的类方法。

    /********************* XXJumpControllerTool.h 文件 *********************/
    
    #import <Foundation/Foundation.h>
    
    @interface XXJumpControllerTool : NSObject
    
    + (void)pushViewControllerWithParams:(NSDictionary *)params;
    
    @end
    
    
    /********************* XXJumpControllerTool.m 文件 *********************/
    
    #import "XXJumpControllerTool.h"
    #import <UIKit/UIKit.h>
    #import <objc/runtime.h>
    
    @implementation XXJumpControllerTool
    
    + (void)pushViewControllerWithParams:(NSDictionary *)params {
        // 取出控制器类名
        NSString *classNameStr = [NSString stringWithFormat:@"%@", params[@"class"]];
        const char *className = [classNameStr cStringUsingEncoding:NSASCIIStringEncoding];
        
        // 根据字符串返回一个类
        Class newClass = objc_getClass(className);
        if (!newClass) {
            // 创建一个类
            Class superClass = [NSObject class];
            newClass = objc_allocateClassPair(superClass, className, 0);
            // 注册你创建的这个类
            objc_registerClassPair(newClass);
        }
    
        // 创建对象(就是控制器对象)
        id instance = [[newClass alloc] init];
        
        NSDictionary *propertys = params[@"property"];
        [propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
            // 检测这个对象是否存在该属性
            if ([XXJumpControllerTool checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
                // 利用 KVC 对控制器对象的属性赋值
                [instance setValue:obj forKey:key];
            }
        }];
        
        
        // 跳转到对应的控制器
        [[XXJumpControllerTool topViewController].navigationController pushViewController:instance animated:YES];
    }
    
    
    // 检测对象是否存在该属性
    + (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName {
        unsigned int count, i;
        
        // 获取对象里的属性列表
        objc_property_t *properties = class_copyPropertyList([instance class], &count);
        
        for (i = 0; i < count; i++) {
            objc_property_t property =properties[i];
            //  属性名转成字符串
            NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
            // 判断该属性是否存在
            if ([propertyName isEqualToString:verifyPropertyName]) {
                free(properties);
                return YES;
            }
        }
        free(properties);
        
        return NO;
    }
    
    // 获取当前显示在屏幕最顶层的 ViewController
    + (UIViewController *)topViewController {
        UIViewController *resultVC = [XXJumpControllerTool _topViewController:[[UIApplication sharedApplication].keyWindow rootViewController]];
        while (resultVC.presentedViewController) {
            resultVC = [XXJumpControllerTool _topViewController:resultVC.presentedViewController];
        }
        return resultVC;
    }
    
    + (UIViewController *)_topViewController:(UIViewController *)vc {
        if ([vc isKindOfClass:[UINavigationController class]]) {
            return [XXJumpControllerTool _topViewController:[(UINavigationController *)vc topViewController]];
        } else if ([vc isKindOfClass:[UITabBarController class]]) {
            return [XXJumpControllerTool _topViewController:[(UITabBarController *)vc selectedViewController]];
        } else {
            return vc;
        }
        return nil;
    }
    
    @end
    

    3.3改进iOS归档和解档

    // 解档
    - (instancetype)xx_modelInitWithCoder:(NSCoder *)aDecoder {
        if (!aDecoder) return self;
        if (!self) {
            return self;
        }
        
        unsigned int count;
        objc_property_t *propertyList = class_copyPropertyList([self class], &count);
        for (unsigned int i = 0; i < count; i++) {
            const char *propertyName = property_getName(propertyList[i]);
            NSString *name = [NSString stringWithUTF8String:propertyName];
            
            id value = [aDecoder decodeObjectForKey:name];
            [self setValue:value forKey:name];
        }
        free(propertyList);
        
        return self;
    }
    
    // 归档
    - (void)xx_modelEncodeWithCoder:(NSCoder *)aCoder {
        if (!aCoder) return;
        if (!self) {
            return;
        }
        unsigned int count;
        objc_property_t *propertyList = class_copyPropertyList([self class], &count);
        for (unsigned int i = 0; i < count; i++) {
            const char *propertyName = property_getName(propertyList[i]);
            NSString *name = [NSString stringWithUTF8String:propertyName];
            
            id value = [self valueForKey:name];
            [aCoder encodeObject:value forKey:name];
        }
        free(propertyList);
    }
    

    然后在需要实现归档解档的模型中,添加 -initWithCoder: 和 -encodeWithCoder: 方法。

    #import "XXPerson.h"
    #import "NSObject+XXModel.h"
    
    @implementation XXPerson
    
    - (instancetype)initWithCoder:(NSCoder *)aDecoder {
        self = [super init];
        if (self) {
            [self xx_modelInitWithCoder:aDecoder];
        }
        return self;
    }
    
    - (void)encodeWithCoder:(NSCoder *)aCoder {
        [self xx_modelEncodeWithCoder:aCoder];
    }
    
    @end
    

    3.4、按钮的重复点击

    + (void)load{
        Method originalMethod = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:));
        Method swizzledMethod = class_getInstanceMethod([self class], @selector(User_SendAction:to:forEvent:));
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
     
    #pragma mark -- 时间间隔 --
    static const void *ButtonDurationTime = @"ButtonDurationTime";
    - (NSTimeInterval)durationTime{
        NSNumber *number = objc_getAssociatedObject(self, &ButtonDurationTime);
        return number.doubleValue;
    }
    - (void)setDurationTime:(NSTimeInterval)durationTime{
        NSNumber *number = [NSNumber numberWithDouble:durationTime];
        objc_setAssociatedObject(self, &ButtonDurationTime, number, OBJC_ASSOCIATION_COPY_NONATOMIC);
       
    }
     
    - (void)User_SendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
      
        self.userInteractionEnabled = NO;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.durationTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            self.userInteractionEnabled = YES;
        });
        
        [self User_SendAction:action to:target forEvent:event];
    }
    

    3.5、字典转模型

    相关文章

      网友评论

        本文标题:iOS开发runtime的使用场景

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