美文网首页
runtime - 应用篇(1)

runtime - 应用篇(1)

作者: SPIREJ | 来源:发表于2019-11-06 16:31 被阅读0次

I - 归档

你还在一个一个的setValue: forKey:吗?? 更好的办法就是我们使用runtime来实现归档,这个就不必多说了,相信都用过,这里就抛砖引玉记录一个我使用过例子。

场景:
1. 这里有一个SPPerson类有一些属性,将来对这个类的实例属性做归档
2. 遵循NSSecureCoding协议,在.m文件中实现协议方法

@interface SPPerson : NSObject<NSSecureCoding>

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) NSInteger age;

@end
- (void)encodeWithCoder:(NSCoder *)coder {
    unsigned int count = 0;
    Ivar* ivars = class_copyIvarList([self class], &count);
    
    for (int i = 0; i < count; i++) {
        Ivar var = ivars[i];
        const char* name = ivar_getName(var);
        NSString* key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        [coder encodeObject:value forKey:key];
    }
    free(ivars);
}

- (instancetype)initWithCoder:(NSCoder *)coder {
    if (self = [super init]) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([self class], &count);
        for (int i = 0; i < count; i++) {
            Ivar var = ivars[i];
            const char *name = ivar_getName(var);
            NSString *key = [NSString stringWithUTF8String:name];
            id value = [coder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
        free(ivars);
    }
    return self;
}

+ (BOOL)supportsSecureCoding {
    return YES;
}
  1. 实现一个SPArchiveTool的归档工具类,提供归档和解档类方法
@interface SPArchiveTool : NSObject

+ (BOOL)sp_archiveObject:(id)object prefix:(NSString *)prefix;
+ (id)sp_unarchiveClass:(Class)class prefix:(NSString *)prefix;

@end
+ (BOOL)sp_archiveObject:(id)object prefix:(NSString *)prefix {
    if (!object) {
        return NO;
    }
    
    NSError *error;
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object requiringSecureCoding:NO error:&error];
    
    if (error) {
        return NO;
    }
    
    [data writeToFile:[SPArchiveTool getPathWithPrefix:prefix] atomically:YES];
    
    return YES;
}

+ (id)sp_unarchiveClass:(Class)class prefix:(NSString *)prefix {
    
    NSError *error;
    NSData *data = [[NSData alloc] initWithContentsOfFile:[SPArchiveTool getPathWithPrefix:prefix]];
    
    if (!data) {
        return nil;
    }
    
    // 会调用对象的 initWithCoder方法
    id content = [NSKeyedUnarchiver unarchivedObjectOfClass:class fromData:data error:&error];
    
    if (error) {
        return nil;
    }
    return content;
}

// 存放的文件路径
+ (NSString *)getPathWithPrefix:(NSString *)prefix {
    NSError *error;
    NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    NSString *filePathFolder = [documentPath stringsByAppendingPaths:@[@"archiveTemp"]].firstObject;
    if (![[NSFileManager defaultManager] fileExistsAtPath:filePathFolder]) {
        [[NSFileManager defaultManager] createDirectoryAtPath:filePathFolder withIntermediateDirectories:YES attributes:nil error:&error];
    }
    NSString *path = [NSString stringWithFormat:@"%@/%@.plist", filePathFolder, prefix];
    return path;
}
  1. 测试
SPPerson *p = [[SPPerson alloc] init];
p.name = @"spirej";
p.nickName = @"sp";
p.age = 18;

// 归档
[SPArchiveTool sp_archiveObject:p prefix:NSStringFromClass(SPPerson.class)];

// 解档
[SPArchiveTool sp_unarchiveClass:SPPerson.class prefix:NSStringFromClass(SPPerson.class)];

NSLog(@"name = %@, nickName = %@, age = %ld", p.name, p.nickName, (long)p.age);

/*
打印结果
name = spirej, nickName = sp, age = 18
*/

II - 动态创建类

场景:

  1. 动态创建一个SPCat类,并测试添加实例变量,添加方法,创建实例对象并对实例变量赋值,调用方法等
void hunting(id self, SEL _cmd) {
    NSLog(@"%@ - %s", self, __func__);
}

- (void)createClassTest {
    // 创建一类对
    Class SPCat = objc_allocateClassPair([NSObject class], "SPCat", 0);
    
    // 添加实例变量
    NSString *name = @"spCat";
    class_addIvar(SPCat, name.UTF8String, sizeof(id), log2(sizeof(id)), @encode(id));
    
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
    // 添加方法
    class_addMethod(SPCat, @selector(hunting), (IMP)hunting, "v@:");
    
    // 注册类
    objc_registerClassPair(SPCat);
    
    // 创建实例对象
    id cat = [[SPCat alloc] init];
    [cat setValue:@"Tom" forKey:name];
    NSLog(@"name = %@", [cat valueForKey:name]);
    
    // 方法调用
    [cat performSelector:@selector(hunting)];
}
  1. 打印结果
2019-11-06 02:31:46.433386+0800 runtime应用[22760:2231475] name = Tom
2019-11-06 02:31:46.434725+0800 runtime应用[22760:2231475] <SPCat: 0x6000023444d0> - hunting

III - 动态创建类(动态创建不存在的控制器)

场景:

  1. 我们将要在控制器间跳转,有红绿蓝三个vc,其中红绿vc已经在项目中创建过的,蓝vc是没有的,那么我们需要点击按钮也可以跳转到想要的蓝色vc,怎么做呢?答案依然是通过runtime底层动态创建

废话不多说,直接上代码

self.dataArray = @[@{@"class":@"SPRedViewController",
      @"data":@{@"name":@"spirej-18",
                @"backgroundColor":@"red"}},
    @{@"class":@"SPGreenViewController",
      @"data":@{@"slogan":@"和谐学习,不急不躁",
                @"backgroundColor":@"green"}},
    @{@"class":@"SPBlueViewController",
      @"data":@{@"ending":@"我就是我,颜色不一样的烟火",
                @"backgroundColor":@"blue"}}];
- (IBAction)goRedVC:(UIButton *)sender {
    [self pushToAnyVCWithData:self.dataArray[0]];
}

- (IBAction)goGreenVC:(UIButton *)sender {
    [self pushToAnyVCWithData:self.dataArray[1]];
}

- (IBAction)goBlueVC:(UIButton *)sender {
    [self pushToAnyVCWithData:self.dataArray[2]];
}

- (void)pushToAnyVCWithData:(NSDictionary *)dataDic {
    
    const char *clsName = [dataDic[@"class"] UTF8String];
    Class cls = objc_getClass(clsName);
    
    // 1.创建类
    if (!cls) {
        Class superClass = [UIViewController class];
        // 创建一类对
        cls = objc_allocateClassPair(superClass, clsName, 0);
        // 添加成员变量
        class_addIvar(cls, "ending", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
        class_addIvar(cls, "show_lb", sizeof(UILabel *), log2(sizeof(UILabel *)), @encode(UILabel *));
        // 添加方法
        Method method = class_getInstanceMethod([self class], @selector(sp_instanceMethod));
        IMP methodIMP = method_getImplementation(method);
        const char *types = method_getTypeEncoding(method);
        BOOL rest = class_addMethod(cls, @selector(viewDidLoad), methodIMP, types);
        NSLog(@"rest == %d", rest);
    }
    
    // 实例化对象
    id instance = nil;
    @try {
        UIStoryboard *sb = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];
        instance = [sb instantiateViewControllerWithIdentifier:dataDic[@"class"]];
    } @catch (NSException *exception) {
        instance = [[cls alloc] init];
    } @finally {
        NSLog(@"OK");
    }
    
    NSDictionary *dict = dataDic[@"data"];
    [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        
        // 检测是否存在key的属性
        if (class_getProperty(cls, [key UTF8String])) {
            [instance setValue:obj forKey:key];
        }
        // 检测是否存在key的变量
        if (class_getInstanceVariable(cls, [key UTF8String])) {
            [instance setValue:obj forKey:key];
        }
    }];
    
    [self.navigationController pushViewController:instance animated:YES];
}

- (void)sp_instanceMethod {
    [super viewDidLoad];
    
    [self setValue:[UIColor blueColor] forKeyPath:@"view.backgroundColor"];
    [self setValue:[[UILabel alloc] initWithFrame:CGRectMake(100, 200, 200, 30)] forKey:@"show_lb"];
    UILabel *show_lb = [self valueForKey:@"show_lb"];
    [[self valueForKey:@"view"] addSubview:show_lb];
    show_lb.text = [self valueForKey:@"ending"];
    show_lb.font = [UIFont systemFontOfSize:14];
    show_lb.textColor = [UIColor blackColor];
    show_lb.textAlignment = NSTextAlignmentCenter;
    show_lb.backgroundColor = [UIColor whiteColor];
    NSLog(@"hello word");
}
  1. 测试效果,发现成功跳转动态创建的vc
测试效果
  1. 本篇示例代码地址https://github.com/SPIREJ/runtime-/tree/master

相关文章

网友评论

      本文标题:runtime - 应用篇(1)

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