• iOS大厂面试查漏补缺


    一、UI方面

    1. UI卡顿问题
    1. 原因
      • 屏幕垂直信号之间是一帧率,16.7ms
      • CPU计算,GPU渲染,这两步要在一帧的时间完成。
      • 如果没完成,就会出现卡顿,掉帧情况
    2. 优化方案
      • CPU层面:
      • 尽量用更加轻量级的对象,比如不需要处理事件的视图,可以用CALayer代替UIView
      • 图片的解码,可以在子线程用CGBitmapContext绘制,然后从Bitmap中创建图片
      • 尽量不要对UIView频繁调用属性修改,frame,bounds,transform等修改
      • 尽量提前计算好布局,在有需要的时候一次性调整对应的属性
      • Autolayout会比直接设置frame消耗更多的CPU资源,所以尽量直接设置frame
      • 图片的size最好刚好跟UIImageView的size保持一致
      • 控制线程的最大并发数量
      • 对象创建、及时销毁
      • 文本等预排版(boudingRectWithSize && drawWithRect 这两个方法)
      • GPU层面:
      • 避免离屏渲染(避免设置圆角带来离屏渲染:贝塞尔曲线绘制)
      • 减少视图层级(Texture/ASDK 就是通过pre-composing技术,提前把多个sub-layer合成渲染为一张图片,减少视图层级,大大提高渲染效率)
    2. 聊聊WebView?
    • 使用
      1. 设置uiDelegate和navigationDelegate
      1. 设置config
      • 其中config里面,设置userContentController的时候,注意添加JS脚本监听的时候,防止循环引用
      • 通过创建中间类弱引用,让WKScripMessageHandler类设置为弱类型。
      1. 实现代理事件
      • uiDelegate: 处理JS脚本,确认框,警告等
      • navigationDelegate: 处理跳转,加载页面等
      • decisionHandler方法一定要实现,否则点击跳转链接就会崩溃
    3. 事件响应链是如何传递的?

    事件传递 & 事件响应

    • 事件传递: 从UIApplication到UIWindow,再顺着子视图往下找,根据两个方法
      • pointInSide: withEvent
      • hitTest: withEvent
    • 事件响应
      - 从传递最后确认的视图开始,验证是否能处理响应事件,如果不能就顺着视图往上找父视图,如果可以处理,就处理了,和事件传递大致相反,根据判断方法
      • touchesBegan: withEvent
      • touchesMoved: withEvent
    4. layoutsubviews是在什么时机调用的?
    • init初始化是不会触发
    • addSubview会触发
    • 设置frame且前后值发生变化,frame为zero且不添加到指定视图,不会触发
    • 旋转screen会触发父视图的layoutSubviews
    • 滚动UIScrollView引起View重新布局时候会触发layoutSubviews
    5. sizeThatFits 和 sizeToFit的区别
    • UIView如果设置过autolayout约束,则该sizeToFit方法失效
    • 调用sizeToFit会自动调用sizeThatFits
    • 自定义View的时候,不应该重写sizeToFit方法,应该重写sizeThatFits,在合适的地方只需要调用sizeToFit方法,就会自动触发sizeThatFits方法
    • sizeToFit可以改变UIView的frame,sizeThatFit不会改变UIView的frame
    • 它们都没有递归,不管subviews,只管自己

    二、网络方面

    1. 网络请求缓存问题?
      • 空间:URLCache是苹果提供的网络请求缓存类,主要用于GET方法,请求资源用的。默认内存是512K,磁盘10M,也可以自己修改
      • 策略:6种,使用缓存,不使用缓存,实现,不实现等等。
      • 更新缓存内容:
      • Last-Modified:服务器会在资源后面有个标记Last-Modified,表示最后修改时间。第一次请求会缓存这个时间,后面请求时候,发送一个If-Modified-Since报头。如果返回304就不会传输资源数据,用本地缓存的即可。否则会传输资源。
      • Etag:服务器对资源实体用Etag来标记,就是对实体进行散列计算,得到唯一值。第二次请求,对这个值进行比较。如果返回304就数据为空,和上述情况一样。

    三、安全方面

    1. Mach-O
      • Mach Object的缩写,是mac及iOS上可执行文件的格式,不一定是可执行,只是一种文件格式。
      • 包含类型:
        1. OBJECT:.o.a 文件(目标文件)
        1. EXECUTE:指的是IPA拆包后的文件(可执行文件)
        1. DYLIB:指的是.dylib.framework (动态库文件)
        1. DYLINKER:指的是动态连接器(动态库连接器文件)
        1. DSYM:(符号表),指的是有保存符号信息用于分析闪退信息的文件(符号表文件)
      • 加载过程
        1. 把可执行文件加载到内存中
        1. 从可执行文件中分析dyld的路径
        1. dyld 加载到内存中
        1. dyld 递归加载所有的动态连接库dylib
    2. ldid重签名
        1. ldid -e 命令导出可执行文件的签名文件entitlements
        1. 用越狱了的手机,导出SpringBoard的签名文件entitlements
        1. SpringBoard的签名文件覆盖自己的APP的签名文件entitlements
    3. 怎么防止应用crash?不用bugly记录crash?

    四、底层原理

    1. OC为什么要设计Metaclass?
      • 这个是要从面向对象设计的哲学去考虑这个问题
      • 第一:提取出某一种群体的特性,对这类群体进行划分,从而有基本的属性成员信息方法信息
        第二:可以复用消息传递,在OC的消息传递过程中,只要是这个群体的实体,就不需要关心自身的类型是什么,只需要关心这个接口。在传递消息的时候就可以复用了。
        第三:对于描述这个类的类,即元类,对于类的类方法而言,就类似实例的实例方法,也需要这么一个复用消息传递机制,所以也就产生了针对类的元类,本质上来说,我认为也是消息传递的复用工具
    2. 类可以调用对象方法吗?
      • 可以,因为基类的super-class指针,指向的是类。
    3. isa的指针指向?
        1. 实例对象的isa指向类对象,类对象的isa指向元类
        1. isa指向,在不同平台(arm64 或 x86)通过掩码计算,来得到真实的地址值。
    4. KVO? 键值监听
        1. 使用:
          [self.person addObserver:self forKeyPath:@"age" options:options context:nil];
        1. 本质:
          第一:NSKVONotifying_Person 继承 Person
          第二:在set方法里,增加了willChangedidChange方法
          第三:联想到在swift 里,我们可以写属性监听器
          第四:从runtime的角度也可以验证,通过调用methodForSelector方法,查看实际上调用的方法是什么
          第五:从runtime调用class_copyMethodList方法,也可以获取添加监听后的类的所有的方法列表,打印就会发现与添加之前不一样了。
          第六:从runtime调用object_getClass获取类名,打印也会发现与添加之前不一样
        1. 手动触发KVO?
          第一:只需要手动在合适的地方添加willChangedidChange方法
          第二:通过-> 方式赋值,不会调用set方法,不会触发KVO
    5. KVC 键值编码
        1. 使用
          [self.person setValue:@10 forKey:@"age"];
          [self.person setValue:@20 forKeyPath:@"cat.weight"];
        1. 原理
          第一:赋值
          setKey
          _setKey
          _key
          _isKey
          key
          isKey
          第二:取值
          getKey
          Key
          isKey
          _key
        1. 所以,KVC是可以触发KVO的,因为会调用set方法
    6. Category的理解?
    1. 分类的使用场景:一般业务比较复杂,需要将一部分功能或业务逻辑分模块出来,就可以用分类

    2. Category的本质

      • 通过runtime加载某个类的所有Category数据
      • 把所有的Category的方法、属性、协议,的数据,合并到一个大数组中
        • 后编译的Category数据,会在数组的前面,所以后编译的Category会被先调用
        • 如果分类中含有和原类的类方法同名的,就会先调用分类的方法
        • 通过runtime关联属性
          • objc_setAssociatedObject
          • objc_getAssociatedObject
          • 关联对象并不是存储在被关联的对象中,而是存储在全局统一的AssociationsManager中
          • 设置关联对象为nil,就是相当于移除关联对象
    3. Category和Extension的区别(OC里)

    • 区别一
      • extension在编译期决议,它是类的一部分,在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。
      • extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension。
    • 区别二
      • 但是category则完全不一样,它是在运行期决议的。 就category和extension的区别来看,我们可以推导出一个明显的事实,extension可以添加实例变量,而category是无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。
    • 区别三
      • 会在APP冷启动第二阶段调用,category附加到类的工作会先于+load方法的执行。
    1. Runtime给Category动态添加方法、属性
    • 关联对象都由AssociationsManager管理。AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。
    • 而map的的key是这个对象的指针地址(任意两个不同对象的指针地址一定是不同的),而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的kv对。
    • runtime的销毁对象函数objc_destructInstance里面会判断这个对象有没有关联对象,如果有,会调用_object_remove_assocations做关联对象的清理工作
    1. 调用被覆盖的方法?
    • 通过class_copyMethodList,把所有的方法列表获取到。
    • 然后for循环,把最后一个方法的名字进行匹配。
    Class currentClass = [TestClass class];
    TestClass *my = [[TestClass alloc] init];
    
    if (currentClass) {
        unsigned int methodCount;
        Method *methodList = class_copyMethodList(currentClass, &methodCount);
        IMP lastImp = NULL;
        SEL lastSel = NULL;
        for (NSInteger i = 0; i < methodCount; i++) {
            Method method = methodList[i];
            NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method)) 
                                            encoding:NSUTF8StringEncoding];
            if ([@"printName" isEqualToString:methodName]) {
                lastImp = method_getImplementation(method);
                lastSel = method_getName(method);
            }
        }
        typedef void (*fn)(id,SEL);
    
        if (lastImp != NULL) {
            fn f = (fn)lastImp;
            f(my, lastSel);
        }
        free(methodList);
    }   
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    1. class的结构
    在oc中打开objc.h
    
    typedef struct objc_class *Class;     //Class是指向结构体objc_class的指针
    
    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY; //isa,代表的是该类类对象
    
    #if !__OBJC2__
        Class _Nullable super_class                              OBJC2_UNAVAILABLE; //父类
        const char * _Nonnull name                               OBJC2_UNAVAILABLE; //类名
        long version                                             OBJC2_UNAVAILABLE; 
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE; //对象大小
        struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE; //成员变量列表
        struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE; //实例方法列表
        struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE; //方法缓存列表(是个hash表),用来消息发送时候,快速查找方法
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE; //类实现协议列表
    #endif
    
    } OBJC2_UNAVAILABLE;
    /* Use `Class` instead of `struct objc_class *` */
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    7. load、initialize方法
      • 调用顺序
      • load
        • 先调用类的load(先编译的先调用),再调用分类的load(也是先编译的先调用)。
        • 先调用父类,再调用子类。
      • initialize
        • 先看父类有没有实现,再调用子类。
        • +initialize 方法的调用与普通方法的调用是一样的,走的都是消息发送的流程。
        • 如果子类没有实现 +initialize 方法,那么继承自父类的实现会被调用;
        • 如果一个类的分类实现了 +initialize 方法,那么就会对这个类中的实现造成覆盖。
      • 调用方式
        • load是在系统加载的时候,根据函数地址直接调用的
        • initialize是通过消息发送调用
      • 调用时间
        • load是runtime加载类、分类的时候调用,只会调用一次(在APP冷启动的第二阶段就会调用)
        • initialize是类在第一次接收到消息的时候调用,每个类只会initialize一次。但是父类的initialize方法可能会被调用多次。
    	// JHPerson 和 JHTeacher 都是 Person 的子类,并且只有Person这个父类实现了+initialize方法
    - (void)testInit {
       NSLog(@"--------");
        [JHPerson alloc];
        [JHPerson alloc];
        [JHTeacher alloc];
        [JHTeacher alloc];
    }
    
    	// 伪代码逻辑
        if (JHPerson没有初始化) {
     	if (JHPerson的父类Person没有初始化) {
    			objc_msgSend([Person class], @selector(initialize)); // 第一次调用父类
    		}
    		objc_msgSend([JHPerson class],@selector(initialize)); // 调用子类,子类没实现,去找到父类调用,第二次调用父类
        }
     	
        if (JHTeacher没有初始化) {
     	if (JHTeacher的父类Person没有初始化) { // 父类已经初始化了,不会进来
    		   objc_msgSend([Person class], @selector(initialize));
    		}
    		objc_msgSend([JHTeacher class],@selector(initialize)); // 调用子类,子类没实现,去找到父类调用,第三次调用父类
         }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    8. 聊聊block
      1. block的本质是一个OC对象
        NSGloabalBlock --> NSBlock --> NSObject
      1. block类型问题
      • GloabalBlock – 没有访问auto变量 – 程序的数据区域
      • NSStackBlock – 访问了auto变量 – 栈里面
      • NSMallocBlock – NSStackBlock复制 – 堆里面
      • NSMallockBlock复制,引用计数+1
        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PeWg5rcG-1659199501480)(:/30c14379e1d04de58fd6b16fbc2fb6dd)]
      1. 变量捕获
        1. 局部变量会被捕获,全局变量不会被捕获
        1. static修饰的或者是__block修饰的,是指针传递
        1. 默认没有修饰,(即auto)是值传递
      1. 下划线_block修饰后的对象,是一个结构体
      struct __Block_byref_age_0 {
      	void *__isa;
      	__Block_byref_age_0 *__forwarding; // 这个指针指向自己
      	int __flags;
      	int __size;
      	int age;
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • forwarding为什么指向自己?如图,保证当从栈复制到堆时候,也能正确指向属性的指针地址
        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dm6wIdsq-1659199501482)(:/0f99685a42514e41ad6c3e4a2797c715)]
      1. 循环引用
        1. ARC下
        • 用__weak一般是最佳方案,因为安全,对象销毁指针指向nil
        • 用_unsafe_unretain,不安全,对象销毁,指针地址不变
        • __block 方案,个人取个名字叫过河拆桥法,方便记忆。
          1. 首先__block修饰后,变为一个结构体。
          1. 结构体里面有强指针指向实例对象__block实例对象产生了强引用
          1. 实例对象调用block对象实例对象block产生了强引用
          1. block再访问了__block对象的内容,block__block产生了强引用,如图:
    // 1. __block修饰的那部分:__block Person *person = [[Person alloc] init];
        struct __Block_byref_person_0 {
      	void *__isa;
    		__Block_byref_person_0 *__forwarding;
     	int __flags;
     	int __size;
     	void (*__Block_byref_id_object_copy)(void*, void*);
    		void (*__Block_byref_id_object_dispose)(void*);
     	Person *__strong person; // 后面使用的person指针,都是这个指针
    	};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iVMOYnk5-1659199501506)(:/1a2c46f22eaf41ba96794a004f9651d9)]
    - 5. 现在我要过河拆桥,我调用block的时候,通过__block访问到了我想要访问的实例对象的属性(比如,这里我的目的就是访问person.age),那么我的目的已经完成了,所以,我不再需要让__block对象再去强引用实例对象了。

    // 1. __block修饰的那部分:__block Person *person = [[Person alloc] init];
        struct __Block_byref_person_0 {
      	void *__isa;
    		__Block_byref_person_0 *__forwarding;
     	int __flags;
     	int __size;
     	void (*__Block_byref_id_object_copy)(void*, void*);
    		void (*__Block_byref_id_object_dispose)(void*);
     	Person *__strong person = nil; // 在这里来过河拆桥,把它变为nil
     	
    	};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

      1. MRC
      • 1.因为MRC不支持弱引用的情况,所以可以使用__unsafe_unretained
      • 2.使用__block解决
        因为__block不会对对象产生强引用
    9. 聊聊Runloop

    runloop链接

    10. 聊聊Runtime

    runtime链接

    11. 聊聊锁
    1. 自旋锁、互斥锁比较
    • 简单总结:
      • 单核的、复杂的操作,用互斥锁
      • 多核的、简单的操作,用自旋锁
    • 自旋锁:
      • 是一种用于保护多线程共享资源的锁,与一般互斥锁(mutex)不同之处在于当自旋锁尝试获取锁时以忙等待(busy waiting)的形式不断地循环检查锁是否可用。当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会一直等待(不会睡眠),当上一个线程的任务执行完毕,下一个线程会立即执行。
        在多 CPU 的环境中,对持有锁较短的进程来说,使用自旋锁代替一般的互斥锁往往能够提高进程的性能。
    • 互斥锁:
      • 当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会进入睡眠状态等待任务执行完毕,当上一个线程的任务执行完毕,下一个线程会自动唤醒然后执行任务。
    1. @synchronized 的原理
    • 第一步
    @synchronized(obj) {
        // do work
    }
    转化成这样的东东:
    @try {
        objc_sync_enter(obj);
        // do work
    } @finally {
        objc_sync_exit(obj);    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 第二步:objc_sync_enter 和 objc_sync_exit 是什么鬼?它们是如何实现的?
    // Begin synchronizing on 'obj'. 
    // Allocates recursive mutex associated with 'obj' if needed.
    // Returns OBJC_SYNC_SUCCESS once lock is acquired.  
    // 开始在obj上执行同步操作, 懒加载生成一个递归锁关联obj, 返回OBJC_SYNC_SUCCESS
    int objc_sync_enter(id obj)
    {
        int result = OBJC_SYNC_SUCCESS;
     
        if (obj) {
            // 查找这个obj是否已经生成SyncData,如果没有生成一个
            SyncData* data = id2data(obj, ACQUIRE); 
            assert(data);
            data->mutex.lock(); // 调用SyncData的递归锁加锁
        } else {
            // @synchronized(nil) does nothing
            // 如果传入nil, 打印了一个log,然后什么都不做
            if (DebugNilSync) {
                _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
            }
            objc_sync_nil();
        }
     
        return result;
    }
     
     
    // End synchronizing on 'obj'. 
    // Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
    // 结束在obj上的同步操作, 
    int objc_sync_exit(id obj)
    {
        int result = OBJC_SYNC_SUCCESS;
        
        if (obj) {
            //还是找到这个对象所在的结构体SyncData
            SyncData* data = id2data(obj, RELEASE); 
            if (!data) {
                // 如果这个结构体在block执行过程中找不到了,会返回error
                result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
            } else {
                // 尝试解锁,解锁失败也会返回error
                bool okay = data->mutex.tryUnlock();
                if (!okay) {
                    result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
                }
            }
        } else {
            
            // @synchronized(nil) does nothing
            // 如果这个对象在block执行过程中变成nil了,会什么都不做
        }
    	
     
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    1. 第三步:data->mutex.lock(); // 调用SyncData的递归锁加锁 怎么调?
    typedef struct SyncData {
        id object;
        recursive_mutex_t mutex;
        struct SyncData* nextData;
        int threadCount;
    } SyncData;
     
    typedef struct SyncList {
        SyncData *data;
        spinlock_t lock;
    } SyncList;
     
    // Use multiple parallel lists to decrease contention among unrelated objects.
    #define COUNT 16
    #define HASH(obj) ((((uintptr_t)(obj)) >> 5) & (COUNT - 1))
    #define LOCK_FOR_OBJ(obj) sDataLists[HASH(obj)].lock
    #define LIST_FOR_OBJ(obj) sDataLists[HASH(obj)].data
    static SyncList sDataLists[COUNT];
     
     
    ----------
    SyncData中的recursive_mutex_t最终是recursive_mutex_tt类型,
    recursive_mutex_tt内部有个pthread_mutex_t的锁,
    这个锁初始化为一个递归锁 PTHREAD_RECURSIVE_MUTEX_INITIALIZER
    ----------
     
    class recursive_mutex_tt : nocopy_t {
        pthread_mutex_t mLock;
     
      public:
        recursive_mutex_tt() : mLock(PTHREAD_RECURSIVE_MUTEX_INITIALIZER) {
            lockdebug_remember_recursive_mutex(this);
        }
     
        recursive_mutex_tt(const fork_unsafe_lock_t unsafe)
            : mLock(PTHREAD_RECURSIVE_MUTEX_INITIALIZER)
        { }
     
        void lock()
        {
            lockdebug_recursive_mutex_lock(this);
     
            int err = pthread_mutex_lock(&mLock);
            if (err) _objc_fatal("pthread_mutex_lock failed (%d)", err);
        }
     
        void unlock()
        {
            lockdebug_recursive_mutex_unlock(this);
     
            int err = pthread_mutex_unlock(&mLock);
            if (err) _objc_fatal("pthread_mutex_unlock failed (%d)", err);
        }
        ..... 其他方法
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    1. 其他问题
    • 锁是如何与你传入 @synchronized 的对象关联上的?
      • 你调用 sychronized 的每个对象,Objective-C runtime 都会为其分配一个递归锁并存储在哈希表中。
    • @synchronized会保持(retain,增加引用计数)被锁住的对象么?
      • ARC下会引用计数+1,MRC下不会
    • 如果传入@synchronized 的对象值为 nil 将会怎么样?
      • @synchronized(nil)不会有任何作用,hash计算为空,加锁失败,代码块不是线程安全的。你可以通过在 objc_sync_nil 上加断点来查看是否发生了这样的事情。
    • 假如传入 @synchronized 的对象在 @synchronized 的 block 里面被释放或者被赋值为 nil 将会怎么样?
      • 如果在 sychronized 内部对象被释放或被设为 nil 看起来都 OK。不过这没在文档中说明,所以我不会再生产代码中依赖这条。
    12. 聊聊多线程

    多线程链接

    五、聊聊数据持久化

    • 本地存储的几种方式
    • 数据库(sqlite、coredate、FMDB、WCDB)

    六、聊聊路由

    组件化链接

    • Beehive
    • CTMeditor

    七、聊聊Swift和OC的区别?

    7.1 struct & class
    • struct是值引用,更轻量,存放于栈,不可继承
    • class是指针引用(或称类型引用),存放于堆区,可以继承
    7.2 swift的派发机制
    • 直接派发
      • 是在struct、enum中,速度快,但是不能继承
    • 函数表派发
      • swift里函数表叫Witness Table ,类会有一个数组存储里面的函数指针。
      • 一个对象的内存地址前8位存储类的类型信息,类型信息就包括了函数指针。
      • 后8位信息是引用计数
    • 消息机制派发
      • 消息派发是在运行时,可以改变函数的行为,KVO和CoreData都是对这种机制的运用。OC默认使用消息派发,C语言使用的是直接派发。
        • 当一个消息被派发,程序运行时就会按照继承关系向上查找被调用的函数,但是效率不高,所以会通过缓存来提高效率,性能和函数表派发差不多。
    • 场景
      • 直接派发
        • 值类型
        • final
        • @inline
        • class和协议的extension
      • 函数表派发:
        • class和协议的初始化声明
      • 消息机制派发:
        • class和@objc extension
        • dynamic:可以让类里面的函数使用消息机制派发,可以重载extension里的函数
        • @nonobjc: 来禁止消息机制派发这个函数
    7.3 对协议的理解?
    • 解决面向对象菱形继承问题
  • 相关阅读:
    软硬件架构分层总结
    小心这几个职场中容易触碰的规则
    .NET DataGridView数据绑定说明
    电子行业ERP解决方案
    手写 Promise(1)核心功能的实现
    Vue介绍&如何安装vue&Vue生命周期钩子&MVVM
    Go中的工作池:并发任务的优雅管理
    Java基础之正则表达式
    线程重用问题--ThreadLocal数据错乱
    【Java基础】Java8新特性
  • 原文地址:https://blog.csdn.net/JH_Cao/article/details/126080066