项目作者: Tuccuay

项目描述 :
一个集合了常用 Objective-C Runtime 使用方法的 Playground。
高级语言: Objective-C
项目地址: git://github.com/Tuccuay/RuntimeSummary.git
创建时间: 2016-04-24T10:43:02Z
项目社区:https://github.com/Tuccuay/RuntimeSummary

开源协议:MIT License

关键词:
cocoa objective-c playground runtime

下载


RuntimeSummary

RuntimeSummary

一个集合了常用 Objective-C Runtime 使用方法的 Playground

目录

如何使用这个 Playground

Screenshot

先选择 Scheme,然后 Run!

消息机制介绍 / Messaging

  1. // 创建一个对象
  2. // Cat *harlan = [[Cat alloc] init];
  3. // 使用 Runtime 创建一个对象
  4. // 根据类名获取到类
  5. Class catClass = objc_getClass("Cat");
  6. // 同过类创建实例对象
  7. // 如果这里报错,请将 Build Setting -> Enable Strict Checking of objc_msgSend Calls 改为 NO
  8. Cat *harlan = objc_msgSend(catClass, @selector(alloc));
  9. // 初始化对象
  10. // harlan = [harlan init];
  11. // 通过 Runtime 初始化对象
  12. harlan = objc_msgSend(harlan, @selector(init));
  13. // 调用对象方法
  14. // [harlan eat];
  15. // 通过 Runtime 调用对象方法
  16. // 调用的这个方法没有声明只有实现所以这里会有警告
  17. // 但是发送消息的时候会从方法列表里寻找方法
  18. // 所以这个能够成功执行
  19. objc_msgSend(harlan, @selector(eat));
  20. // 输出: 2016-04-21 21:10:20.733 Messaging[20696:1825249] burbur~
  21. // 当然,objc_msgSend 可以传递参数
  22. Cat *alex = objc_msgSend(objc_msgSend(objc_getClass("Cat"), sel_registerName("alloc")), sel_registerName("init"));
  23. objc_msgSend(alex, @selector(run:), 10);

方法交换 / MethodSwizzling

  1. + (void)load {
  2. // 获取到两个方法
  3. Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
  4. Method tuc_imageNamedMethod = class_getClassMethod(self, @selector(tuc_imageNamed:));
  5. // 交换方法
  6. method_exchangeImplementations(imageNamedMethod, tuc_imageNamedMethod);
  7. }
  8. + (UIImage *)tuc_imageNamed:(NSString *)name {
  9. // 因为来到这里的时候方法实际上已经被交换过了
  10. // 这里要调用 imageNamed: 就需要调换被交换过的 tuc_imageNamed
  11. UIImage *image = [UIImage tuc_imageNamed:name];
  12. // 判断是否存在图片
  13. if (image) {
  14. NSLog(@"加载成功");
  15. } else {
  16. NSLog(@"加载失败");
  17. }
  18. return image;
  19. }

动态加载方法 / ResolveInstanceMethod

  1. void run(id self, SEL _cmd, NSNumber *metre) {
  2. NSLog(@"跑了%@米",metre);
  3. }
  4. // 当调用了一个未实现的方法会来到这里
  5. + (BOOL)resolveInstanceMethod:(SEL)sel {
  6. if (sel == NSSelectorFromString(@"run:")) {
  7. // 动态添加 run: 方法
  8. class_addMethod(self, @selector(run:), run, "v@:@");
  9. return YES;
  10. }
  11. return [super resolveInstanceMethod:sel];
  12. }

消息转发 / ForwardMessage

  1. #pragma mark - 实例方法
  2. // 第一步
  3. // 在没有找到方法时,会先调用此方法,可用于动态添加方法
  4. // 返回 YES 表示相应 selector 的实现已经被找到并添加到了类中,否则返回 NO
  5. + (BOOL)resolveInstanceMethod:(SEL)sel {
  6. return YES;
  7. }
  8. // 第二步
  9. // 如果第一步的返回 NO 或者直接返回了 YES 而没有添加方法,该方法被调用
  10. // 在这个方法中,我们可以指定一个可以返回一个可以响应该方法的对象
  11. // 如果返回 self 就会死循环
  12. - (id)forwardingTargetForSelector:(SEL)aSelector {
  13. return nil;
  14. }
  15. // 第三步
  16. // 如果 `forwardingTargetForSelector:` 返回了 nil,则该方法会被调用,系统会询问我们要一个合法的『类型编码(Type Encoding)』
  17. // https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
  18. // 若返回 nil,则不会进入下一步,而是无法处理消息
  19. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
  20. return [NSMethodSignature signatureWithObjCTypes:"v@:"];
  21. }
  22. // 当实现了此方法后,-doesNotRecognizeSelector: 将不会被调用
  23. // 如果要测试找不到方法,可以注释掉这一个方法
  24. // 在这里进行消息转发
  25. - (void)forwardInvocation:(NSInvocation *)anInvocation {
  26. // 我们还可以改变方法选择器
  27. [anInvocation setSelector:@selector(touch)];
  28. // 改变方法选择器后,还需要指定是哪个对象的方法
  29. [anInvocation invokeWithTarget:self];
  30. }
  31. - (void)touch {
  32. NSLog(@"Cat 没有实现 -stoke 方法,并且成功的转成了 -touch 方法");
  33. }
  34. - (void)doesNotRecognizeSelector:(SEL)aSelector {
  35. NSLog(@"无法处理消息:%@", NSStringFromSelector(aSelector));
  36. }

动态关联属性 / AssociatedObject

  1. - (void)setName:(NSString *)name {
  2. // 把属性关联给对象
  3. objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  4. }
  5. - (NSString *)name {
  6. // 取出属性
  7. return objc_getAssociatedObject(self, "name");
  8. }

字典转模型 / MakeModel

  1. + (instancetype)modelWithDict:(NSDictionary *)dict updateDict:(NSDictionary *)updateDict {
  2. id model = [[self alloc] init];
  3. // 遍历模型中属性
  4. unsigned int count = 0;
  5. Ivar *ivars = class_copyIvarList(self, &count);
  6. for (int i = 0 ; i < count; i++) {
  7. Ivar ivar = ivars[i];
  8. // 属性名称
  9. NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
  10. ivarName = [ivarName substringFromIndex:1];
  11. id value = dict[ivarName];
  12. // 模型中属性名对应字典中的key
  13. if (value == nil) {
  14. if (updateDict) {
  15. NSString *keyName = updateDict[ivarName];
  16. value = dict[keyName];
  17. }
  18. }
  19. [model setValue:value forKeyPath:ivarName];
  20. }
  21. return model;
  22. }
  23. + (instancetype)modelWithDict:(NSDictionary *)dict {
  24. return [self modelWithDict:dict updateDict:nil];
  25. }

对象归档、解档 / ObjectArchive

  1. - (void)tuc_initWithCoder:(NSCoder *)aDecoder {
  2. // 不光归档自身的属性,还要循环把所有父类的属性也找出来
  3. Class selfClass = self.class;
  4. while (selfClass &&selfClass != [NSObject class]) {
  5. unsigned int outCount = 0;
  6. Ivar *ivars = class_copyIvarList(selfClass, &outCount);
  7. for (int i = 0; i < outCount; i++) {
  8. Ivar ivar = ivars[i];
  9. NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
  10. // 如果有实现忽略属性的方法
  11. if ([self respondsToSelector:@selector(ignoredProperty)]) {
  12. // 就跳过这个属性
  13. if ([[self ignoredProperty] containsObject:key]) continue;
  14. }
  15. id value = [aDecoder decodeObjectForKey:key];
  16. [self setValue:value forKey:key];
  17. }
  18. free(ivars);
  19. selfClass = [selfClass superclass];
  20. }
  21. }
  22. - (void)tuc_encodeWithCoder:(NSCoder *)aCoder {
  23. Class selfClass = self.class;
  24. while (selfClass &&selfClass != [NSObject class]) {
  25. unsigned int outCount = 0;
  26. Ivar *ivars = class_copyIvarList([self class], &outCount);
  27. for (int i = 0; i < outCount; i++) {
  28. Ivar ivar = ivars[i];
  29. NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
  30. if ([self respondsToSelector:@selector(ignoredProperty)]) {
  31. if ([[self ignoredProperty] containsObject:key]) continue;
  32. }
  33. id value = [self valueForKeyPath:key];
  34. [aCoder encodeObject:value forKey:key];
  35. }
  36. free(ivars);
  37. selfClass = [selfClass superclass];
  38. }
  39. }

更多

觉得还有其它常用的 Runtime 用法没有讲到?欢迎 Pull Request!

或者懒得动手想看看大家的思路?没问题,提个 issue

觉得看了这个 Playground 豁然开朗?来 Star 一个吧!

License

RuntimeSummary is released under the MIT license. See LICENSE .