UIButton 继承自谁? 父类负责什么?
UIButton继承自UIControl, UIConrol最重要的增加了target/action 的响应方式
UIButton加一个tap手势,会响应手势还是action事件?为什么?
会响应手势,手势识别级别更高。(图抄的)
NSNotification 和 KVO 的比较
KVO仅适用于值,NSNotification可用于值更改,但它可用于任何事物,并且可以承载更大的有效负载。
例如,只要文件下载完毕,就可以发布NSNotification,userInfo可以包含所花费的时间长度,下载的字节数以及文件保存到的文件系统路径。
Stop Using NSNotificationCenter
TCP 和 UDP的区别
1.基于连接与无连接
2.对系统资源的要求(TCP较多,UDP少)
3.UDP程序结构较简单
4.流模式与数据报模式
5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。
避免滥用单例
1. 全局状态
// 一个单例可以被使用在任何地方,而不需要显式地声明依赖。
2. 对象的生命周期
// 单例应该只用来保存全局的状态,并且不能和任何作用域绑定。如果这些状态的作用域比一个完整的应用程序的生命周期要短,那么这个状态就不应该使用单例来管理。
隐式动画
其实是Core Animation在每个RunLoop周期中会自动开始一次新的事务,即使你不显式的使用[CATranscation begin]开始一次事务,任何在一次RunLoop运行时循环中属性的改变都会被集中起来,执行默认0.25秒的动画。
为什么UIView没有隐式动画?为什么在UIView animation的block内,可以实现动画
每个UIView对它关联的图层都遵循了CALayerDelegate协议,并且实现了-actionForLayer:forKey方法。当不在一个动画块中修改动画属性时,UIView对所有图层行为都返回了nil,但是在动画Block范围就返回了非空值
iOS 常见的设计模式
见单独的笔记
三次握手、四次挥手
TCP 的标志(flag)--------- SYN(synchronize 同步) 和 ACK(acknowledgement 确认)
为什么挥手是四次?
个人理解: 之所以中间的两个动作没有合并,是因为tcp存在「半关闭」状态,也就是单向关闭。就是在server进入到close_wait阶段,其实server有可能需要给client发送一些自己想要发送的消息的(如果还有消息没传递完全),如果直接传了两个状态过去,直接就关闭了,没有了这个机会
因为TCP是全双工通信的
- 第一次挥手 因此当主动方发送断开连接的请求(即FIN报文)给被动方时,仅仅代表主动方不会再发送数据报文了,但主动方仍可以接收数据报文。
- 第二次挥手
被动方此时有可能还有相应的数据报文需要发送,因此需要先发送ACK报文,告知主动方“我知道你想断开连接的请求了”。这样主动方便不会因为没有收到应答而继续发送断开连接的请求(即FIN报文)。 - 第三次挥手 被动方在处理完数据报文后,便发送给主动方FIN报文;这样可以保证数据通信正常可靠地完成。发送完FIN报文后,被动方进入LAST_ACK阶段(超时等待)。
- 第四挥手 如果主动方及时发送ACK报文进行连接中断的确认,这时被动方就直接释放连接,进入可用状态。
MRC 下的copy 、retain 、 assign
//copy环境下
-(void)setName:(NSString *)name{
if (_name != name) {
[_name release];
_name = [name copy];
}
}
//retain环境下
-(void)setName:(NSString *)name{
if (_name != name) {
[_name release];
_name = [name retain];
}
}
//assign环境下
-(void)setName:(NSString *)name{
_name = name;
}
SDWebImage 下载图片
SDWebImage原理和缓存机制

由于UIImage的imageWithData函数是每次画图的时候才将Data解压成ARGB的图像,所以在每次画图的时候,会有一个解压操作,这样效率很低,但是只有瞬时的内存需求。为了提高效率通过SDWebImageDecoder将包装在Data下的资源解压,然后画在另外一张图片上,这样这张新图片就不再需要重复解压了。这种做法是典型的空间换时间。这里有的公司会问道解压的具体实现
SDWebImage 下载图片(一边下载一边播放的原理)
pod Update 和 pod install的区别
更新pod spec properties
元类的根类
元类的类是根元类,根元类在继承体系中是根类的元类,根元类的类就是自己(即 isa 指针指向自己),根元类的superClass 指针指向 根类。

objc_msgSend执行流程
OC中的方法调用(消息机制),其实都是转换为objc_msgSend函数的调用, 就是给方法调用者(reciver)发送消息
详细的看底层三的笔记
这里只是简单介绍
1.消息发送
2.动态方法解析
3.消息转发
SEL:类成员方法的指针,但和C的函数指针还不一样,函数指针直接保存了方法地址,但是SEL只是方法编号
IMP:函数指针,保存了方法地址
我们叫@selector(beFriendWith:)为消息的选择子或者选择器。(A selector identifying the message to send)
主要说一下消息发送的过程: reciver 的方法到类对象中找,首先 cache ---> class_rw_t ---> superClass的cache ---> superClass的class_rw_t
C语言的函数调用和oc的消息机制有什么区别
- 对于
C语言,函数的调用在编译期决定调用哪个函数。编译完之后直接顺序执行。OC的函数调用称为消息发送。属于动态调用过程。在编译的时候决不能决定真正调用那个函数(实时证明,在编译阶段,oc可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错,而c语言在编译阶段或报错)
简单来说就是静态绑定/动态绑定的区别
在OC中,对象发送消息,就会使用动态绑定机制来决定需要调用的方法。其实底层都是C语言实现的函数,当对象收到消息后,究竟调用那个方法完全决定于运行期,甚至你也可以直接在运行时改变方法,这些特性都使OC成为一门动态语言。
宏
iOS宏定义 宏展开不占运行时间,只占编译时间
如果subView 超过superView的范围,要响应点击事件的解决办法
用来判断触摸点是否在控件上
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event:
用来判断控件是否接受事件以及找到最合适的view
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event:
// 重写superView 的 hitTest 方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
CGPoint rightPoint = [self convertPoint:point toView:_subView];
if ([_subView pointInside:rightPoint withEvent:event]) {
return _subView;
}
return [super hitTest:point withEvent:event];
}
或者
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *view = [super hitTest:point withEvent:event];
if (view == nil) {
for (UIView *subView in self.subviews) {
CGPoint subPoint = [subView convertPoint:point fromView:self];
if (CGRectContainsPoint(subView.bounds, subPoint)) {
view = subView;
}
}
}
return view;
}
NSCache
是线程安全的 ,底层由ptherd_mutex控制, (切换到后台就会被清理,这个要注意)
锁的性能排序
os_unfair_lock: iOS10 才支持使用,OSSpinlink:替代品,性能应该是目前最好的。 使用就是调用加锁、解锁的API
OSSpinlink: 自旋锁,性能最好, 目前不安全可能出现性能翻转的问题。(高优先级优先调度,如果现在有低优先级的任务卡住线程,高优先级占用资源,将造成死锁)
dispatch_semaphore: 信号量 (性能好,常用)
ptherd_mutex: NSCache 内部保证线程安全,就是用的这个锁
dispatch_queue(DISPATCH_QUEUE_SERIAL):串行队列
NSLock:
NSCondition
ptherd_mutex(recursive)
NSRecursiveLock
NSConditionLock
@synchronized
网络五层分类
应用层: DNS 、 FTP 、 HTTP
传输层: TCP 、 UDP
网络层: IP首部
数据链路层:以太网首部
物理层: 物理传输
https通信认证的具体过程
单向认证:
- 客户端发起
https请求:比如用户在浏览器里输入一个https网址,然后连接到server的443端口。- 服务器响应请求,并把服务器证书传给客户端
- 客户端收到证书,并和本地证书进行对比,如果不一致,那么断开连接。如果
通过进行第4步- 客户端随机生成一个
密钥key(用来后面通信的对称加密),用证书中的公钥加密该key,并传回服务端- 服务端接收到
key加密后的数据,然后用私钥解出key- 服务端和客户端现在都指定这个
key为后面通信对称加密的密钥,握手结束。- 服务端和客户端开始真正通信,通信数据由
key对称加密。
IM保证消息的到达
用户A给用户B发送一个“你好”,很容易想到,流程如下:

未到达的情况
1. 服务器崩溃,msg:N包未发出
2. 网络抖动,msg:N包被网络设备丢弃
3. client-B崩溃,msg:N包未接收
要想实现应用层的消息可靠投递,必须加入应用层的确认机制,即:要想让发送方client-A确保接收方client-B收到了消息,必须让接收方client-B给一个消息的确认,这个应用层的确认的流程,与消息的发送流程类似:
client-B向im-server发送一个ack请求包,即ack:R。
im-server在成功处理后,回复client-B一个ack响应包,即ack:A则im-server主动向client-A发送一个ack通知包,即ack:N
一条消息的发送,分别包含(上)(下)两个半场,即msg的R/A/N三个报文,ack的R/A/N三个报文。一个应用层即时通讯消息的可靠投递,共涉及6个报文,这就是IM系统中消息投递的最核心技术(如果某个IM系统不包含这6个报文,不要谈什么消息的可靠性)。
rutime怎么对weak属性置为nil
weak 对象会放入一个 hash 表中。
用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc。
假如 weak 指向的对象内存地址是addr,那么就会以addr为键, 在这个 weak 表中搜索,找到所有以addr为键的 weak 对象,从而设置为 nil。
atomic里面做了啥,为什么说线程安全
在setter和getter内部用@synchronized(self) 加锁
kvo是所有的属性都能监听吗,那些不能监听,怎么让你声明的属性不能被监听
kvo原理是重写isa指针,指向一个新创建的当前类的子类,重写setter方法调用,不通过setter方法就不会被调用
@property 做了啥
离屏渲染
参考:
事件响应
苹果会在APP启动时候,注册一个source1(基于mach port)用来接收系统事件,其回调函数为__IOHIDEventSystemClientQueueCallback()。
当一个硬件事件发生后(触摸/锁屏/摇晃),IOKit生成一个IOHIDEvent,由springBoard接收,随后用mach port转发给需要的APP进程,之后苹果注册的source1触发回调IOHIDEventSystemClientQueueCallback,回调触发Source0,Source0调用_UIApplicationHandleEventQueue()进行应用内分发
_UIApplicationHandleEventQueue() 会把 IOHIDEvent 包装成 UIEvent 进行分发或处理,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。
两个大数相加
将字符串转成数据,进行按照位数计算,最好使用动态数组,最后将数组转换成字符串
响应者链条
其中摇一摇、键盘弹出等需要判断第一响应者
一次完整的触摸事件的传递响应的过程
UIAppliction --> UIWiondw -->递归找到最适合处理事件的控件 控件调用touches方法-->判断是否实现touches方法-->没有实现默认会将事件传递给上一个响应者-->找到上一个响应者
1.响应者链条:
- 由很多响应者链接在一起组合起来的一个链条称之为响应者链条
- 什么是响应者:继承UIResponder的对象称之为响应者对象
2.默认做法是将事件顺着响应者链条向上传递,将事件交给上一个响应者进行处理
如何判断当前响应者的上一个响应者是谁?
- 1.判断当前是否是控制器的View, 如果是控制器的View上一个响应者就是控制器
- 2.如果当前不是控制器的View,上一个响应者就是父控件
3.响应者链条有什么用?
可以让一个触摸事件发生的时候让多个响应者同时响应该事件

总结:触摸事件是从底向上的,响应者是从上到下的
dyld加载流程
-
dyld 从_dyld_start开始
-
dyld::main函数
-
配置环境变量
-
加载共享缓存
-
实例化主程序
-
加载动态库
-
链接动态库
-
初始化方法 (runTime)
-
调用map_images进行可执行文件内容的解析和处理
-
在load_images中调用call_load_methods,调用所有Class和Category的+load方法
-
进行各种objc结构的初始化(注册Objc类 、初始化类对象等等)
-
调用C++静态初始化器和__attribute__((constructor))修饰的函数
-
-
-
进入main函数
进程和线程

weakSelf / strongSelf
深入研究 Block 用 weakSelf、strongSelf、@weakify、@strongify 解决循环引用
strongSelf 使用场景:
- 一个
block内多次使用self调用方法
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong __typeof(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doOtherThing];
});
block内部代码有延时,可能导致对象被释放掉
strongSelf是一个自动变量,当block执行完毕就会释放自动变量strongSelf,不会对self进行一直进行强引用。
数组和链表的区别 、使用场景
数组和链表的区别
数组是将元素在内存中连续存储的;
-
它的优点:因为数据是连续存储的,内存地址连续,所以在查找数据的时候效率比较高;
-
它的缺点:在存储之前,我们需要申请一块连续的内存空间,并且在编译的时候就必须确定好它的空间的大小。在运行的时候空间的大小是无法随着你的需要进行增加和减少而改变的,当数据两比较大的时候,有可能会出现越界的情况,数据比较小的时候,又有可能会浪费掉内存空间。在改变数据个数时,增加、插入、删除数据效率比较低
-
链表是动态申请内存空间,不需要像数组需要提前申请好内存的大小,链表只需在用的时候申请就可以,根据需要来动态申请或者删除内存空间,对于数据增加和删除以及插入比数组灵活。还有就是链表中数据在内存中可以在任意的位置,通过应用来关联数据(就是通过存在元素的指针来联系)
链表和数组使用场景
数组应用场景:数据比较少;经常做的运算是按序号访问数据元素;数组更容易实现,任何高级语言都支持;构建 的线性表较稳定。
链表应用场景:对线性表的长度或者规模难以估计;频繁做插入删除操作;构建动态性比较强的线性表。
position与anchorPoint
同时主要bounds修改www.jianshu.com/p/b59f0b69c… ,bounds修改,只是调整内部subView的位置
工程中经常修改tablewview的contentInset值。该技巧常用于屏幕两边,上下头部的“留白”。修改contentInset的时候其实修改的也是bounds。 比如,self.tableView.contentInset = UIEdgeInsetsMake(3.5, 0, 0, 0);,那么tableview的bounds已经变了,y的值已经变为-3.5了。
bounds 修改 size ,会同步影响frame改变。将frame按照anchorPoint进行放大,缩小
动态库、静态库
// 等待补充
static const extern 宏定义
static
iOS 常用关键字 static、const、 extern、define
- 修饰全局变量 在全局变量前加static, 全局变量就被定义成为一个全局静态变量(全局变量和静态全局变量的生命周期是一样的, 都是在堆中的静态区, 在整个工程执行期间内一直存在) 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。
不改变作用域
作用域:全局静态变量在声明他的文件之外是不可见的。准确地讲从定义之处开始到文件结尾。非静态全局变量的作用域是整个源程序(多个源文件可以共同使用)。
- 修饰局部变量
1.延长局部变量的生命周期, 程序结束才会销毁。
2.局部变量只会生成一份内存, 不管方法执行多少次, 其只会初始化一次。
3.存储区域从栈移动到bbs(静态区)。
- 修饰函数(与修饰变量相同)
const
const 表示常量, 被修饰之后的数据类型, 由变量转为常量, 其不可以被修改, 在编译阶段会执行检查, 其存储区域位于常量区, 常用于配合 static 或 extern 使用。
static NSString * const gString = @"Global";
总结: const 修饰其后面内容
extern
这个单词翻译过来是"外面的, 外部的"。 顾名思义, 它的作用是声明外部全局变量。这里需要特别注意 extern 只能声明, 不能用于实现。 当使用 extern 来声明变量时, 其会先在编译单元内部进行查找, 如果没有则继续到外部进行查找, 如果缺少实现并且使用到了此数据时会导致编译不通过。
define
简单说其实就是字符替换, 系统会在调用的地方进行文本替换, 可用于修饰数据, 函数, 结构体, 方法等, 系统不会对其做类型检查。
define 与 const 选择
宏定义是在预编译期间处理, 在使用时系统直接进行的方法替换, 静态变量等则是在编译期间进行的。 宏定义不会系统不会做编译检查, 所以类型错误也能通过编译, const 则会做编译检查。 能显式的声明数据类型, 并且不会出现自己定义的宏被其他人员更换,导致出现难以排查的 Bug。 不过宏不仅能对数据类型进行定义, 还能对函数, 结构体, 方法等进行定义相对比起常量来说作用会更多一些。
总结
- 编译时刻:宏是预编译, const是编译阶段
- 编译检查:宏不做检查, 有错误不会提示, const会检查, 有错误会提示
- 宏的优点:高效,灵活,可用于替换各种 函数,方法,结构体,数据等;
- 宏的缺点:由于在预编译期间完成, 大量使用宏, 容易造成编译时间久
- const优点:编译器通常不为普通 const 常量分配存储空间, 而是将它们保存在符号表中, 这使得它成为一个编译期间的常量, 没有了存储与读内存的操作, 使得它的效率也很高, 相当于宏更加高效, 并且容错率很低。
- const缺点:const 能定义的内容非常有限, 只能用于定义常量
- 宏定义所定义的生命周期与所在的载体的生命周期有关
- const修饰具有就近性, 即 const 后面的参数是不可变的, const修饰的参数具有只读性, 如果试图修改, 编译器就会报错
- 苹果官方不推荐我们使用宏, 推荐使用const常量
建议: 在实际开发中, 对于能使用常量定义完成的, 尽量使用常量能实现, 而不要考虑使用 宏。 读 [Effective Objective-C] 第四条: 多用类型常量, 少用 #define 与处理指令, 苹果也推荐我们在开发中尽量使用常量。
多个网络请求完成后,再调用其他请求
iOS(线程同步)多个网络请求ABC执行完再执行D的正确理解(异步并发下的任务是网络请求信号量的使用)
iOS动态调用方法
performselector
performselector:AfterDelay:0: 当传递数值为0的时候,会在下一个runLoop进行调用,这里是通过NSTimer进行的一个延时调用,如果没有afterDelay的话,和直接调用没有区别
Aop切片编程的实现
Aspects、阿里的切片库
async 和 sync 的区别
sync会阻塞当前线程
NS_ENUM & NS_OPTIONS
深入浅出了解frame和bounds
YYWebImage 源码剖析
数组和集合
集合的特性是不添加重复的元素 (底层可以用双向链表、动态数组、哈希表、平衡二叉树实现)
ListSet (底层用动态链表、或者数组实现)
- 搜索是
O(N),添加是O(N),删除是O(N)
TreeSet (底层用红黑树实现,可以按照从小到大(不是添加顺序)的顺序进行遍历<中序遍历>)
-
搜索、添加、删除都是
O(logN) -
元素必须具备可比较性
HashSet (空间复杂度大,空间使用率低,典型的空间换时间)
- 搜索、添加、删除都是
O(1) - 元素需要实现
- (BOOL)isEqual:(id)other和- (NSUInteger)hash方法
OrderSet : 利用链表,底层是LinkedHashMap,将元素依次串联起来

哈希表
- 哈希表底层是一个
数组是数据结构 - 哈希表元素的添加、删除、搜索的时间复杂度是
O(1) - 哈希表元素不要求可比较性
哈希冲突
- 开放地址法
按照一定的规则向其他地址探测,直到遇到空桶
- 再哈希法
设计多个哈希函数
- 链地址法
通过链表将同一个
index的元素串起来
JDK1.8的哈希冲突解决方案

设计模式
iOS 分区
iOS APM (性能监控) - 数据采集实现调研
为什么category无法增加成员变量?
首先因为结构上不允许,成员变量存储在不可变数组里面。从设计角度看,实例对象持有成员变量,内存大小在编译完成已经完成了分配,修改成员变量的成本过于巨大,且存在一定风险。