iOS锦囊

Runtime-基础与应用

2018-02-06  本文已影响10人  mtry

主要内容

Runtime 基础

Objective-C 语言将决定尽可能的从编译和链接推迟到运行时。只要有可能 Objective-C 总是使用动态的方式来解决问题。这就意味着 Objective-C 语言不仅需要一个编译器,同时需要一个运行时系统来执行编译好的代码。

交互方式

大部分情况下,运行时系统在后台自动运行,我们只需编写和编译 Objective-C 源代码。当编译 Objective-C 类和方法时,编译器为实现语言动态特性将自动创建一些数据结构和函数,方便运行期发送消息。

NSObject 有些方法能在运行时获得类的信息,并检查一些特性,比如 class 返回对象的类;isKindOfClass:isMemberOfClass: 则检查对象是否在指定的类继承体系中;respondsToSelector: 检查对象能否响应指定的消息;conformsToProtocol: 检查对象是否实现了指定协议类的方法;methodForSelector: 则返回指定方法实现的地址。

运行时系统是一个有公开接口的动态库,由一些数据结构和函数的集合组成,这些数据结构和函数的声明头文件在 /usr/include/objc 中。这些函数支持用纯 C 的函数来实现和 Objective-C 同样的功能。还有一些函数构成了 NSObject 类方法的基础。这些函数使得访问运行时系统接口和提供开发工具成为可能。尽管大部分情况下它们在 Objective-C 程序不是必须的,但是有时候对于 Objecitve-C 程序来说某些函数是非常有用的。

消息传递

在很多语言,比如 C ,调用一个方法其实就是跳到内存中的某一点并开始执行一段代码。没有任何动态的特性,因为这在编译时就决定好了。而在 Objective-C 中,[object foo]语法并不会立即执行 foo 这个方法的代码。它是在运行时给 object 发送一条叫 foo 的消息。这个消息,也许会由 object 来处理,也许会被转发给另一个对象,或者不予理睬假装没收到这个消息。多条不同的消息也可以对应同一个方法实现。这些都是在程序运行的时候决定的。

Objective-C 消息语句

[object foo];

编译器编译之后

objc_msgSend(object, @selector(foo));

如果有参数则为

objc_msgSend(object, @selector(foo), arg1, arg2, ...)

接下来我来看一下转换为 objc_msgSend 函数之后的传递过程

id objc_msgSend(id self, SEL _cmd, ...);
  1. 检查忽略的_cmd,比如当我们运行在有垃圾回收机制的环境中,将会忽略 retain 和 release 消息。
  2. 检查 self 是否为 nil。不像其他语言,nil 在 Objective-C 中是完全合法的,并且这里有很多原因你也愿意这样,比如,至少我们省去了给一个对象发送消息前检查对象是否为空的操作。如果 self 为空,则会将 _cmd 也设置为空,并且直接返回到消息调用的地方。如果对象非空,就继续下一步。
  3. 接下来会根据 SEL 到当前类中查找对应的 IMP(方法实现的函数指针),首先会在 cache 中检索它,如果找到了就根据函数指针跳转到这个函数执行,否则进行下一步。
  4. 检索当前类对象中的方法表(method list),如果找到了,加入 cache 中,并且就跳转到这个函数之行,否则进行下一步。
  5. 从父类中寻找,直到根类(NSObject)。找到了就将方法加入对应类的 cache 表中,否则进行下一步。
  6. 动态方法解析,没有实现解析进入下一步。
  7. 消息转发,如果没有实现转发下一步。
  8. 闪退。

动态方法解析

当向一个对象发送一个消息时,在对象中没有找到对应的实现,那么就会进入动态解析。

例:动态解析实例方法

@interface MTObject : NSObject
@end

@implementation MTObject

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if(sel == @selector(testFun))
    {
        class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void dynamicMethodIMP(id self, SEL _cmd)
{
    NSLog(@"ok");
}
@end

例:动态解析类方法

@interface MTObject : NSObject
@end

@implementation MTObject

+ (BOOL)resolveClassMethod:(SEL)sel
{
    if(sel == @selector(testFun))
    {
        class_addMethod(object_getClass(self), sel, (IMP)dynamicMethodIMP, "v@:");
        return YES;
    }
    return [super resolveClassMethod:sel];
}

void dynamicMethodIMP(id self, SEL _cmd)
{
    NSLog(@"ok");
}
@end

注意:

  1. IMP 为函数指针定义为 typedef id (*IMP)(id, SEL, ...)
  2. IMP 可以通过传人一个块获取 IMP imp_implementationWithBlock(id block)
  3. class_addMethod 中 "v@:" 表示IMP的返回值和参数。依次表示:函数返回为空、id类型的参数、SEL类型的参数,具体参考Type Encoding 官方Type Encoding 其它

消息转发

重定向实例方法

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(xxx))
    {
        return otherObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}

重定向类方法

+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(xxx))
    {
        return NSClassFromString(@"className");
    }
    return [super forwardingTargetForSelector:aSelector];
}

转发(当动态方法解析返回 NO,在重定向返回的是 nil 或 self 时开始进行)

@interface MTProxy : NSProxy
@property (nonatomic, strong) id target;
@end

@implementation MTProxy

- (instancetype)initWithTarget:(id)target
{
    self.target = target;
    return self;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    [anInvocation invokeWithTarget:self.target];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
    return [self.target methodSignatureForSelector:sel];
}

@end

注意:

  1. 消息转发时,在forwardInvocation:消息发送前,Runtime系统会向对象发送methodSignatureForSelector:消息,并取到返回的方法签名用于生成NSInvocation对象。所以我们在重写forwardInvocation:的同时也要重写methodSignatureForSelector:方法。

几个难点

1、关于元类(Meta Class),需要先了解一些数据结构的定义

id

typedef struct objc_object *id;

objc_object

struct objc_object { 
    Class isa; 
};

Class

typedef struct objc_class *Class;

objc_class

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
    
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

从上面的数据结构可以看出,任何一个可以通过 id 表示的对象可以通过 isa 指针找到对应类的 Class。需要注意的是,在 objc_class 中还有个 isa 指针指向一个 Class,这个 Class 就是元类(Meta Class)。既然元类也是类,自然也有一个 isa 指针了,这时的 isa 指针指向的是 Root Class 的元类,而 Root Class 的元类的 isa 指针指向自己。元类中的 objc_method_list 存储的是类方法。

总结:

image_1.png

Runtime 应用

@interface MTObject : NSObject

@property (nonatomic, strong) NSString *string1;

@end

@implementation MTObject
{
    NSString *string2;
}
@end


//核心代码
- (void)allProperty
{
    unsigned int count = 0;
    objc_property_t *propertyList = class_copyPropertyList([MTObject class], &count);
    for(int i = 0; i < count; i++)
    {
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(propertyList[i])];
        NSLog(@"%@", propertyName);
    }
}

//输出结果
19:15:23.177 ObjectiveDemo[1214:54491] string1

@interface MTObject : NSObject

@property (nonatomic, readonly) NSString *string1;

@end

@implementation MTObject
{
    NSString *string2;
}
@end

//核心代码
- (void)allIvar
{
    unsigned int count = 0;
    Ivar *ivarList = class_copyIvarList([MTObject class], &count);
    for(int i = 0; i < count; i++)
    {
        NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivarList[i])];
        NSLog(@"%@", propertyName);
    }
}

//输出结果
19:22:39.955 ObjectiveDemo[1287:57828] string2
19:22:39.955 ObjectiveDemo[1287:57828] _string1

注意:@property定义的成员变量都带下划线 _property;如果有 @synthesize 重新定义,则和重新定义的一致。
@interface MTObject : NSObject

@end

@implementation MTObject

- (void)instanceMethod{}
+ (void)classMethod{}

@end

//核心方法
- (void)allInstanceMethod
{
    unsigned int count = 0;
    Method *methodList = class_copyMethodList([MTObject class], &count);
    for(int i = 0; i < count; i++)
    {
        SEL sel = method_getName(methodList[i]);
        NSString *methodName = NSStringFromSelector(sel);
        NSLog(@"%@", methodName);
    }
}

//输出结果
19:49:40.219 ObjectiveDemo[1501:69002] instanceMethod
@interface MTObject : NSObject

@end

@implementation MTObject

- (void)instanceMethod{}
+ (void)classMethod{}

@end

//核心代码(其实就是 class_copyMethodList 传人的 Class 为对于的元类就可以了)
- (void)allClassMethod
{
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(objc_getMetaClass(class_getName([MTObject class])), &count);
    for(int i = 0; i < count; i++)
    {
        SEL sel = method_getName(methodList[i]);
        NSString *methodName = NSStringFromSelector(sel);
        NSLog(@"%@", methodName);
    }
}

//输出结果
19:55:45.357 ObjectiveDemo[1528:71022] classMethod
@interface MTObject : NSObject

@end

@implementation MTObject

@end

@interface MTObject (category)

@property (nonatomic, strong) NSString *categoryProperty;

@end

@implementation MTObject (category)

- (void)setCategoryProperty:(NSString *)categoryProperty
{
    objc_setAssociatedObject(self, 
                            @selector(categoryProperty), 
                            categoryProperty, 
                            OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)categoryProperty
{
    return objc_getAssociatedObject(self, @selector(categoryProperty));
}

@end


objc_AssociationPolicy几种类型
OBJC_ASSOCIATION_ASSIGN           指定一个关联对象的弱引用。                  相当于@property(assign)或
                                                                              @property(unsafe_unretained)
OBJC_ASSOCIATION_RETAIN_NONATOMIC 指定一个关联对象的强引用,不能被原子化使用。   相当于@property(nonatomic, strong)
OBJC_ASSOCIATION_COPY_NONATOMIC   指定一个关联对象的copy引用,不能被原子化使用。 相当于@property(nonatomic, copy)
OBJC_ASSOCIATION_RETAIN           指定一个关联对象的强引用,能被原子化使用。     相当于@property(atomic, strong)
OBJC_ASSOCIATION_COPY             指定一个关联对象的copy引用,能被原子化使用。   相当于@property(atomic, copy)
@implementation UIViewController (Tracking)

+ (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);

        // 如果是类方法
        // Class class = object_getClass((id)self);
        // ...
        // Method originalMethod = class_getClassMethod(class, originalSelector);
        // Method swizzledMethod = class_getClassMethod(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 {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}

@end

注意:理解 selector, method, implementation 这三个概念之间关系的最好方式是:在运行时,类(Class)维护了一个消息分发列表来解决消息的正确发送。每一个消息列表的入口是一个方法(Method),这个方法映射了一对键值对,其中键值是这个方法的名字 selector(SEL),值是指向这个方法实现的函数指针 implementation(IMP)。 Method swizzling 修改了类的消息分发列表使得已经存在的 selector 映射了另一个实现 implementation,同时重命名了原生方法的实现为一个新的 selector。

参考资料

  1. Objective-C 2.0运行时系统编程指南(官方文档翻译)
  2. Objective-C Runtime
  3. Objective-C runtime之运行时的基本特点(一)
  4. Objective-C runtime之消息(二)
  5. Objective-C runtime之消息转发机制(三)
  6. Type Encoding 官方
  7. Type Encoding 其它
  8. 使用NSProxy和NSObject设计代理类的差异
  9. runtime详解(应用)
  10. 谈Runtime机制和使用的整体化梳理(应用)
  11. Associated Objects
  12. Method Swizzling
  13. Method Swizzling的各种姿势
上一篇下一篇

猜你喜欢

热点阅读