Wrappres' Studio.

iOS 面试题

字数统计: 8.4k阅读时长: 30 min
2018/11/29 Share

计算机部分

设计模式

策略模式(Strategy)

它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。

装饰模式(Decorator)

动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
例如:Swift 中的协议组装

代理模式(Proxy)

为其他对象提供一种代理以控制对这个对象的访问。
常见于 iOS 中 UIKit 中。

工厂模式(Factory Method)

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

原型模式(Prototype)

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

模板方法模式(Template Method)

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

外观模式(Facade)

为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

建造者模式(Builder)

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

观察者模式(Observer)

定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
例如:KVO

抽象工厂模式(Abstract Factory)

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

状态模式(State)

当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
例如:React Component 的 state,reduex 的 state

适配器模式(Adapter)

将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

备忘录模式(Memento)

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

组合模式(Composite)

将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

迭代器模式(Iterator)

提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。

单例模式(Singleton)

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

桥接模式(Bridge)

将抽象部分与它的实现部分分离,使它们都可以独立地变化。

命令模式(Command)

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

职责链模式(Chain of Responsibility)

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

中介者模式(Mediator)

用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

享元模式(Flyweight)

运用共享技术有效地支持大量细粒度的对象。

解释器模式(Interpreter)

给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

访问者模式(Visitor)

表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

设计模式的基本原则

单一职责原则(SRP)

就一个类而言,只做一件事。

开放-封闭原则(OCP)

是说软件实体(类、模块、函数等等)应该可以拓展,但是不可修改。

依赖倒转原则(DIP)

A. 高层模块不应该依赖低层模块,两个都应该依赖抽象。
B. 抽象不应该依赖细节,细节应该依赖抽象。

里氏代换原则(LSP)

子类型必须能够替换掉它们的父类型。

迪米特原则(LoD)

如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。

合成/聚合复用原则(CARP)

尽量使用合成/聚合,尽量不要使用类继承。

网络相关

这里

算法相关

这里

iOS 部分

App 启动的完整过程

解析 info.plist
加载相关信息,例如:闪屏
沙盒建立,权限检查
Mach-O 文件加载
如果是二进制文件,找到适合当前 CPU 类别的部分
加载所依赖的 Mach-O 文件,递归调用 Mach-o 加载方法
定位内部外部指针引用,例如指针、字符串等
执行 attribute(constructor) 的 C 函数
加载类中的扩展中的方法
C++ 静态对象加载,调用 Objc 的 +load 方法

程序执行

main 函数
执行 UIApplicationMain
创建 Application 对象
创建 UIAppDelegate 对象并复制
读取配置文件 info.plist ,设置程序启动的一些属性
创建 Main Runloop
调用 AppDelegate 中的 didFinishxxxx
如果配置了 Storyboard,加载 Storyboard 文件

让你开发一个新的 App,你会考虑哪些内容

  1. 架构
    • 技术选型:
    • 架构选型:
    • 根据需求文档,对功能进行划分
    • 分层,细分
    • 开源框架
  2. 代码规范
  3. 团队协作工具
  4. 埋点统计
  5. crash 收集
  6. 持续集成

MVC、MVP、MVVM

资料

MVC

MVC 已经拥有 50 年的历史。且出现了很多 MVC 的衍生版,而至今仍然没有一个明确的定义。下面讲讲我认为的 MVC:
尽管 MVC 的在不同平台上的定义有很多,iOS 上有,SpringMVC 上也有,甚至 ASP,.Net 上也有 MVC。但我认为 MVC 最为重要的一部分是将软件或是整个应用进行了分层。主要分为了以下三层,这也是在众多 MVC 实现中基本没有争议的一部分。

  • 视图:管理作为位图展示到屏幕上的图形和文字输出
  • 控制器:翻译用户的输入并按照用户的输入操作模型和视图
  • 模型:管理应用的行为和数据,响应数据请求(通常来自于视图)和更新状态指令(通常来自于控制器)


控制器负责对模型中的数据进行更新,而视图向模型中请求数据。当有用户的行为触发操作时,会有控制器更新模型,并通知视图进行更新,在此时视图向模型请求新的数据。

MVP

MVP 是 MVC 的一个变种。在 MVP 中最主要的是使用 Presenter 对视图和模型进行的了解耦。 在 MVP 中 Presenter 可理解为松散的控制器,其中包含了视图和 UI 业务逻辑,所有从视图发出的事件都会通过代理给 Presenter 进行处理,同时,Presenter 也通过视图暴露的接口与其进行通信。

MVVM

最早有一个 Presentation Model 模式,与 MVP 较为相似。PM 从视图层中分离了行为和状态,创建了一个视图的抽象,叫做 Presentation Model。通过引入展示模型将模型层中的数据与复杂的业务逻辑分装成属性与简单的数据同时暴露给视图,让视图和展示模型中的属性进行同步。

而 MVVM 是 PM 的一种实现。其中的 ViewModel 是视图模型,也就是 PM 中的展示模型。同时还使用隐式的 Binder 层,而声明式的数据和命令的绑定在 MVVM 模式中就是通过它完成的。

无论是 MVVM 还是 Presentation Model,其中最重要的不是如何同步视图和展示模型/视图模型之间的状态,是使用观察者模式,双向绑定还是其他的机制都不是整个模式中最重要的部分,最为关键的是展示模型/视图模型创建了一个视图的抽象,将视图中的状态和行为抽离出一个新的抽象

APNS 发送系统消息的机制

APNS 优势:杜绝了类似安卓那种为了接受通知不停在后台唤醒程序保持长连接的行为,由 iOS 系统和 APNS 进行长连接替代。

  • 应用在通知中心注册,由 iOS 系统向 APNS 请求返回设备令牌(device Token)
  • 应用程序接收到设备令牌并发送给自己的后台服务器
  • 服务器把要推送的内容和设备发送给 APNS
  • APNS 根据设备令牌找到设备,再由 iOS 根据 APPID 把推送内容展示

IBOutlet 连出来的视图属性为什么可以被设置成 weak

在 Storyboard 中添加一个控件引用关系是这样的(以 UIButton 为例): UIViewController -> UIView -> UIButton
此时 UIViewController 强引用着 UIView, UIView 强引用着 UIButton, IBoutlet 连线到控制器的.m 或者.h 中作为视图的属性时用 weak 修饰就可以了, (觉得用 strong 修饰也可以但是没有必要)。添加到子控件也是强引用:UIButton 就是添加到了 UIViewController 的 view 上。

静态库和动态库之间的区别

静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。
动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。

NSNotificationCenter 是在哪个线程发送的通知

Notification 的发送与接收处理都是在同一个线程中。

为什么一定要在主线程里面更新 UI

UI 操作涉及到渲染访问各种 View 对象的属性,如果是异步操作会有读写问题。加锁呢,性能损耗大(视图层次深,属性多)。所以主线程操作 UI,是约定俗成的开发规则。

进程和线程的区别

进程:是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程:是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

同步异步的区别

同步:阻塞当前线程操作,不能开辟线程。
异步:不阻碍线程继续操作,可以开辟线程来执行任务。

并行和并发的区别

并发:当有多个线程在操作时,如果系统只有一个 CPU,则它根本不可能真正同时进行一个以上的线程,它只能把 CPU 运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。.这种方式我们称之为并发(Concurrent)。
并行:当系统有一个以上 CPU 时,则线程的操作有可能非并发。当一个 CPU 执行一个线程时,另一个 CPU 可以执行另一个线程,两个线程互不抢占 CPU 资源,可以同时进行,这种方式我们称之为并行(Parallel)。
区别:并发和并行是即相似又有区别的两个概念,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。倘若在计算机系统中有多个处理机,则这些可以并发执行的程序便可被分配到多个处理机上,实现并行执行,即利用每个处理机来处理一个可并发执行的程序,这样,多个程序便可以同时执行。

iOS 内存管理

  1. 没经历过 MRC 时代,刚开始学就是 ARC 。
  2. ARC 是通过编译器的静态分析能力,在编译时期帮我们在合适的位置插入 retain ,release 代码,不用我们手动的去管理内存
  3. 创建对象的时候,引用计数是 1 ,当有新的指针指向这个对象时,引用计数加 1 ,不再指向的时候,引用计数减 1 ,当引用计数为 0 时,该对象会被释放掉。
  4. 使用引用计数的语言都会面临一个问题,就是循环引用。两个对象之间互相引用,也可能是多个对象之间形成环形;另一个是 block ,block 也有可能造成循环引用,当对象持有 block ,而 block 中又调用了对象的方法。
  5. 面对循环引用,ARC 引用了 weak 弱引用,weak 标识的对象,只持有对象,但不增加引用计数,且会自动设置 nil。

ARC 的原则

  • 不能使用 retain,release,retainCount,autorelease
  • 必须遵循内存管理方法的命名规则
  • 不需要显示地调用 dealloc
  • 使用 autoreleasePool 代替 NSAutoreleasePool
  • oc 对象不可以作为 C 结构体成员
  • 自己生成的对象自己持有
  • 非自己生成的对象,自己可以持有
  • 自己持有的对象,不再需要时,需要对其进行释放
  • 非自己持有的对象无法释放

为什么已经有了 ARC ,但还是需要 @AutoreleasePool 的存在

及时释放不需要的空间,避免内存峰值

内存分区

堆、栈、代码区、字符串常量区、静态区

BAD_ACCESS 在什么情况下出现,如何调试

原因是访问了野指针,比如访问已经释放对象的成员变量或者发消息、死循环等。
利用 Xcode 的 Analyze 和 Instruments。

lldb(gdb)常用的控制台调试命令

  • p 输出基本类型。是打印命令,需要指定类型。是 print 的简写 p (int)[[[self view] subviews] count]
  • po 打印对象,会调用对象 description 方法。是 print-object 的简写 po [self view]
  • expr 可以在调试时动态执行指定表达式,并将结果打印出来。常用于在调试过程中修改变量的值。
  • bt:打印调用堆栈,是 thread backtrace 的简写,加 all 可打印所有 thread 的堆栈
  • br l:是 breakpoint list 的简写

UIKit

查看这里

weak

查看这里

__weak 和 _Unsafe_Unretain

  1. 前者会自动置 nil,后者不会,容易出现悬垂指针
  2. 后者效率更高

多线程

查看这里

Runloop

查看这里

Runtime

这里

Objective-C 部分

SDWebImage 的缓存策略

sd 加载一张图片的时候,会先在内存里面查找是否有这张图片,如果没有会根据图片的 md5(url)后的名称去沙盒里面去寻找,是否有这张图片,如果没有会开辟线程去下载,下载完毕后加载到 imageview 上面,并 md(url)为名称缓存到沙盒里面。

AFN 为什么添加一条常驻线程

AFN 目的:就是开辟线程请求网络数据。如果没有常住线程的话,就会每次请求网络就去开辟线程,完成之后销毁开辟线程,这样就造成资源的浪费,开辟一条常住线程,就可以避免这种浪费,我们可以在每次的网络请求都添加到这条线程。

block 的实质是什么?有几种 block?分别是怎样产生的

block:本质就是一个 object-c 对象。block:存储位置,可能分为 3 个地方:代码区,堆区、栈区(ARC 情况下会自动拷贝到堆区,因此 ARC 下只能有两个地方:代码区、堆区)。代码区:不访问栈区的变量(如局部变量),且不访问堆区的变量(alloc 创建的对象),此时 block 存放在代码区。堆区:访问了处于栈区的变量,或者堆区的变量,此时 block 存放在堆区。需要注意实际是放在栈区,在 ARC 情况下会自动拷贝到堆区,如果不是 ARC 则存放在栈区,所在函数执行完毕就回释放,想再外面调用需要用 copy 指向它,这样就拷贝到了堆区,strong 属性不会拷贝、会造成野指针错区。

__block 修饰的变量为什么能在 block 里面能改变其值

默认情况下,block 里面的变量,拷贝进去的是变量的值,而不是指向变量的内存的指针。
当使用__block 修饰后的变量,拷贝到 block 里面的就是指向变量的指针,所以我们就可以修改变量的值。

什么是指针常量和常量指针

指针常量:本质是一个常量,而用指针修饰它。指针常量的值是指针,这个值因为是常量,所以不能被赋值。
常量指针:又叫常指针,可以理解为常量的指针,也即这个是指针,但指向的是个常量,这个常量是指针的值(地址),而不是地址指向的值。

Objc 在向一个对象发送消息时,发生了什么

根据对象的 isa 指针找到类对象 id,先在对应类的缓存方法数组中寻找,然后在查询类对象里面的 methodLists 方法函数列表,如果没有找到,再沿着 superClass,寻找父类,再在父类 methodLists 方法列表里面查询,最终找到 SEL,根据 id 和 SEL 确认 IMP(指针函数)。如果一直找不到,转向拦截调用,如果没有重写拦截调用,程序报错。

浅拷贝和深拷贝的区别

浅拷贝是指复制指向对象的指针,而不是复制对象本身。深拷贝:复制对象本身,两者完全独立。

用@property 声明 NSString / NSArray / NSDictionary 经常使用 copy 关键字

  1. 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论为传入一个可变对象还是不可变对象,我本身持有的就是一个不可变的副本。
  2. 如果使用 strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。

总结:使用 copy 的目的是,防止把可变类型的对象赋值给不可变类型的对象时,可变类型对象的值发生变化会无意间篡改不可变类型对象原来的值。

关于复制的两个方法 copy 和 mutableCopy

copy 返回的是不可变对象,mutableCopy 返回的是可变对象。
对于非集合类型不可变对象:copy 是指针复制,mutableCopy 是内容复制。
对于非集合类型的可变对象:copy 和 mutableCopy 都是内容复制。
对于集合类型不可变对象:copy 是指针复制,mutableCopy 是单层内容复制。
对于集合类型的可变对象:copy 和 mutableCopy 都是单层内容复制。
单层内容复制:仅限于对象本身,集合内的对象任是指针复制。

@property 中 retain 和 copy 生成的 setter 方法的区别

retain:

1
2
3
4
5
- (void)setName:(NSString *)str {
[str retain];
[_name release];
_name = str;
}

copy

1
2
3
4
5
- (void)setName:(NSString *)str {
id t = [str copy];
[_name release];
_name = t;
}

PS:所以 copy 关键词和可变类型是不能搭配的,如果调用的可变对象的方法,会提示找不到方法,因为 copy 返回的是不可变对象

分类和扩展有什么区别

分类(Category)是表示一个指向分类的结构体的指针。原则上它只能增加方法,不能增加成员(实例)变量,因为分类的结构体指针中没有属性列表。
扩展(Extension)是分类(Category)的一个特例。类扩展与分类相比只少了分类的名称,所以称之为“匿名分类”。

分类有哪些局限性?分类的结构体里面有哪些成员

①分类中原则上只能增加方法(能添加属性的的原因只是通过 runtime 解决无 setter/getter 的问题而已);
②类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private 类型的(用范围只能在自身类,而不是子类或其他地方);
③类扩展中声明的方法没被实现,编译器会报警,但是分类中的方法没被实现编译器是不会有任何警告的。这是因为类扩展是在编译阶段被添加到类中,而分类是在运行时添加到类中。
④类扩展不能像分类那样拥有独立的实现部分(@implementation 部分),也就是说,类扩展所声明的方法必须依托对应类的实现部分来实现。
⑤定义在 .m 文件中的类扩展方法为私有的,定义在 .h 文件(头文件)中的类扩展方法为公有的。类扩展是在 .m 文件中声明私有方法的非常好的方式。
Category 是表示一个指向分类的结构体的指针,其定义如下:

1
2
3
4
5
6
7
8
typedef struct objc_category *Category;
struct objc_category {
char *category_name OBJC2_UNAVAILABLE; // 分类名
char *class_name OBJC2_UNAVAILABLE; // 分类所属的类名
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 实例方法列表
struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 类方法列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 分类所实现的协议列表
}

分类的格式:

1
2
3
4
@interface 待扩展的类(分类的名称)
@end
@implementation 待扩展的名称(分类的名称)
@end

类扩展格式:

1
2
3
4
@interface XXX ()
//私有属性
//私有方法(如果不实现,编译时会报警,Method definition for 'XXX' not found)
@end

为什么说 Objective-C 是动态运行时语言

将数据类型的确定由编译时,推迟到了运行时。Runtime 可以使我们在运行时才决定一个对象。

KVC 的底层实现

KVC(key-Value coding) 键值编码,指 iOS 开发中,可以允许开发者通过 Key 名直接访问对象的属性,或者给对象的属性赋值。不需要调用明确的存取方法,这样就可以在运行时动态访问和修改对象的属性,而不是在编译时确定,底层还是调用 runtime 来实现的。
当 setValue 时:

  • 检查是否存在相应的 key 的 set 方法,如果存在,就调用 set 方法
  • 如果 set 方法不存在,查找与 key 相同名称并且带下划线的成员变量,如果有,直接给成员变量属性赋值
  • 如果没找到_key,查找相同名称的属性 key,如果有就直接赋值
  • 如果没找到,则调用 valueForUndefinedKey 和 setValue:forUndefinedKey 方法,这些方法默认实现都会抛出异常

KVO 的底层实现

基于 runtime 实现。当一个对象使用了 KVO 监听,iOS 系统会修改这个对象的 isa 指针,改为指向一个全新的通过 Runtime 动态创建的子类,子类拥有自己的 set 方法实现,set 方法实现内部会顺序调用 willChangeValueForKey 方法、原来的 setter 方法实现、didChangeValueForKey 方法,而 didChangeValueForKey 方法内部又会调用监听器的 observeValueForKeyPath:ofObject:change:context:监听方法。

如何取消系统默认的 KVO 并手动触发(给 KVO 的触发设定条件:改变的值符合某个条件时再触发 KVO)

被监听的属性的值被修改时,就会自动触发 KVO。如果想要手动触发 KVO,则需要我们自己调用 willChangeValueForKey 和 didChangeValueForKey 方法即可在不改变属性值的情况下手动触发 KVO,并且这两个方法缺一不可。
参考链接

Objective-C 的反射机制

  • class 反射

    1
    2
    3
    4
    5
    6
    //通过类名的字符串形式实例化对象
    Class class = NSClassFromString(@"student");
    Student *stu = [[class alloc] init];
    //将类名变为字符串
    Class class =[Student class];
    NSString className = NSStringFromClass(class);
  • SEL 的反射

    1
    2
    3
    4
    5
    //通过方法的字符串形式实例化方法
    SEL selector = NSSelectorFromString(@"setName");
    [stu performSelector:selector withObject:@"Mike"];
    //将方法变成字符串
    NSStringFromSelector(@selector(setName:));

调用方法的两种方式

  • 直接通过方法名调用

    1
    [person show];
  • 间接的通过 SEL 数据来调用

    1
    2
    SEL aaa = @selector(show);
    [person performSelector:aaa];

如何访问并修改一个类的私有属性

  • 通过 KVC 获取
  • 通过 runtime 访问并修改

下面代码的输出

1
2
3
4
5
6
7
8
9
@implementation Son : Father
- (id)init {
if (self = [super init]) {
NSLog(@"%@", NSStringFromClass([self class])); // Son
NSLog(@"%@", NSStringFromClass([super class])); // Son
}
return self;
}
@end

self 是类的隐藏参数,指向当前调用方法的这个类的实例。super 是一个 Magic Keyword,它本质是一个编译器标示符,和 self 是指向的同一个消息接收者。不同的是:super 会告诉编译器,调用 class 这个方法时,要去父类的方法,而不是本类里的。上面的例子不管调用[self class]还是[super class],接受消息的对象都是当前 Son *obj 这个对象。

为什么 atomic 不能保证线程安全

property 的 atomic 是采用 spinlock_t 也就是俗称的自旋锁实现的。这个锁仅仅保证了 getter 和 setter 存取方法的线程安全.这种安全仅仅是 set/get 的读写安全,并非真正意义上的线程安全。

  1. 对于 NSArray 类型 @property(atomic)NSArray *array 我们用 atomic 修饰,数组的添加和删除并不是线程安全的,这是因为数组比较特殊,我们要分成两部分考虑,一部分是&array 也就是这个数组本身,另一部分是他所指向的内存部分。atomic 限制的只是&array 部分,对于它指向的对象没有任何限制。
  2. 当线程 A 进行写操作,这时其他线程的读或者写操作会因为该操作而等待。当 A 线程的写操作结束后,B 线程进行写操作,然后当 A 线程需要读操作时,却获得了在 B 线程中的值,这就破坏了线程安全,如果有线程 C 在 A 线程读操作前 release 了该属性,那么还会导致程序崩溃。所以仅仅使用 atomic 并不会使得线程安全,我们还要为线程添加 lock 来确保线程的安全。

关联对象 AssociatedObject 有什么应用,系统如何管理关联对象?其被释放的时候需要手动将所有的关联对象的指针置空么

AssociatedObject 的应用主要有:

  1. 添加公共属性,例:如何給 NSArray 添加一个属性(不能使用继承)
  2. 添加私有成员变量,例:給按钮添加点击事件的回调
  3. 关联 KVO 观察者,例:有时候我们在分类中使用 KVO,推荐使用关联的对象作为观察者,尽量避免对象观察自身

管理关联对象有三个方法:

1
2
3
4
5
6
//关联对象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//获取关联的对象
id objc_getAssociatedObject(id object, const void *key)
//移除关联的对象
void objc_removeAssociatedObjects(id object)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum { 
OBJC_ASSOCIATION_SETTER_ASSIGN = 0,
OBJC_ASSOCIATION_SETTER_RETAIN = 1,
OBJC_ASSOCIATION_SETTER_COPY = 3, // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below.
OBJC_ASSOCIATION_GETTER_READ = (0 << 8),
OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8),
OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8)
};

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};

当对象被释放时,如果设置的协议是 OBJC_ASSOCIATION_ASSIGN,那么他的关联对象不会减少引用计数,其他的协议都会减少从而释放关联对象。
unsafe_unretain 一般认为外部有对象控制,所以对象不用处理,因此不管什么协议,对象释放时都无需手动讲关联对象置空。
关联对象(AssociatedObject)参考

Autoreleasepool 所使用的数据结构是什么?AutoreleasePoolPage 结构体了解么

较为宽泛的概念
较为深入的理解

讲一下对象,类对象,元类,跟元类结构体的组成以及他们是如何相关联的?为什么对象方法没有保存的对象结构体里,而是保存在类对象的结构体里

对象 isa 指向类对象,类对象的 isa 指向元类。元类 isa 指向根元类。根元类的 isa 指针指向自己,superclass 指针指向 NSObject 类。实例对象结构体只有一个 isa 变量,指向实例对象所属的类。类对象有 isa,superclass,方法,属性,协议列表,以及成员变量的描述。所有的对象调用方法都是一样的,没有必要存在对象中,对象可以有无数个,类对象就有一个所以只需存放在类对象中。参考

Swift 部分

struct 和 class 的区别

struct 是值类型,class 是引用类型。struct 不具备继承的特性。当你需要值语义的时候用 struct ,当你需要引用语义的时候就用 class。
嵌套类型

  • 包含其他引用类型的引用类型,这没什么特别的。如果持有内部或外部值的引用,就可以修改这个值。改动会同步到所有持有者。
  • 包含其他值类型的值类型,这样做的结果是一个更庞大的值类型。当内部值是外部值的一部分时,如果你将外部值存储到某个新地方,整个值类型都会被拷贝,包括内部值。如果你将内部值储存到新地方,那就只拷贝内部值。
  • 包含值类型的引用类型,被引用的值会变大。外部值的引用可以操作整个对象,包括内部值。修改内部值时,外部值引用的持有者都会同步改动。如果你将内部值储存到新地方,它会被拷贝。
  • 包含引用类型的值类型,这就有点复杂了。你可能会遇到意料之外的行为。这有利有弊,取决于你的使用方式。如果你将一个引用类型放到值类型中,然后拷贝这个值类型到一个新地方,拷贝中的内部对象的引用值是相同的,它们都指向相同的地方。

什么是 ABI

ABI(Application Binary Interface): 应用程序二进制接口 描述了应用程序和操作系统之间,一个应用和它的库之间,或者应用的组成部分之间的低接口。

Swift mutating 关键字的使用

Swift 的 mutating 关键字修饰方法是为了能在该方法中修改 struct 或是 enum 的变量,所以如果你没在接口方法里写 mutating 的话,别人如果用 struct 或者 enum 来实现这个接口的话,就不能在方法里改变自己的变量了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protocol Vehicle
{
var numberOfWheels: Int {get}
var color: UIColor {get set}

mutating func changeColor()
}

struct MyCar: Vehicle {
let numberOfWheels = 4
var color = UIColor.blueColor()

mutating func changeColor() {
color = UIColor.redColor()
}
}

defer 的使用场景

defer block 里的代码会在函数 return 之前执行,无论函数是从哪个分支 return 的,还是有 throw,还是自然而然走到最后一行。
无论如何都会执行db.close(),关闭数据库连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
func someQuery() -> ([Result], [Result]){
let db = DBOpen("xxx")
defer {
db.close()
}
guard results1 = db.query("query1") else {
return nil
}
guard results2 = db.query("query2") else {
return nil
}
return (results1, results2)
}

定义静态方法时关键字 static 和 class 有什么区别

static 不能被继承 ,class 可以。

值捕获

OC 中的 block 捕获的是值,需要添加__block关键词 才会传递指针。

1
2
3
4
5
6
7
8
NSInteger i = 1;
void(^block)(void) = ^{
NSLog(@"block %ld:", i);
};
i += 1;
NSLog(@"out1 %ld:", i);
block();
NSLog(@"out2 %ld:", i);

普通 Swift 中闭包只有在运行时,才会去获取值,即传递的是引用

1
2
3
4
5
6
7
8
9
var i = 1
let closure = {
i += 1
print("closure \(i)")
}
i += 1
print("out1 \(i)")
closure()
print("out 2 \(i)")

在 Swift 中需要实现类似 OC 中的值捕获,需要如下处理

1
2
3
4
5
6
7
8
9
var i = 1
let closure = {
[i] in
print("closure \(i)")
}
i += 1
print("out1 \(i)")
closure()
print("out 2 \(i)")

CATALOG
  1. 1. 计算机部分
    1. 1.1. 设计模式
      1. 1.1.1. 策略模式(Strategy)
      2. 1.1.2. 装饰模式(Decorator)
      3. 1.1.3. 代理模式(Proxy)
      4. 1.1.4. 工厂模式(Factory Method)
      5. 1.1.5. 原型模式(Prototype)
      6. 1.1.6. 模板方法模式(Template Method)
      7. 1.1.7. 外观模式(Facade)
      8. 1.1.8. 建造者模式(Builder)
      9. 1.1.9. 观察者模式(Observer)
      10. 1.1.10. 抽象工厂模式(Abstract Factory)
      11. 1.1.11. 状态模式(State)
      12. 1.1.12. 适配器模式(Adapter)
      13. 1.1.13. 备忘录模式(Memento)
      14. 1.1.14. 组合模式(Composite)
      15. 1.1.15. 迭代器模式(Iterator)
      16. 1.1.16. 单例模式(Singleton)
      17. 1.1.17. 桥接模式(Bridge)
      18. 1.1.18. 命令模式(Command)
      19. 1.1.19. 职责链模式(Chain of Responsibility)
      20. 1.1.20. 中介者模式(Mediator)
      21. 1.1.21. 享元模式(Flyweight)
      22. 1.1.22. 解释器模式(Interpreter)
      23. 1.1.23. 访问者模式(Visitor)
    2. 1.2. 设计模式的基本原则
      1. 1.2.1. 单一职责原则(SRP)
      2. 1.2.2. 开放-封闭原则(OCP)
      3. 1.2.3. 依赖倒转原则(DIP)
      4. 1.2.4. 里氏代换原则(LSP)
      5. 1.2.5. 迪米特原则(LoD)
      6. 1.2.6. 合成/聚合复用原则(CARP)
    3. 1.3. 网络相关
    4. 1.4. 算法相关
  2. 2. iOS 部分
    1. 2.1. App 启动的完整过程
    2. 2.2. 程序执行
    3. 2.3. 让你开发一个新的 App,你会考虑哪些内容
    4. 2.4. MVC、MVP、MVVM
      1. 2.4.1. MVC
      2. 2.4.2. MVP
      3. 2.4.3. MVVM
    5. 2.5. APNS 发送系统消息的机制
    6. 2.6. IBOutlet 连出来的视图属性为什么可以被设置成 weak
    7. 2.7. 静态库和动态库之间的区别
    8. 2.8. NSNotificationCenter 是在哪个线程发送的通知
    9. 2.9. 为什么一定要在主线程里面更新 UI
    10. 2.10. 进程和线程的区别
    11. 2.11. 同步异步的区别
    12. 2.12. 并行和并发的区别
    13. 2.13. iOS 内存管理
    14. 2.14. ARC 的原则
    15. 2.15. 为什么已经有了 ARC ,但还是需要 @AutoreleasePool 的存在
    16. 2.16. 内存分区
    17. 2.17. BAD_ACCESS 在什么情况下出现,如何调试
    18. 2.18. lldb(gdb)常用的控制台调试命令
    19. 2.19. UIKit
    20. 2.20. weak
    21. 2.21. __weak 和 _Unsafe_Unretain
    22. 2.22. 多线程
    23. 2.23. Runloop
    24. 2.24. Runtime
  3. 3. Objective-C 部分
    1. 3.1. SDWebImage 的缓存策略
    2. 3.2. AFN 为什么添加一条常驻线程
    3. 3.3. block 的实质是什么?有几种 block?分别是怎样产生的
    4. 3.4. __block 修饰的变量为什么能在 block 里面能改变其值
    5. 3.5. 什么是指针常量和常量指针
    6. 3.6. Objc 在向一个对象发送消息时,发生了什么
    7. 3.7. 浅拷贝和深拷贝的区别
    8. 3.8. 用@property 声明 NSString / NSArray / NSDictionary 经常使用 copy 关键字
    9. 3.9. 关于复制的两个方法 copy 和 mutableCopy
    10. 3.10. @property 中 retain 和 copy 生成的 setter 方法的区别
    11. 3.11. 分类和扩展有什么区别
    12. 3.12. 分类有哪些局限性?分类的结构体里面有哪些成员
    13. 3.13. 为什么说 Objective-C 是动态运行时语言
    14. 3.14. KVC 的底层实现
    15. 3.15. KVO 的底层实现
    16. 3.16. 如何取消系统默认的 KVO 并手动触发(给 KVO 的触发设定条件:改变的值符合某个条件时再触发 KVO)
    17. 3.17. Objective-C 的反射机制
    18. 3.18. 调用方法的两种方式
    19. 3.19. 如何访问并修改一个类的私有属性
    20. 3.20. 下面代码的输出
    21. 3.21. 为什么 atomic 不能保证线程安全
    22. 3.22. 关联对象 AssociatedObject 有什么应用,系统如何管理关联对象?其被释放的时候需要手动将所有的关联对象的指针置空么
    23. 3.23. Autoreleasepool 所使用的数据结构是什么?AutoreleasePoolPage 结构体了解么
    24. 3.24. 讲一下对象,类对象,元类,跟元类结构体的组成以及他们是如何相关联的?为什么对象方法没有保存的对象结构体里,而是保存在类对象的结构体里
  4. 4. Swift 部分
    1. 4.1. struct 和 class 的区别
    2. 4.2. 什么是 ABI
    3. 4.3. Swift mutating 关键字的使用
    4. 4.4. defer 的使用场景
    5. 4.5. 定义静态方法时关键字 static 和 class 有什么区别
    6. 4.6. 值捕获