[iOS] category / load / initialize / performSelector

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,539评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,594评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,871评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,963评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,984评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,763评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,468评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,850评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,002评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,144评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,823评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,483评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,026评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,150评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,415评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,092评论 2 355

推荐阅读更多精彩内容