iOS面试资料(二)

iOS面试题

Block相关

Block

  • block本质
    • block是将函数及其执行上下文封装起来的对象

关于block的截获特性,你是否有了解?block的截获变量特性是怎样的?

  • 变量捕获机制分析:

    • 对于“基本数据类型”的“局部变量”截获其值
    • 对于“对象”类型的局部变量“连同所有权修饰符”一起截获
    • 以“指针形式”截获局部静态变量(指针传递)
    • 不截获全局变量、静态全局变量(直接访问)
  • 改外部变量必要条件

    • 将auto从栈copy到堆
      原因:栈中内存管理是由系统管理,出了作用域就会被回收,堆中才是可以由程序员管理

对栈上的block进行copy之后,假如在mrc环境下,内存是否回泄漏?

  • copy操作之后,堆上的block没有额外的成员变量指向它,正如我们alloc对象后,没有进行release,造成内存泄漏

为什么block会产生循环引用?

  • 如果当前block对当前对象的某一成员变量进行截获,block会对当前对象有一个强引用
  • 而当前block由于当前对象对其有一个强引用,产生了一个自循环引用的一个循环引用的问题

Block不允许修改外部变量的值原因

  • block 本质上是一个对象,block 的花括号区域是对象内部的一个函数,变量进入 花括号,实际就是已经进入了另一个函数区域---改变了作用域。

  • 在几个作用域之间进行切换时,如果不加上这样的限制,变量的可维护性将大大降低。

  • 比如想在block内声明了一个与外部同名的变量,此时是允许呢还是不允许呢?只有加上了这样的限制,这样的情景才能实现。

  • 所以 Apple 在编译器层面做了限制,如果在 block 内部试图修改 auto 变量(无修饰符),那么直接编译报错。

  • 可以把编译器的这种行为理解为:对 block 内部捕获到的 auto 变量设置为只读属性---不允许直接修改。

如何实现对外部变量的捕获?

  • 将变量设置为全局变量。原理:block内外可直接访问全局变量
  • 加 static (放在静态存储区/全局初始化区)。原理是block内部对外部auto变量进行指针捕获
  • 最优解:使用__block 关键字

__block

  • 将auto变量封装为结构体(对象),在结构体内部新建一个同名的auto变量
  • block内截获该结构体的指针
  • 在block中使用自动变量时,使用指针指向的结构体中的自动变量
__block int var = 10;
void(^blk)(void) = ^{
    var = 20;
};
blk();

转换后的代码:

struct __Block_byref_var_0 {
    void *__isa;
    __Block_byref_var_0 *__forwarding;
    int __flags;
    int __size;
    int var; // 10 => 20 该结构体持有相当于原来自动变量的成员变量
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_var_0 *var; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_var_0 *_var, int flags=0) : var(_var->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

block在修改NSMutableArray需不需要添加__block

  • 不需要
  • 原因:
    • 当变量是一个指针的时候,block里只是复制了一份这个指针,两个指针指向同一个地址。
    • 所以,在block里面对指针指向内容做的修改,在block外面也一样生效。

block是如何捕获局部变量的?

  • block捕获外界变量时,在内部会自动生成同一个属性来保存

UIView动画中block回调里self要不要弱引用?

  • 不需要,它不会造成循环引用,因为它是类方法。
  • 之所以需要弱引用本身,是因为怕对象之间产生循环引用,当前控制器不可能强引用一个类,所以循环无法形成。

block里面会不会存在self为空的情况(weak strong的原理)?

__weak typeof(self) weakself = self;
[self wj_refresh_addRefreshHeader:^{
    __strong typeof(weakself) strongself = weakself;
    [strongself.dataSource reloadDataWithCompletion:nil];
}];
  • 有时候weakSelf在block里在执行reloadDataWithCompletion还存在
  • 但在执行reloadDataWithCompletion前,可能会被释放了
  • 为了保证self在block执行过程里一直存在,对他强引用strongSelf

__block与__weak的区别

  • __block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型
  • __weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)
  • __block对象可以在block中被重新赋值,__weak不可以。

多层block嵌套如何使用weakSelf?

__weak typeof(self) weakself = self;
[self wj_refresh_addRefreshHeader:^{
    __strong typeof(weakself) strongself = weakself;
    __weak typeof(self) weakSelf2 = strongself;
    [strongself.dataSource reloadDataWithCompletion:^(BOOL result) {
        __strong typeof(self) strongSelf2 = weakSelf2;
    }];
}];

Masonry对于block内部引用self会不会造成循环引用?

  • 不会
  • 这个block没有copy,是在栈上,使用完直接释放了,
- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}

__weak来解决block中的循环引用,还有别的方法吗

  • __block
  • 将对象传进入修改

代理、Block利弊

  • 与委托代理模式的代码相比,用block写出的代码更为整洁

  • 代理优点:

    • 代理语法清晰,可读性高,易于维护
    • 它减少代码耦合性,使事件监听与事件处理分离
    • 一个控制器可以实现多个代理,满足自定义开发需求,灵活性较高
  • 代理缺点:

    • 实现代理的过程较繁琐
    • 跨层传值时加大代码的耦合性,并且程序的层次结构也变得混乱
    • 当多个对象同时传值时不易区分,导致代理易用性大大降低
  • block优点:

    • 语法简洁,代码可读性和维护性较高
    • 配合GCD优秀的解决多线程问题
  • block缺点:

    • Block中得代码将自动进行一次retain操作,容易造成内存泄漏
    • Block内默认引用为强引用,容易造成循环应用
  • 运行成本:

    • delegate运行成本低,block的运行成本高
    • block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是假引用技术,使用完block置nil才会消除
    • delegate只是保存了一个对象的指针,直接回调,没有额外的消耗。就像c的函数指针,只多了一个查表动作

Runtime

Runtime的相关术语

SEL、id、Class、Method、IMP、Cache、Property

介绍下runtime的内存模型(isa、对象、类、metaclass、结构体的存储信息等)

为什么要设计metaclass

class_copyIvarList & class_copyPropertyList区别

class_rw_t 和 class_ro_t 的区别

讲述一下runtime的概念,message send如果寻找不到相应的对象,会如何进行后续处理 ?

  • objc向一个对象发送消息,runtime会根据对象的isa指针找到该对象实际所属类,然后在该类方法列表以及父类的方法列表中查找方法实现,如果向一个nil对象发送消息,在查找isa指针时就是0地址返回,不会出错

交互两个方法的现实有什么风险?

Runloop

  • 什么是RunLoop?
    • RunLoop 实际上是一个对象
    • 这个对象在循环中用来处理程序运行过程中出现的各种事件(比如说触摸事件、UI刷新事件、定时器事件、Selector事件)
    • 从而保持程序的持续运行
    • 在没有事件处理的时候,会使线程进入睡眠模式,从而节省 CPU 资源,提高程序性能
  • 应用范畴
    • 定时器(Timer)、PerformSelect
    • GCD Async Main Queue
    • 事件响应、手势识别、界面刷新
    • 网络请求
    • AutoreleasePool
  • 基本应用
    • 保持程序的持续运行
    • 处理App中的各种事件(比如触摸事件、定时器事件等)
    • 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
  • runloop和线程的关系
    • 每条线程都有唯一的一个与之对应的RunLoop对象
    • RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
    • 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
    • RunLoop会在线程结束时销毁
    • 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
/*
 * 从字典中获取,如果没有则直接创建
 */
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
if (!loop) {
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
    __CFSpinLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    
    if (!loop) {
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
    
    // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
    __CFSpinUnlock(&loopsLock);
    CFRelease(newLoop);
}

NSTimer相关

NSTimer准吗?如果不准的话原因是什么?如何解决?

  • 原因:
    • NSTimer的触发时间到的时候,会在RunLoop中被检测一次;
    • 如果在这一次的RunLoop中做了耗时的操作,会处于阻塞状态
    • 时间超过了定时器的间隔时间,触发时间就会推迟到下一个runloop周期
  • 解决方法:
    • 在子线程中创建timer,在子线程中进行定时任务的操作,需要UI操作时切换回主线程进行操作
    • 使用CADisplayLink(一般用来做UI展示更新,同样存在runloop卡顿问题)
    • 使用GCD定时器

使用NSTimer是如何处理循环引用的?

使用类方法
TODO(待填充);

谈谈常用的三种定时器优缺点(NSTimer、CADisplayLink、GCD定时器)✨✨✨✨✨

  • NSTimer和CADisplayLink依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时
  • 相比之下GCD的定时器会更加准时,因为GCD不是依赖RunLoop,而是由内核决定
  • CADisplayLink和NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用

在viewWillDisappear或者viewDidDisappear方法中将 timer = nil,是否还会造成循环引用?

  • 问题一:如果只是想在离开此页时要释放,进入下一页时不要释放,场景就不适用了
  • runloop->timer;controller->timer

如何利用runloop监控卡顿✨✨✨

NSThread中的Runloop的作用,如何使用

  • runloop是一个线程里运行循环,并对收到的事件进行处理,

  • 保持线程长时间存活

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
[runLoop run];
  • 用于定时器

KVO

KVO基础

KVO 的 全称Key-Value Observing,俗称“键值监听”,可以用于某个对象属性值的改变

iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么)

  • 利用runtimeAPI动态生成一个子类(NSKVONotifying_XXXX),并且让instance对象的isa指向这个全新的子类
  • 当修改instance对象的属性时,会动用Foundation的_NSSetXXXValueAndNotify函数
  • willChangeValueForKey
  • 父类原来的setter方法
  • didChangeValueForKey
  • 内部触发监听器(ObserveValueForKeyPath:ofObject:change:context

如何手动触发KVO?

  • 手动调用willChangeValueForKey
  • 修改成员变量值
  • 手动调用didChangeValueForKey

直接修改成员变量会触发KVO么?

  • 不会触发KVO(原因看KVO的本质)

KVC

KVC基础

KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性

通过KVC修改属性会出发KVO么?

  • 能触发KVO()
  • KVC在修改属性时,会调用willChangeValueForKey:和didChangeValueForKey:方法

KVC的赋值和取值过程是怎样的?

  • 赋值过程
    • 首先会按照setKey、_setKey的顺序查找方法,找到方法,直接调用方法并赋值;
    • 未找到方法,则调用+ (BOOL)accessInstanceVariablesDirectly;
    • 若accessInstanceVariablesDirectly方法返回YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到直接赋值,找不到则抛出异常;
    • 若accessInstanceVariablesDirectly方法返回NO,则直接抛出异常;
  • 取值过程
    • 首先会按照getKey、key、isKey、_key的顺序查找方法,找到直接调用取值
    • 若未找到,则查看+ (BOOL)accessInstanceVariablesDirectly的返回值,若返回NO,则直接抛出异常;
    • 若返回的YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到则取值;
    • 找不到则抛出异常;

使用场景

  • 单层字典模型转化:
[self.model setValuesForKeysWithDictionary:dict];
  • 通过KVC修改未暴露的属性:
UILabel *placeholderLabel=[self.userTextField valueForKeyPath:@"placeholderLabel"];
placeholderLabel.textColor = [UIColor redColor];
  • 使用valueForKeyPath可以获取数组中的最小值、最大值、平均值、求和
CGFloat sum = [[array valueForKeyPath:@"@sum.floatValue"] floatValue];
CGFloat avg = [[array valueForKeyPath:@"@avg.floatValue"] floatValue];
CGFloat max =[[array valueForKeyPath:@"@max.floatValue"] floatValue];
CGFloat min =[[array valueForKeyPath:@"@min.floatValue"] floatValue];
  • 数组内部去重
[dataArray valueForKeyPath:@"@distinctUnionOfObjects.self"]
  • 数组合并(去重合并:distinctUnionOfArrays.self、直接合并:unionOfArrays.self)
NSArray *temp1 = @[@3, @2, @2, @1];
NSArray *temp2 = @[@3, @4, @5];
NSLog(@"\n%@",[@[temp1, temp2] valueForKeyPath:@"@distinctUnionOfArrays.self"]);
NSLog(@"\n%@",[@[temp1, temp2] valueForKeyPath:@"@unionOfArrays.self"]);

输出两个数组:( 5, 1, 2, 3, 4 ), ( 3, 2, 2, 1, 3, 4, 5 )。

  • 大小写转换(uppercaseString)及 打印字符串长度同样适用(length)
NSArray *array = @[@"name", @"w", @"aa", @"jimsa"];
NSLog(@"%@", [array valueForKeyPath:@"uppercaseString"]);
打印:
(NAME,W,AA,JIMSA)

简单介绍一下KVC和KVO,他们都可以应用在哪些场景?

  • KVO:键值监听,观察某一属性的方法
  • KVC:键值编码,间接访问对象的属性

Category

Category相关

Category的使用场合

  • 将一个类拆成很多模块(其实就是解耦,将相关的功能放到一起)

Category的实现原理

  • 通过runtime动态将分类的方法合并到类对象、元类对象中
  • Category编译之后的底层结构是 struct_category_t , 里面存储着分类的对象方法、类方法、属性、协议信息
  • 在程序运行的时候,runtime会将 Category 的数据,合并到类信息中(类对象、元类对象)

category和extension区别

  • Extension在编译时,就把信息合并到类信息中
  • Category是在运行时,才会将分类信息合并到类信息中
  • 分类声明的属性,只会生成getter/setter方法声明,不会自动生成成员变量和getter/setter方法实现,而扩展会
  • 分类不可用为类添加实例变量,而扩展可以

分类的局限性

  • 无法为类添加实例变量,但可通过关联对象进行实现
  • 分类的方法如果和类重名,会覆盖原来方法的实现
  • 多个分类的方法重名,会调用最后编译的那个分类的实现

为什么category不能添加属性?使用Runtime就可以了?

  • 分类没有自己的isa指针
  • 类最开始生成了很多基本属性,比如IvarList,MethodList
  • 分类只会将自己的method attach到主类,并不会影响到主类的IvarList
  • 实例变量没有setter和getter方法。也没有自己的isa指针
  • 关联对象都由AssociationsManager管理
  • AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。
  • 相当于把所有对象的关联对象都存在一个全局map里面。而map的的key是这个对象的指针地址
  • 而这个map的value又是另外一个AssAssociationsHashMap,里面保存了关联对象的kv对

Category中有load方法么?load方法什么时候调用的?load方法能继承么?

  • +load方法会在runtime加载类、分类时调用;

  • 每个类、分类的+load,在程序运行过程中只调用一次

  • 调用顺序

  • 先调用类的+load,(按照编译先后顺序,先编译,先调用),调用子类的+load之前会调用父类的+load

  • 再调用分类的+load按照编译先后顺序调用(先编译,先调用)

test方法和load方法的本质区别?(+load方法为什么不会被覆盖)

  • test方法是通过消息机制调用 objc_msgSend([MJPerson class], @selector(test))
  • +load方法调用,直接找到内存中的地址,进行方法调用

load调用顺序

  • +load方法会在runtime加载类、分类时调用
  • 每个类、分类的+load,在程序运行过程中只调用一次
  • 调用顺序
    • 先调用类的+load方法,之后按照编译先后顺序调用(先编译,先调用,调用子类的+load之前会先调用父类的+load)
    • 再调用分类的+load,之后按照编译先后顺序调用(先编译,先调用)

不同Category中存在同一个方法,会执行哪个方法?如果是两个都执行,执行顺序是什么样的?

  • 根据Build Phases->Compile Sources中添加的文件顺序,后面的会覆盖前面的

load、initialize方法的区别是什么?它们在category中的调用顺序?以及出现继承时他们之间的调用过程?🌟🌟🌟🌟🌟

区别:

  • 调用方式不同
    • load是根据函数地址直接调用
    • initialize是通过objc_msgSend调用
  • 调用时刻
    • load是runtime加载 类/分类 的时候调用(只会调用1次)
    • initialize是类第一次接收消息时调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
  • 调用顺序
    • load:先调用类的load。先编译的类,优先调用load(调用子类的load之前,会先调用父类的load)
    • 再调用分类的load(先编译的分类,优先调用load)
    • initialize:先初始化父类, 再初始化子类(可能最终调用的是父类的initialize方法)

分类中方法替换

  • category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA
  • category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面
  • 这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休_,殊不知后面可能还有一样名字的方法。

为什么不能动态添加成员变量?

  • 方法和属性并不“属于”类实例,而成员变量“属于”类实例
  • “类实例”概念,指的是一块内存区域,包含了isa指针和所有的成员变量。
  • 假如允许动态修改类成员变量布局,已经创建出的类实例就不符合类定义了,变成了无效对象。但方法定义是在objc_class中管理的,不管如何增删类方法,都不影响类实例的内存布局,已经创建出的类实例仍然可正常使用
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容