iOS开发-关于+load和+initialize

最近面试遇到这样的问题,一般来说我们会在+load+initialize进行method swizzle,那么两个方法差异在哪里

执行时机

load

+load方法是在main函数之前被调用,先调用类的+load,再调用分类的+load,且每个+load方法只执行一次。

这里的只执行一次是值得是每个文件中执行一次,因为类要调用+load,分类也要调用+load

+load方法由dyld触发,整个流程如下

  1. _objc_init 主要是_dyld_objc_notify_register(&map_images, load_images, unmap_image);注册3个方法
  2. map_images 读取image
  3. _read_images 依次读取mach-o各个section
  4. reMethodizeClass生成class_rw_t
  5. load_images 调用call_load_methods方法
  6. call_load_methods调用call_class_loads
  7. call_class_loads中直接调用存储的IMP,而非方法调用

子类调用+load时,会先调用父类的+load,但是仅仅都是只会执行一次。

+initialize+load的区别在于,+load只能执行一次,而+initialize会执行多次,为什么呢,因为我们知道+initialize是消息机制触发的,当一个子类中没有+initialize方法时,它自然会执行父类的+initialize方法,而+load方法逻辑是,当该类没有实现+load时,则会跳过!

看下+load的实现:

/***********************************************************************
* add_class_to_loadable_list
* Class cls has just become connected. Schedule it for +load if
* it implements a +load method.
**********************************************************************/
void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method 这里跳过了
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

再看下+initialize实现,是通过objc_msgSend触发的:

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}

initialize

initialize的调用时机则比较延迟,是在对该类发送的第一个消息时,即对该类调用objc_msgSend方法时,就会调用。
例如如下代码:

int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        [LGPerson className];
        LGPerson *objc1 = [[LGPerson alloc] init];
        id __weak objc2 = objc1;
        id __weak objc3 = objc2;
        
        // 对象相等 = hash 

     }
    return 0;
}

通过debug调试,[LGPerson className]执行时就会触发+initailize方法,而不用等到alloc执行

其堆栈如下:

在这里插入图片描述
对于+initialize是线程安全的,当一个线程调用+initialize时,另一个线程调用将会被阻塞,至到+initialize完成,由于阻塞原因,所以其不应该执行太复杂的逻辑,见 官方文档

此外子类如果未实现+initialize则会调用[super initialize].,如果要防止多次调用,可以如下做法:

+ (void)initialize {
  if (self == [ClassName self]) {
    // ... do the initialization ...
  }
}

self 指的是当前的receiver,而[ClassName self]必定是ClassName
对于selfsuper,前者是先从本身类中寻找方法,而后者是从父类中寻找方法。只是寻找方法的起点不一,真实的接收对象receiver还是self,这也是[self class][super class]init方法中输出一致的原因。

+initialize则是在初次使用这个类时调用,那么就有这样的问题。

如下:

Person继承NSObject
Teacher继承Person

当使用[Person new]时无疑会调用Person+initialize,然后,当再使用[Teacher new]时无疑会调用Teacher+initialize吗,答案是不会的。

我们加上日志:

#import "Person.h"

@implementation Person

+ (void)initialize {
    NSLog(@"%@ initialize",[self class]);
}

@end


#import "Teacher.h"

@implementation Teacher
+ (void)initialize {
    NSLog(@"%s initialize",__FUNCTION__);
}
@end

  1. case 1
	Teacher *peo = [Person new];
    NSLog(@"------------------");
    Teacher *mircle = [Teacher new];

这段代码的输出如下:

 +[Person initialize] initialize
 ------------------
 +[Teacher initialize] initialize
  1. case 2
NSLog(@"------------------");
Teacher *mircle = [Teacher new];

输出为:

 ------------------
 +[Person initialize] initialize
 +[Teacher initialize] initialize

Teacher中没有重写+initialize时,会默认调用父类Person+initialize实现。

method swizzle

我们使用+initialize中进行方法交换,避免冷启动时main函数之前的加载过程过长。

代码如下:

#import "Person.h"
#import <objc/runtime.h>

@implementation Person
+ (void)initialize {
    NSLog(@"%s initialize",__FUNCTION__);
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originMethod = class_getInstanceMethod([self class], @selector(hello));
        Method exMethod = class_getInstanceMethod([self class], @selector(hi));
        
        if (class_addMethod([self class], @selector(hello), method_getImplementation(exMethod), method_getTypeEncoding(exMethod))) {
            class_replaceMethod([self class], @selector(hi), method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
        }else{
            method_exchangeImplementations(originMethod, exMethod);
        }
    });
}

- (void)hello {
    NSLog(@"%s hello",__FUNCTION__);
}

- (void)hi {
    NSLog(@"%s hi",__FUNCTION__);
}
@end
#import "Teacher.h"
#import <objc/runtime.h>
@implementation Teacher
+ (void)initialize {
    NSLog(@"%s initialize",__FUNCTION__);
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originMethod = class_getInstanceMethod([self class], @selector(hello));
        Method exMethod = class_getInstanceMethod([self class], @selector(standup));
        
        if (class_addMethod([self class], @selector(hello), method_getImplementation(exMethod), method_getTypeEncoding(exMethod))) {
            class_replaceMethod([self class], @selector(standup), method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
        }else{
            method_exchangeImplementations(originMethod, exMethod);
        }
    });
}

//- (void)hello {
//    NSLog(@"%s hello2",__FUNCTION__);
//}

- (void)standup {
    NSLog(@"%s standup",__FUNCTION__);
}
@end

执行下列语句:

Person *peo = [Person new];
Teacher *mircle = [Teacher new];

[peo hello];
[mircle standup];

结果如下:

 +[Person initialize] initialize
 +[Teacher initialize] initialize
 -[Person hi] hi
 -[Person hi] hi

Teacher本意是交换hellostandup,但是由于父类已经交换了hellohi,所以最终为histandup交换。这也是方法交换不安全的地方。

另外一种情况,当Teacher中有自己的实现hello方法时

#import "Teacher.h"
#import <objc/runtime.h>
@implementation Teacher
+ (void)initialize {
    NSLog(@"%s initialize",__FUNCTION__);
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originMethod = class_getInstanceMethod([self class], @selector(hello));
        Method exMethod = class_getInstanceMethod([self class], @selector(standup));
        
        if (class_addMethod([self class], @selector(hello), method_getImplementation(exMethod), method_getTypeEncoding(exMethod))) {
            class_replaceMethod([self class], @selector(standup), method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
        }else{
            method_exchangeImplementations(originMethod, exMethod);
        }
    });
}

- (void)hello {
    NSLog(@"%s hello2",__FUNCTION__);
}

- (void)standup {
    NSLog(@"%s standup",__FUNCTION__);
}
@end

结果输出如下:

 +[Person initialize] initialize
 +[Teacher initialize] initialize
 -[Person hi] hi
 -[Teacher hello] hello2

确是交换正常了,因为直接从本类method list中交换了实现,没有交换父类那个已经交换过的了。


人生何处不相逢,金樽对玉琼,望君一饮而过,豪情万丈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值