美文网首页
[iOS] category / load / initiali

[iOS] category / load / initiali

作者: 木小易Ying | 来源:发表于2020-12-25 18:33 被阅读0次

performSelector

下面会输出神马呢?

- (IBAction)btnAction:(id)sender {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
        [self performSelector:@selector(test) withObject:nil afterDelay:0];
        NSLog(@"2");
    });
}

- (void)test
{
    NSLog(@"3");
}

答案是只打印:1、2
因为[self performSelector:@selector(test) withObject:nil afterDelay:.0]实际在runloop里面,是一个定时器,但是因为在子线程,runloop是默认没有开启的。

实现原理:当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。

(以上内容来自:https://www.jianshu.com/p/9264b4847626


load & initialize

load是加载的时候触发,initialize是第一次使用的时候触发,但是注意哦load是不会自动继承的,但是initialize是会的

+ loadruntime源码 方法执行过程:(本文中所有涉及runtime源码均为 objc4-750.1 版本)

objc_os.mm开始,这里可以理解成runtime的入口方法
1、_objc_init(dyld通知注册镜像,在库初始化之前有libsystem调用)
2、load_images(处理那些正在被dyld映射的镜像文件中的+load方法)
3、prepare_load_methods(将要加载load方法时调用)
4、schedule_class_load(遍历class调用执行 ‘5’)
5、add_class_to_loadable_list(将需要执行+ load方法的class添加到一份全局表loadable_class中)
6、add_category_to_loadable_list( 将需要执行 load 的 category 添加到另一个全局列表loadable_category里)
7、call_load_methods(遍历调用‘8’和‘9’)
8、call_class_loads(调用class的+load方法)
9、call_category_loads(调用category的load方法)

子类的 +load 方法会在它的所有父类的 +load 方法执行之后执行,而分类的 +load 方法会在它的主类的 +load 方法执行之后执行。其实就是被依赖的会先执行load。

如果A是酱紫的:

@implementation ClassA

+ (void)load {
    NSLog(@"class a loaded");
}

+ (void)initialize
{
    NSLog(@"class A initialize");
}

@end

然后B是酱紫的,继承A的:

@interface ClassB : ClassA

@end

@implementation ClassB

+ (void)load {
    NSLog(@"class b loaded");
}

+ (void)initialize
{
    NSLog(@"class B initialize");
}

@end

那么如果现在 alloc init 一个 ClassB 会放生什么呢?结果是酱紫的:

2020-12-25 15:44:55.850725+0800 Example1[591:160086] class a loaded
2020-12-25 15:44:55.851376+0800 Example1[591:160086] class b loaded
2020-12-25 15:44:55.909289+0800 Example1[591:160086] class A initialize
2020-12-25 15:44:55.909342+0800 Example1[591:160086] class B initialize

如果注释掉B的 load 方法就是酱紫的:

2020-12-25 15:46:15.197522+0800 Example1[606:161162] class a loaded
2020-12-25 15:46:15.256616+0800 Example1[606:161162] class A initialize
2020-12-25 15:46:15.256689+0800 Example1[606:161162] class B initialize

如果注释掉B的 initialize 是酱紫的:

2020-12-25 15:47:05.969011+0800 Example1[619:161855] class a loaded
2020-12-25 15:47:05.969682+0800 Example1[619:161855] class b loaded
2020-12-25 15:47:06.033214+0800 Example1[619:161855] class A initialize
2020-12-25 15:47:06.033273+0800 Example1[619:161855] class A initialize

为什么会是酱紫呢?因为B虽然继承了A,但是它自己没有load方法的时候就不会执行load,也不会把A的load拿来执行;但是B如果没有initialize方法的时候,他会找A要一个initialize方法,但是因为B继承了A,在第一次使用B的时候也就第一次使用了A,所以A的initialize会再调用一次,于是A的initialize会调用两次。

如果先初始化A然后再初始化B就会是酱紫的(B仍旧没有自己的initialize):

ClassA *a = [[ClassA alloc] init];
NSLog(@"a and b separator");
ClassB *b = [[ClassB alloc] init];

输出
2020-12-25 15:50:13.567200+0800 Example1[634:163133] class a loaded
2020-12-25 15:50:13.568748+0800 Example1[634:163133] class b loaded
2020-12-25 15:50:13.643834+0800 Example1[634:163133] class A initialize
2020-12-25 15:50:13.643895+0800 Example1[634:163133] a and b separator
2020-12-25 15:50:13.643908+0800 Example1[634:163133] class A initialize
  • 那么如何防止父类的initialize被调用多次呢?
// In Parent.m
+ (void)initialize {
  if (self == [Parent class]) {
    NSLog(@"Initialize Parent, caller Class %@", [self class]);
  }
}

Category & Extensions & AssociatedObject

我们都知道 category 是不能加实例变量的,但是可以增加属性和方法,但是 runtime 提供了一个看起来很神奇的方法class_addIvar(),但它却不能帮助我们给已有的类加实例变量,我们来看看它的定义:

This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.

大概的意思说,这个函数只能在“构建一个类的过程中”调用。当编译类的时候,编译器生成了一个实例变量内存布局 ivar layout,来告诉运行时去那里访问类的实例变量们,一旦完成类定义,就不能再添加成员变量了。经过编译的类在程序启动后就被 runtime 加载,没有机会调用 addIvar。程序在运行时动态构建的类需要在调用 objc_registerClassPair 之后才可以被使用,同样没有机会再添加成员变量。

但是其实我们都知道 category 是可以添加属性的,通过用关联对象来实现 dynamic 的属性~ 那么关联对象是存在哪里的呢?

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // break any existing association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else {
            // setting the association to nil breaks the association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i !=  associations.end()) {
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

所有关联对象其实是由 AssociationsManager 管理的,大概是酱紫:

image.png

AssociationsManager 持有一个map,key是DISGUISE(object),也就是disguised_ptr_t指针,这个指针又会指向一个保存了这个对象所有关联对象的map,这个map的key就是我们传入的key啦,会指向一个value和policy的对。

当对象销毁的时候,也会清空他对应的所有关联对象的:

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

话说为啥policy没有提供 weak 呢?

这个问题其实我没有找到一个答案,我感觉其实加weak的时候同时把引用加到弱引用表就好啦,没啥难度吖,为啥不能直接提供呢。同事表示弱引用表是编译时生成的,如果遍历关联对象找weak太麻烦了,但是我感觉编译时的话可能难度比较大啊,内容应该还是动态添加的叭。


  • Extension 和 Category 的区别?
  1. Extension 在编译期决议,它就是类的一部分,在编译期和头文件里的 @interface 以及实现文件里的 @implement 一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。Extension 一般用来隐藏类的私有信息,你必须有一个类才能为这个类添加 Extension,所以你无法为系统的类比如 NSString 添加 Extension。
  2. Category 则完全不一样,它是在运行期决议的。
  3. Extension 可以添加成员变量,而 Category 一般不可以。

总之,就 Category 和 Extension 的区别来看,Extension 可以添加成员变量,而 Category 是无法添加成员变量的。因为 Category 在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局。


Extension

在swift中,swift可以为特定的class, strut, enum或者protocol添加新的特性。当你没有权限对源代码进行改造的时候,此时可以通过extension来对类型进行扩展。extension有点类似于OC的类别 -- category,但稍微不同的是category有名字,而extension没有名字

swift的extension可以做如下几件事

  • 添加计算属性 - computed properties
  • 添加方法 - methods
  • 添加初始化方法 - initializers
  • 添加附属脚本 - subscripts
  • 添加并使用嵌套类型 - nested types
  • 遵循并实现某一协议 - conform protocol

在swift中,你甚至可以对一个协议protocol进行扩展,实现其协议方法或添加额外的功能,以便于实现该协议的类型可以使用,在swift中,这叫做协议扩展 - protocol extension。但是它不能覆盖已有的特性。例如Animal已经有eat的方法,我们不能使用extension覆盖Animal的eat方法。

不能覆写已有方法

这里其实也能感受到其实extension是编译时定的,而category是运行时的,所以category运行覆写。

reference:
https://www.jianshu.com/p/ccbd2a07db1f
https://www.jianshu.com/p/f5c8d9a043c0
https://www.cnblogs.com/lxlx1798/p/9256643.html
https://www.jianshu.com/p/783df05a9b59

相关文章

网友评论

      本文标题:[iOS] category / load / initiali

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