Some summaries of iOS knowledge points
推荐使用Typora编辑或阅读,没有数字编号的标题表示已回答
[TOC]
refer:https://www.jianshu.com/p/6b23e809e322
原文没有答案,我就尝试回答一下,有空就更新,欢迎各路大神不吝赐教。
-
协议
- [C] 定义属性、方法等,类似接口。协议可以被继承,多继承
-
@protocol NSObject - (BOOL)isEqual:(id)object; @property (readonly) NSUInteger hash; - (BOOL)isProxy; @end
-
Swift 包含[C]协议的所有功能;
此外协议可以被应用于泛型;可以扩展协议,在扩展中自定义方法或给协议中的方法提供默认实现
protocol Animal { func walk() var name: String { get set } } extension Animal { func walk() { // 默认实现 } func anotherFunction() { // 添加新的方法 } }
-
-
泛型
-
[C] 不支持
-
Swift 一大亮点,更安全的泛型;弱化的契约编程,可以指定泛型的基础类型。
-
-
结构体、类
-
[C] 结构体就是C语言的东西,属于值类型,由编译器管理生命周期。
由于结构体没有析构函数(C++中的结构体如果包含方法就会升级为C++类),所以结构体不能含有[C]对象,因为编译器不知道在什么地方release对象才好。[1] 类是包含了一个runtime的C的结构体,属于指针类型,采用引用计数管理生命周期。
-
Swift 也有结构体和类的区别。和[C]一样,结构体是值类型,类是指针(引用)类型。但Swift中结构体更强大,除了不能被继承、没有析构函数(deinit)外拥有类的所有功能。
-
-
类型安全
-
[C] [C]的对象在编译期都可以被看作id类型,一个方法的参数需要接收类型A,在直接调用的地方也确实传了类型A的对象,但是有时候这个对象来自外部,而外部有可能莫名其妙传了一个类型B进来,编译的时候有可能还通过了,导致在运行时会出现找不到某某方法的selector而崩溃。
-
Swift 强静态类型语言,A就是A,B就是B,不会出现C/C++的隐式转换。
let a = 1.23 // Double类型 let b: Int = a // ❌ 无法直接从Double转换成Int,需要通过构造函数 Int(a)才行
-
[1] 在Apple LLVM version 10.0.0 (clang-1000.11.45.5)中,C结构体可以包含ARC对象
另见StackOverflow上的讨论:https://stackoverflow.com/questions/28471855/does-objective-c-forbid-use-of-structs
- charles 抓包,充当开发初期的mock工具。
- Xcode Document,查看API😼
- Git,版本管理
iOS中绝大多数的类都继承自NSObject
NSProxy是一个虚类,继承它,重写下面两个方法可以将消息转发到另一个对象。
(void)forwardInvocation:(NSInvocation *)invocation;
(nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
从methodSignatureForSelector:
方法的签名可以看出,Swift不能使用这个利器了。
setNeedsDisplay | setNeedsLayout | |
---|---|---|
同步/异步 | 异步 | 异步 |
实际会调用的方法 | drawRect | layoutSubviews |
作用 | 绘制界面 | (非自动布局的情况下)调整子控件的布局,此时的控件frame确定了 |
为什么它们都是异步方法?
在整个UI界面下有一个RunLoop事件循环机制,代码的每次操作都基于事件循环。当我们调用setNeedsDisplay或setNeedsLayout时,内部实现仅仅是给某个标志位置1并立即返回,在下一次UI更新轮询的时候就会去调用drawRect或layoutSubviews方法更新UI并将标志位置0,所以在本次事件循环中不会立即调用。
layoutSubviews的触发时机
-
手动调用setNeedsLayout
-
addSubview
-
view.frame = value
-
滚动一个UIScrollView
-
旋转Screen
-
改变一个UIView大小
drawRect的触发时机
-
手动调用setNeedsDisplay
-
在调用sizeToFit后被调用
-
通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect
**注意:**layoutSubviews和drawRect不会被自身调用,调用源头是Controller,即init一个View后不会触发上面的方法
参考上面
CALayer和UIView的设计遵从了面向对象单一职责原则:CALayer负责显示内容的绘制,UIView则是管理。
在每一个UIView实例当中,都有一个默认的支持图层layer,UIView负责创建并且管理这个图层。实际上 UIView之所以能够显示,就是因为它里面有这个一个层,才具有显示的功能 。UIView仅仅是对它的一层封装,实现了CALayer的delegate,提供了处理事件交互的具体功能,还有动画底层方法的高级API。可以说CALayer是UIView的内部实现细节。
区别:
-
首先UIView可以响应事件,而CALayer不可以
-
一个 Layer 的 frame 是由它的 anchorPoint、position、bounds和 transform 共同决定的,而一个 View 的 frame 只是简单的返回 Layer的 frame
-
UIView主要是对显示内容的管理而 CALayer 主要侧重显示内容的绘制
-
在做 iOS 动画的时候,修改非 RootLayer的属性(譬如位置、背景色等)会默认产生隐式动画,而修改UIView则不会。
UDID是Unique Device Identifier的缩写,中文意思是设备唯一标识。
iOS6后用UIDevice.current.identifierForVendor
来获取,它对同一个app供应商是相同的。但这个有坑,app被删后就会重新生成。
UUID是Universally Unique Identifier的缩写,中文意思是通用唯一识别码。UUID()
就可以生成一个不同的UUID。
Tips:利用Keychain保存UDID或者UUID来保证唯一性。(从用户角度上,不推荐此作法)
refers:
https://www.jianshu.com/p/1011c872458d
https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor
CPU和GPU之所以大不相同,是由于其设计目标的不同,它们分别针对了两种不同的应用场景。CPU需要很强的通用性来处理各种不同的数据类型,同时又要逻辑判断又会引入大量的分支跳转和中断的处理。这些都使得CPU的内部结构异常复杂。而GPU面对的则是类型高度统一的、相互无依赖的大规模数据和不需要被打断的纯净的计算环境。
GPU采用了数量众多的计算单元和超长的流水线,但只有非常简单的控制逻辑并省去了Cache。而CPU不仅被Cache占据了大量空间,而且还有有复杂的控制逻辑和诸多优化电路,相比之下计算能力只是CPU很小的一部分。
CPU 基于低延时的设计;GPU是基于大的吞吐量设计。
什么类型的程序适合在GPU上运行?
(1)计算密集型的程序。所谓计算密集型(Compute-intensive)的程序,就是其大部分运行时间花在了寄存器运算上,寄存器的速度和处理器的速度相当,从寄存器读写数据几乎没有延时。可以做一下对比,读内存的延迟大概是几百个时钟周期;读硬盘的速度就不说了,即便是SSD, 也实在是太慢了。
(2)易于并行的程序。GPU其实是一种SIMD(Single Instruction Multiple Data)架构, 他有成百上千个核,每一个核在同一时间最好能做同样的事情。
refer: https://www.zhihu.com/question/19903344/answer/96081382
原文截取自知乎,详细内容请参考上面的链接
成员变量:属于类的内存布局的一部分,在new对象的时候会被创建。
属性:用语法糖包装了一个方法调用,对某一个成员变量的访问,或者成员变量的成员变量或属性的访问。
@interface Label: NSObject {
@private int _cnt; // 纯粹的成员变量
}
@property(nonatomic, copy) NSString *content; // 编译器会帮助生成一个名叫_content的NSString变量
@property(nonatomic) int cnt; // 自己实现了cnt的get-set方法后,编译器就不会自动生成变量了。
@end
@implementation Label
- (int)cnt {
return _cnt;
}
- (void)setCnt:(int)val {
_cnt = val;
}
@end
(1)import和include
(2)@class
(3)全局 & 静态变量
(1)分类拓展协议中哪些可以声明属性?
(2)继承和类别的区别
(3)分类的作用
(4)分类的局限性
[C]:category为类添加新的方法,甚至属性(关联对象)。category可以有多个,每个category都可以遵从新的协议;extension又被称作匿名分类,一个类只允许有一个extension,extension中可以添加成员变量、属性、方法等,起到数据和接口隐藏的作用。
Swift中category称作extension,extension的作用和[C]的category功能一致,且没有匿名分类。
**注意:**分类中如果写了已经存在的方法,就会涉及到一个方法覆盖的问题。这个行为是不确定的,具体请参考runtime的消息转发。
(1)字符串
(2)字符串截取
(3)格式
(1)iOS遍历数组/字典的方法
(2)NSValue NSNumber
(3)其它
(4)如何避免循环引用
点(.)调用的是属性,也就是方法,可以触发KVO。
name = "object"
实际上是直接访问成员变量,即self->name = "object"
。
(1)应用的生命周期
(2)简要说明一下APP的启动过程,main文件说起,main函数中有什么函数?作用是什么?
(3)UIApplicationMain函数作用
(4)main函数作用
delegate针对one-to-one关系,并且reciever可以返回值给sender。
notification可以针对one-to-one/many/none,reciever无法返回值给sender。
所以**,delegate用于sender希望接受到reciever的某个功能反馈值,notification用于通知多个object**某个事件。
Thinking:
- delegate需要注意什么?
- 注册了通知需要删除通知吗,如果需要什么时机删除合适?
还有一个使用场景。页面层级跨越很大,此时不适合delegate一层一层传递,用通知会很好地解耦。
#ifndef ONE_YEAR_SECONDS // 防止重复声明
#define ONE_YEAR_SECONDS (365 * 24 * 60 * 60) // 表达式需要加上括号,这是陷阱
// 或者
#define ONE_YEAR_SECONDS 31536000 // 直接算出来,常数不用加括号
#endif
#ifndef MIN
#define MIN(a, b) ((a) > (b) ? (b) : (a)) // 老生常谈,括号,括号
#endif
有些问的貌似是关于C++的,有的不明所以,这里我就以C++的视角解读一下吧。
const常量修饰关键字,例如:const int val = 1;
声明一个int的常量。在语义层面上,后续是不可对val进行修改的,否则会被编译器报错。在编译器层面上,会对const修饰的变量优化,即常量传递,在使用val的地方会直接使用数字1,而不是从寄存器中读取val的值。
#include <iostream>
using namespace std;
void main()
{
const int nConst = 5;
int * pConst = (int*)&nConst;
*pConst = 6;
int nVar = nConst;
cout << "nConst: " << nConst << " nVar: " << nVar << endl;
}
// 输出:nConst: 5 nVar: 5
// 这段代码,C也是一样
区别const int *obj
和int *const obj;
前者obj指向的内容不可更改;后者obj存储的地址不可再改变,指向的内容可以改变。例如:
const int *obj = p;
*obj = 45; // ❌
NSString *const obj2 = @"123";
obj2 = @"1"; // ❌
所以,我们经常把这两个结合起来定义[C]对象的常量:const NSString *const ulr = @"a url"
在C++中,const还可以被用于成员方法中,表示这个方法中的不会对类的成员变量进行修改。
-
被static修饰的变量会存储在静态存储区,在程序初始化的时候就会被初始化为对应类型的零值。如果是在方法内部的的static变量,只有在第一次执行这个方法的时候才会被初始化,比如创建单例的时候使用:
void singleton() { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ <#code to be executed once#> }); }
-
static全局变量,编译器为每一个编译单元生成带有编译单元前缀的符号信息,以保证不同的编译单元不共享同一个static变量。如果某一个头文件含有一个static变量,那么在不同的.m、.mm、.c、.cpp文件中若包含了此头文件,每一个static变量都会是独立的。[1]
extern "C"
extern "C"
用于C/C++的混编,其作用是方法的签名会用C的签名格式。
在C++头文件里,当需要提供对外接口时,若方法参数或返回值不包含C++的东西,一般建议使用extern "C"
修饰方法,以便C调用。
注意:extern "C"
是C++的东西,需要用#ifdef __cpluscplus
包起来。详情看下面的延展阅读
不了解svn/cvs,现在主流是git,这里只作git说明。
(1)封装_点语法
(2)继承
(3)多态
75、简单介绍下NSURLConnection类及+sendSynchronousRequest:returningResponse:error:与– initWithRequest:delegate:两个方法的区别?
NS_CLASS_AVAILABLE_IOS(2_0) @interface UIApplication : UIResponder
NS_CLASS_AVAILABLE_IOS(2_0) @interface UIView : UIResponder <NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, CALayerDelegate>
NS_CLASS_AVAILABLE_IOS(2_0) @interface UIViewController : UIResponder <NSCoding, UIAppearanceContainer, UITraitEnvironment, UIContentContainer, UIFocusEnvironment>
@interface CALayer : NSObject <NSSecureCoding, CAMediaTiming>
我们可以看出UIApplication,UIView,UIViewController都是继承自UIResponder类,可以响应和处理事件。CALayer不是UIResponder的子类,无法处理事件。
事件传递顺序:产生触摸事件 -> 添加到UIApplication事件队列 -> UIApplication调用keyWindow的hitTest方法,keyWindow又会调用它的subviews的hitTest方法,直至返回最合适的view。
UIApplication -> UIWindow(keyWindow) -> 寻找处理事件最合适的view(递归方法:- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
方法的大致实现是:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
return nil;
}
for (UIView *view in self.subviews) {
if([view pointInside:point withEvent:event]) {
UIView *hitTestView = [view hitTest:point withEvent:event];
if(nil == hitTestView){
return view;
}
}
}
return self;
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
判断点是否在当调用者的坐标系上,在返回YES;不在返回NO。
找到合适的view之后,就会调用响应者的3个touch相关的方法:touchesBegan、touchesMoved、touchesEnd。它们的默认实现就是把事件传递给上一层,如:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// 默认会把事件传递给上一个响应者,上一个响应者是父控件,交给父控件处理
[super touchesBegan:touches withEvent:event];
// 注意不是调用父控件的touches方法,而是调用父类的touches方法
}
refer:
这个问题在我大三找实习面试的时候被问过,当时由于才开始学习iOS,所以没答上来,惭愧,惭愧。
frame:自身在父视图坐标系统中的位置和大小。
bounds:自身在本地坐标系统中的位置和大小。
frame和bounds共同决定了view在屏幕上的位置。
bounds的设定会确定自身坐标系统的原点,若bounds=CGRect(0,0, 10, 10),则本地坐标系统的原点为(0, 0);若bounds=CGRect(-20,-20, 10,10),则本地坐标系统的原点为(-20, -20),这种情况下,只有当设置子视图的frame=(-20, -20, 1, 1)时,子视图才会和父视图的左上角重合。具体例子参考refer。
selector(选择器)是方法的名字,通过它可以找到方法的实现地址。
方法属于对象,包含名字和实现。
[C]使用引用计数来管理内存。
远古时代使用MRC(手动引用计数,现在可以在cocos-2dx的cpp代码中找到类MRC的实现),手动来管理引用计数;
WWDC 2011年后使用自动的引用计数(Automatic Reference Count 简称 ARC),由编译器在需要的地方插入MRC需要做的事情,一定程度上解放了程序员的双手。
延迟加载也成懒加载。不是立即需要的数据,不用在一开始就准备好,而是在需要的时候再去生成,降低初始化成本。
在[C]中,一般是通过重写属性的getter方法来实现懒加载。
Swift提供语言层面上的懒加载支持——lazy关键字。被lazy修饰的成员变量会在对象init的时候不会被初始化,直到它被调用。所有Collection也都有lazy Collection用于
(1)什么是深拷贝浅拷贝
(2)字符串什么时候使用copy,strong
(3)字符串所在内存区域
(4)mutablecopy和copy @property(copy) NSMutableArray *arr;这样写有什么问题
(5)如何让自定义类可以使用copy修饰符
106、写一个setter方法用于完成@property (nonatomic,retain)NSString *name,写一个setter方法用于完成@property(nonatomic,copy)NSString *name
(1)mvc模式
(2)单例模式
(3)mvvm模式
(4)观察者模式
(5)工厂模式
(6)代理模式
(7)策略模式
(8)适配器模式
(9)模版模式
(10)外观模式
(11)创建模式
(12)MVP模式
(1)runtime
1.1、什么是runtime
1.2、runtime干什么用,使用场景
(2)消息机制
2.1、消息转发的原理
2.2、SEL isa super cmd 是什么
(3)动态绑定
(1)底层实现
(2)KVO概述
(3)KVC概述
KVO和KVC
(1)如何调用私有变量,如何修改系统的只读属性,KVC的查找顺序
(2)什么是键-值,键路径是什么
(3)kvo的实现机制
(4)KVO计算属性,设置依赖键
(5)KVO集合属性
(6)kvo使用场景
(1)主要功能
(2)缓存
(3)内存缓存与磁盘缓存
密码的安全原则
(1)多线程概念
(2)多线程的作用
(3)使用场景
(1)使用block时什么情况会发生引用循环,如何解决?
(2)在block内如何修改block外部变量?
(3)Block & MRC-Block
(4)什么是block
(5)block 实现原理
(6)关于block
(7)使用block和使用delegate完成委托模式有什么优点
(8)多线程与block
(9)谈谈对Block 的理解?并写出一个使用Block执行UIVew动画?
(10)写出上面代码的Block的定义(接上题)
(1)什么情况使用 weak 关键字,相比 assign 有什么不同?
(2)怎么用 copy 关键字?
(3)weak & strong
(4)这个写法会出什么问题: @property (copy) NSMutableArray *array
(5) 如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
(6) @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的
(7)ivar、getter、setter 是如何生成并添加到这个类中的?
(8)用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
(9)@protocol 和 category 中如何使用 @property
(10)runtime如何通过selector找到对应的IMP地址?
(11)retain和copy区别
(12)copy和strong的使用?
(13)NSString和NSMutableString,前者线程安全,后者线程不安全。
(14)readwrite,readonly,assign,retain,copy,weak ,strong,nonatomic 属性的作用
TableView性能优化
UITableView核心思想
UITableView的优化主要从三个方面入手:
(1)UITableView最核心的思想
(2)定义高度
(3)自定义高度原理
(4)老生常谈之UITableView的性能优化
(5)cell高度的计算
(5.1)定高的cell和动态高度的cell
(6)TableView渲染
(7)减少视图的数目
(8)减少多余的绘制操作
(9)不要给cell动态添加subView
(10)异步化UI,不要阻塞主线程
(11)滑动时按需加载对应的内容
(12)离屏渲染的问题
(13)离屏渲染优化方案
这个没使用过
(1)数据存储技术
(1.1)数据存储的几种方式
(1.2)各自特点(面试考点)
(1.3)偏好设置(面试考点)
(1.4)归档(面试考点)
(2)数据库技术(SQLite&CoreData)
(1)堆栈空间分配区别
(2)堆栈缓存方式区别
(3)堆栈数据结构区别
(1)内存区域
(1.1)堆和栈的区别
(1.2)iOS内存区域
(2)字符串的内存管理
(3)你是如何优化内存管理
(4)循环引用
(5)autorelease的使用
(5.1)工厂方法为什么不释放对象
(5.2)ARC下autorelease的使用场景
(5.3)自动释放池如何工作
(5.4)避免内存峰值
(5.5)ARC和MRC的混用
(5.6)NSTimer的内存管理
(5.7)ARC的实现原理
(1)View和layer的区别
(2)new和alloc init的区别
(1)何实现瀑布流,流水布局
(2)和UITableView的使用区别
(1)网络基础
(2)网络传输
(3)AFN
(1)底层实现
(2)对服务器返回的数据处理
(3)监听请求过程
(4)在文件下载和文件上传的使用难易度
(5)网络监控
(6)ASI提供的其他实用功能
(7)MKNetworkKit
客户端使用到的算法不多,true或false的存储比较多一点,比如某个设置是否开启啊,这儿可以用bit来优化。(纯属抛砖引玉)