1. 多线程的实现方式;

iOS中实现多线程的方案有4种

2. 延迟执行的几种方式;

  • 延迟1秒执行代码
1
2
3
-(void)delayMethod{
NSLog(@"delayMethodEnd");
}
  • performSelector方法:
1
2
3
// 此方式要求必须在主线程中执行,否则无效。
// 是一种非阻塞的执行方式,暂时未找到取消执行的方法。
[self performSelector:@selector(delayMethod) withObject:nil afterDelay:1.0f];
  • 定时器:NSTimer;
1
2
3
// 此方式要求必须在主线程中执行,否则无效。
// 是一种非阻塞的执行方式,可以通过`NSTimer`类的`- (void)invalidate;`取消执行。
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(delayMethod) userInfo:nil repeats:NO];
  • sleep方式;
1
2
3
// 此方式在主线程和子线程中均执行。
// 是一种阻塞的执行方式,`建议放到子线程中,以避免卡住界面`,没有找到取消执行的方法。
[NSThread sleepForTimeInterval:1.0f]; [self delayMethod];
  • GCD方式
1
2
3
4
5
6
7
// 此方式可以在参数中选择执行的线程。
// 是一种非阻塞的执行方式,
// 没有找到取消执行的方式。
__weak id safeSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[safeSelf delayMethod];
});

3. App崩溃的情况有多少种;

1. 违反操作系统规则:比如启动恢复挂起退出时`watchdog`超时,用户强制退出或者低内存终止。
2. 应用程序异常退出。

4. 你使用过Objective-C的运行时编程(Runtime Programming)么?如果使用过,你用它做了什么?你还能记得你所使用的相关的头文件或者某些方法的名称吗?

参考答案:

Objecitve-C的重要特性是Runtime(运行时),在#import <objc/runtime.h> 下能看到相关的方法,用过objc_getClass()class_copyMethodList()获取过私有API;

使用Objecitve-C
Method method1 = class_getInstanceMethod(cls, sel1);
Method method2 = class_getInstanceMethod(cls, sel2);
method_exchangeImplementations(method1, method2);

代码交换两个方法,在写unit test时使用到。

5. 对于Objective-C,你认为它最大的优点和最大的不足是什么?对于不足之处,现在有没有可用的方法绕过这些不足来实现需求。如果可以的话,你有没有考虑或者实践过重新实现OC的一些功能,如果有,具体会如何做?

参考答案:

最大的优点是它的运行时特性,不足是没有命名空间,对于命名冲突,可以使用长命名法或特殊前缀解决,如果是引入的第三方库之间的命名冲突,可以使用link命令及flag解决冲突。

6. iOS中的几种传值问题?

  • 顺传一般是直接传值

  • 代理传值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    A<B-Delegate> // A实现B协议
    B // 声明协议和方法 声明代理属性
    Delegate->(methodFromB:(B)b value:(obj)obj)// B声明协议方法
    property-weak-deleagte

    // 在A中
    B.delegate = A;

    // 当B中发生传值时调用
    B
    [self.delegate methodFromB:self value:obj]

    // 因为 self.delegate = A
    // A中执行定义的协议方法接收到值
    -methodFromB:(B)b value:(obj)obj
  • Block传值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    A
    // A中实现B的block的代码块
    B.block = ^(obj){

    };

    B // 声明代码块
    typedef BBlock
    property-block

    // 当B中调用
    self.block(obj);
    就会调用A中以实现的代码块实现传值
  • 通知传值

1
2
3
4
5
6
7
A中接收通知
NotificationCenter.addobserve(A).name("NAME").selector(noti:)
-noti:(noti)noti{
noti.obj// 接收到值
}
B中发送通知
NotificationCenter.postName("NAME").obj(obj)
  • 单例传值
1
2
3
4
// 用单例中的值更新A的值
A.property = global.singleton.property;
// B中值改变 把值赋给单例
global.singleton.property = B.property;

7. 什么是 ARC? (ARC 是为了解决什么问题而诞生的?)

ARCAutomatic Reference Counting 的缩写, 即自动引用计数. 这是苹果在 iOS5 中引入的内存管理机制. Objective-CSwift 使用 ARC 追踪和管理应用的内存使用. 这一机制使得开发者无需键入 retainrelease
, 这不仅能够降低程序崩溃和内存泄露的风险, 而且可以减少开发者的工作量, 能够大幅度提升程序的流畅性可预测性. 但是 ARC 不适用于 Core Foundation 框架中, 仍然需要手动管理内存.

8. 以下 keywords 有什么区别: assign vs weak , __block vs __weak

assignweak是用于在声明属性时, 为属性指定内存管理的语义.

  • assign 用于简单的赋值, 不改变属性的引用计数, 用于 Objective-C 中的 NSInteger , CGFloat 以及 C 语言中 int , float , double 等数据类型.
  • weak 用于对象类型, 由于 weak 同样不改变对象的引用计数且不持有对象实例, 当该对象废弃时, 该弱引用自动失效并且被赋值为 nil , 所以它可以用于避免两个强引用产生的循环引用导致内存无法释放的问题.

__block__weak 之间的却是确实极大的, 不过它们都用于修饰变量.

  • 前者用于指明当前声明的变量在被 block 捕获之后, 可以在 block 中改变变量的值. 因为在 block 声明的同时会截获该 block 所使用的全部自动变量的值, 而这些值只在 block 中只具有”使用权”而不具有”修改权”. 而 __block 说明符就为 block 提供了变量的修改权.
  • 后者是所有权修饰符, 什么是所有权修饰符? 这里涉及到另一个问题, 因为在 ARC 有效时, id 类型和对象类型同 C 语言中的其他类型不同, 必须附加所有权修饰符. 所有权修饰符一种有 4 种:
  • __strong
  • __weak
  • __unsafe_unretained
  • __autorelease
  • __weakweak 的区别只在于, 前者用于变量的声明, 而后者用于属性的声明.

9. __block 在 ARC 和非 ARC 下含义一样吗?

__block 在 ARC 下捕获的变量会被 block retain, 这样可能导致循环引用, 所以必须要使用弱引用才能解决该问题.
而在非 ARC 下, 可以直接使用 __block 说明符修饰变量, 因为在非 ARC 下, block 不会 retain 捕获的变量.

10. viewWillLayoutSubviews 的作用是什么?

viewWillLayoutSubviews 方法会在视图的 bounds 改变时, 视图会调整子视图的位置, 我们可以在视图控制器中覆写这个方法在视图放置子视图前做出改变, 当屏幕的方向改变时, 这个方法会被调用.

11. SDWebImage 里面给 UIImageView 加载图片的逻辑是什么样的?

我曾经阅读过 SDWebImage 的源代码, 就在这里对如何给 UIImageView 加载图片做一个总结吧, SDWebImage 中为 UIView 提供了一个分类叫做 WebCache, 这个分类中有一个最常用的接口, sd_setImageWithURL:placeholderImage: , 这个分类同时提供了很多类似的方法, 这些方法最终会调用一个同时具有 option progressBlock completionBlock的方法, 而在这个类最终被调用的方法首先会检查是否传入了 placeholderImage 以及对应的参数, 并设置 placeholderImage .
然后会获取 SDWebImageManager 中的单例调用一个 downloadImageWithURL:... 的方法来获取图片, 而这个 manager 获取图片的过程有大体上分为两部分, 它首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以 url 作为数据的索引先在内存中寻找是否有对应的缓存, 如果缓存未命中就会在磁盘中利用 MD5 处理过的 key 来继续查询对应的数据, 如果找到了, 就会把磁盘中的缓存备份到内存中.
然而, 假设我们在内存和磁盘缓存中都没有命中, 那么 manager 就会调用它持有的一 个 SDWebImageDownloader 对象的方法 downloadImageWithURL:... 来下载图片, 这个方法会在执行的过程中调用另一个方法 addProgressCallback:andCompletedBlock:fotURL:createCallback: 来存储下载过程中和下载完成的回调, 当回调块是第一次添加的时候, 方法会实例化一个 NSMutableURLRequestSDWebImageDownloaderOperation , 并将后者加入 downloader 持有的下载队列开始图片的异步下载.
而在图片下载完成之后, 就会在主线程设置 image, 完成整个图像的异步下载和配置.

12. 你一般是怎么用Instruments的?(工作经验的问题,没必要所有都答)

参考答案:

  • 使用Allocations来检测内存和堆栈信息
  • 使用Leaks检测内存的使用情况,包括内存泄露问题
  • 使用Zombies来检测过早释放的僵尸对象,通过它可以检测出在哪里崩溃的。
  • 使用Time Profiler来检测CPU内存使用情况,性能分析

13. performSelector:withObject:afterDelay: 内部大概是怎么实现的,有什么注意事项么?

  • 创建一个定时器,时间结束后系统会使用runtime通过方法名称(Selector本质就是方法名称)去方法列表中找到对应的方法实现并调用方法

注意事项

  • 调用performSelector:withObject:afterDelay:方法时,先判断希望调用的方法是否存在respondsToSelector:
  • 这个方法是异步方法,必须在主线程调用,在子线程调用永远不会调用到想调用的方法

14. 有哪些常见的 Crash 场景?

  • 访问了僵尸对象
  • 访问了不存在的方法
  • 数组越界
  • 在定时器下一次回调前将定时器释放,会Crash

持续更新…