2020面试题汇总

2020不平凡的一年,无论是关于本身,还是关于生活,都不是好过的,再加上自己的人生低谷,无以复加,人道中年,真的也是雪上加霜。慢慢人生之路,不知何去何从,可我们没法选择我们自己的局,不管手中的牌是好是坏,只有两种选择,要不弃牌,要不就是尽力打好!

文章目录

数据结构

1.数据结构的存储一般分为几种?各有什么特点

数据结构的存储一般常用的有两种 顺序存储结构 和 链式存储结构

顺序存储结构:

比如,数组,1-2-3-4-5-6-7-8-9-10,存储是按顺序的。再比如栈和队列等

链式存储结构:

比如,数组,1-2-3-4-5-6-7-8-9-10,链式存储就不一样了 1(地址)-2(地址)-7(地址)-4(地址)-5(地址)-9(地址)-8(地址)-3(地址)-6(地址)-10(地址)。每个数字后面跟着一个地址 而且存储形式不再是顺序

2.集合结构 线性结构 树形结构 图形结构

  • 集合结构
    一个集合,就是一个圆圈中有很多个元素,元素与元素之间没有任何关系 这个很简单
  • 线性结构
    一个条线上站着很多个人。 这条线不一定是直的。也可以是弯的。也可以是值的 相当于一条线被分成了好几段的样子 (发挥你的想象力)。 线性结构是一对一的关系
  • 树形结构
    做开发的肯定或多或少的知道xml 解析 树形结构跟他非常类似。也可以想象成一个金字塔。树形结构是一对多的关系
  • 图形结构
    这个就比较复杂了,他呢 无穷。无边 无向(没有方向)图形机构 你可以理解为多对多 类似于我们人的交集关系

3.单向链表 双向链表 循环链表

  • 单向链表
  • 双向链表
  • 循环链表

4.数组和链表区别

  • 数组
    数组元素在内存上连续存放,可以通过下表查找元素;插入,删除需要移动大量元素,比较适用于元素很少的情况
  • 链表
    链表中的元素在内存中不是顺序存储的,查找慢,插入、删除只需要对元素指针重新赋值,效率高

堆、栈和队列

  • 堆是一种经过排序的树形数据结构,每个节点都有一个值,通常我们所说的堆的数据结构是指二叉树。所以堆在数据结构中通常可以被看做是一棵树的数组对象。而且堆需要满足一下两个性质:
    1.堆中某个节点的值总是不大于或不小于其父节点的值;
    2.堆总是一棵完全二叉树。
  • 堆分为两种情况,有最大堆和最小堆。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆,在一个摆放好元素的最小堆中,父结点中的元素一定比子结点的元素要小,但对于左右结点的大小则没有规定谁大谁小
  • 堆常用来实现优先队列,堆的存取是随意的,这就如同我们在图书馆的书架上取书,虽然书的摆放是有顺序的,但是我们想取任意一本时不必像栈一样,先取出前面所有的书,书架这种机制不同于箱子,我们可以直接取出我们想要的书。

  • 栈是限定仅在表尾进行插入和删除操作的线性表。我们把允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈。栈的特殊之处在于它限制了这个线性表的插入和删除位置,它始终只在栈顶进行。
  • 栈是一种具有后进先出的数据结构,又称为后进先出的线性表,简称 LIFO(Last In First Out)结构。也就是说后存放的先取,先存放的后取,这就类似于我们要在取放在箱子底部的东西(放进去比较早的物体),我们首先要移开压在它上面的物体(放进去比较晚的物体)。
  • 堆栈中定义了一些操作。两个最重要的是PUSH和POP。PUSH操作在堆栈的顶部加入一个元素。POP操作相反,在堆栈顶部移去一个元素,并将堆栈的大小减一。
  • 栈的应用—递归

队列

  • 队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表。允许插入的一端称为队尾,允许删除的一端称为队头。它是一种特殊的线性表,特殊之处在于它只允许在表的前端进行删除操作,而在表的后端进行插入操作,和栈一样,队列是一种操作受限制的线性表。
  • 队列是一种先进先出的数据结构,又称为先进先出的线性表,简称 FIFO(First In First Out)结构。也就是说先放的先取,后放的后取,就如同行李过安检的时候,先放进去的行李在另一端总是先出来,后放入的行李会在最后面出来。

输入一棵二叉树的根结点,求该树的深度?

二叉树的节点定义如下:

struct BinaryTreeNode
{
	int m_nValue ;
	BinaryTreeNode* m_pLeft;
	BinarvTreeNode* m_pRight ;
}
  • 如果一棵树只有一个结点,它的深度为1。
  • 如果根结点只有左子树而没有右子树,那么树的深度应该是其左子树的深度加1;同样如果根结点只有右子树而没有左子树,那么树的深度应该是其右子树的深度加1。
  • 如果既有右子树又有左子树,那该树的深度就是其左、右子树深度的较大值再加1。
int TreeDepth(TreeNode* pRoot)
{
    if(pRoot == nullptr)
        return 0;
    int left = TreeDepth(pRoot->left);
    int right = TreeDepth(pRoot->right);

    return (left>right) ? (left+1) : (right+1);
}

输入一课二叉树的根结点,判断该树是不是平衡二叉树?

  • 重复遍历结点
    先求出根结点的左右子树的深度,然后判断它们的深度相差不超过1,如果否,则不是一棵二叉树;如果是,再用同样的方法分别判断左子树和右子树是否为平衡二叉树,如果都是,则这就是一棵平衡二叉树
  • 遍历一遍结点
    遍历结点的同时记录下该结点的深度,避免重复访问

方法1

struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
};
 
int TreeDepth(TreeNode* pRoot){
    if(pRoot==NULL)
        return 0;
    int left=TreeDepth(pRoot->left);
    int right=TreeDepth(pRoot->right);
    return left>right?(left+1):(right+1);
}
 
bool IsBalanced(TreeNode* pRoot){
    if(pRoot==NULL)
        return true;
    int left=TreeDepth(pRoot->left);
    int right=TreeDepth(pRoot->right);
    int diff=left-right;
    if(diff>1 || diff<-1)
        return false;
    return IsBalanced(pRoot->left) && IsBalanced(pRoot->right);
}

方法2

bool IsBalanced_1(TreeNode* pRoot,int& depth){
    if(pRoot==NULL){
        depth=0;
        return true;
    }
    int left,right;
    int diff;
    if(IsBalanced_1(pRoot->left,left) && IsBalanced_1(pRoot->right,right)){
        diff=left-right;
        if(diff<=1 || diff>=-1){
            depth=left>right?left+1:right+1;
            return true;
        }
    }
    return false;
}
 
bool IsBalancedTree(TreeNode* pRoot){
    int depth=0;
    return IsBalanced_1(pRoot,depth);
} 

Foundation(基础)

1.nil NIL NSNULL区别

  • nil、NIL 可以说是等价的,都代表内存中一块空地址。
  • NSNULL 代表一个指向 nil 的对象。

2.如何实现一个线程安全的 NSMutableArray?

NSMutableArray是线程不安全的,当有多个线程同时对数组进行操作的时候可能导致崩溃或数据错误

  • 线程锁:使用线程锁对数组读写时进行加锁
  • 派发队列:在《Effective Objective-C 2.0…》书中第41条:多用派发队列,少用同步锁中指出:使用“串行同步队列”(serial synchronization queue),将读取操作及写入操作都安排在同一个队列里,即可保证数据同步。而通过并发队列,结合GCD的栅栏块(barrier)来不仅实现数据同步线程安全,还比串行同步队列方式更高效。

3.atomic修饰符是绝对安全吗,为什么?

不是,所谓的安全只是局限于 Setter、Getter 的访问器方法而言的,你对它做 Release 的操作是不会受影响的。这个时候就容易崩溃了。

4.实现 isEqual 和 hash 方法时要注意什么?

  • hash
    对关键属性的hash值进行位或运算作为hash值
  • isEqual
    ==运算符判断是否是同一对象, 因为同一对象必然完全相同
    判断是否是同一类型, 这样不仅可以提高判等的效率, 还可以避免隐式类型转换带来的潜在风险
    判断对象是否是nil, 做参数有效性检查
    各个属性分别使用默认判等方法进行判断
    返回所有属性判等的与结果

5.id 和 instanceType 有什么区别?

  • 相同点
    instancetype 和 id 都是万能指针,指向对象。
  • 不同点
    1.id 在编译的时候不能判断对象的真实类型,instancetype 在编译的时候可以判断对象的真实类型。
    2…id 可以用来定义变量,可以作为返回值类型,可以作为形参类型;instancetype 只能作为返回值类型。

self和super的区别

  • self调用自己方法,super调用父类方法
  • self是类,super是预编译指令
  • [self class] 和 [super class] 输出是一样的
  • self和super底层实现原理
    1.当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;
    而当使用 super 时,则从父类的方法列表中开始找,然后调用父类的这个方法sd
    2.当使用 self 调用时,会使用 objc_msgSend 函数:id objc_msgSend(id theReceiver, SEL theSelector, …)
    第一个参数是消息接收者,第二个参数是调用的具体类方法的 selector,后面是 selector 方法的可变参数。以 [self setName:] 为例,编译器会替换成调用 objc_msgSend 的函数调用,其中 theReceiver 是 self,theSelector 是 @selector(setName:),这个 selector 是从当前 self 的 class 的方法列表开始找的 setName,当找到后把对应的 selector 传递过去。
    3.当使用 super 调用时,会使用 objc_msgSendSuper 函数:id objc_msgSendSuper(struct objc_super *super, SEL op, …)
    第一个参数是个objc_super的结构体,第二个参数还是类似上面的类方法的selector
    struct objc_super {
    id receiver;
    Class superClass;
    };

7.@synthesize和@dynamic分别有什么作用?

  • @property有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var;
  • @synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。
  • @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。

8.typeof 和 __typeof,typeof 的区别?

  • __typeof __() 和 __typeof() 是 C语言 的编译器特定扩展,因为标准 C 不包含这样的运算符。 标准 C 要求编译器用双下划线前缀语言扩展(这也是为什么你不应该为自己的函数,变量等做这些)
  • typeof() 与前两者完全相同的,只不过去掉了下划线,同时现代的编译器也可以理解。
  • 所以这三个意思是相同的,但没有一个是标准C,不同的编译器会按需选择符合标准的写法。

10.struct和class的区别

  • 类: 引用类型(位于栈上面的指针(引用)和位于堆上的实体对象)
  • 结构:值类型(实例直接位于栈中)

UIKit

1.UIView 和 CALayer 是什么关系?

  • UIView 继承 UIResponder,而 UIResponder 是响应者对象,可以对iOS 中的事件响应及传递,CALayer 没有继承自 UIResponder,所以 CALayer 不具备响应处理事件的能力。CALayer 是 QuartzCore 中的类,是一个比较底层的用来绘制内容的类,用来绘制UI
  • UIView 对 CALayer 封装属性,对 UIView 设置 frame、center、bounds 等位置信息时,其实都是UIView 对 CALayer 进一层封装,使得我们可以很方便地设置控件的位置;例如圆角、阴影等属性, UIView 就没有进一步封装,所以我们还是需要去设置 Layer 的属性来实现功能。
  • UIView 是 CALayer 的代理,UIView 持有一个 CALayer 的属性,并且是该属性的代理,用来提供一些 CALayer 行的数据,例如动画和绘制。

Bounds 和 Frame 的区别?

  • Bounds:一般是相对于自身来说的,是控件的内部尺寸。如果你修改了 Bounds,那么子控件的相对位置也会发生改变。
  • Frame :是相对于父控件来说的,是控件的外部尺寸。

setNeedsDisplay 和 layoutIfNeeded 两者是什么关系?

  • UIView的setNeedsDisplay和setNeedsLayout两个方法都是异步执行的。而setNeedsDisplay会自动调用drawRect方法,这样可以拿到UIGraphicsGetCurrentContext进行绘制;而setNeedsLayout会默认调用layoutSubViews,给当前的视图做了标记;layoutIfNeeded 查找是否有标记,如果有标记及立刻刷新。
  • 只有setNeedsLayout和layoutIfNeeded这二者合起来使用,才会起到立刻刷新的效果。

4.谈谈对UIResponder的理解

UIResponder类是专门用来响应用户的操作处理各种事件的,包括触摸事件(Touch Events)、运动事件(Motion Events)、远程控制事件(Remote Control Events)。我们知道UIApplication、UIView、UIViewController这几个类是直接继承自UIResponder,所以这些类都可以响应事件。当然我们自定义的继承自UIView的View以及自定义的继承自UIViewController的控制器都可以响应事件。

5.loadView的作用?

loadView方法会在每次访问UIViewController的view(比如controller.view、self.view)而且view为nil时会被调用,此方法主要用来负责创建UIViewController的view(重写loadView方法,并且不需要调用[super loadView])
这里要提一下 [super loadView],[super loadView]做了下面几件事。

  • 它会先去查找与UIViewController相关联的xib文件,通过加载xib文件来创建UIViewController的view,如果在初始化UIViewController指定了xib文件名,就会根据传入的xib文件名加载对应的xib文件,如果没有明显地传xib文件名,就会加载跟UIViewController同名的xib文件
  • 如果没有找到相关联的xib文件,就会创建一个空白的UIView,然后赋值给UIViewController的view属性
    综上,在需要自定义UIViewController的view时,可以通过重写loadView方法且不需要调用[super loadView]方法。

6.使用 drawRect有什么影响?

drawRect 方法依赖 Core Graphics 框架来进行自定义的绘制 缺点:它处理 touch 事件时每次按钮被点击后,都会用 setNeddsDisplay 进行强制重绘;而且不止一次,每次单点事件触发两次执行。这样的话从性能的角度来说,对 CPU 和内存来说都是欠佳的。特别是如果在我们的界面上有多个这样的UIButton 实例,那就会很糟糕了。这个方法的调用机制也是非常特别. 当你调用 setNeedsDisplay 方法时, UIKit 将会把当前图层标记为 dirty,但还是会显示原来的内容,直到下一次的视图渲染周期,才会将标记为 dirty 的图层重新建立 Core Graphics 上下文,然后将内存中的数据恢复出来, 再使用 CGContextRef 进行绘制

7.keyWindow 和 delegate的window有何区别

  • delegate.window 程序启动时设置的window对象。
  • keyWindow 这个属性保存了[windows]数组中的[UIWindow]对象,该对象最近被发送了[makeKeyAndVisible]消息
    一般情况下 delegate.window 和 keyWindow 是同一个对象,但不能保证keyWindow就是delegate.window,因为keyWindow会因为makeKeyAndVisible而变化,例如,程序中添加了一个悬浮窗口,这个时候keywindow就会变化。

WebView

1.说一下 JS 和 OC 互相调用的几种方式?

  • js调用oc的三种方式:

根据网页重定向截取字符串通过url scheme判断

替换方法.context[@“copyText”]

注入对象:遵守协议JSExport,设置context[@

  • oc调用js代码两种方式

通过webVIew调用 webView stringByEvaluatingJavaScriptFromString: 调用

通过JSContext调用[context evaluateScript:];

2.在使用 WKWedView 时遇到过哪些问题?

白屏问题,Cookie 问题,在WKWebView上直接使用NSURLProtocol无法拦截请求,在WKWebView 上通过loadRequ发起的post请求body数据被丢失,截屏问题等

消息传递的方式

1.KVC实现原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gyg1iuaD-1591231390707)(evernotecid://2CC0A8C7-733A-43F8-8428-C60C9C6C63B2/appyinxiangcom/21128449/ENResource/p395)]

  • KVC,键-值编码,使用字符串直接访问对象的属性。
  • 底层实现,当一个对象调用setValue方法时,方法内部会做以下操作:
    1.检查是否存在相应key的set方法,如果存在,就调用set方法
    2.如果set方法不存在,就会查找与key相同名称并且带下划线的成员属性,如果有,则直接给成员属性赋值
    3.如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值
    4.如果还没找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法

2.KVO的实现原理

  • KVO-键值观察机制,原理如下:
    1.当给A类添加KVO的时候,runtime动态的生成了一个子类NSKVONotifying_A,让A类的isa指针指向NSKVONotifying_A类,重写class方法,隐藏对象真实类信息
    2.重写监听属性的setter方法,在setter方法内部调用了Foundation的_NSSetObjectValueAndNotify 函数
  1. ()_NSSetObjectValueAndNotify函数内部
    • 首先会调用 willChangeValueForKey
    • 然后给属性赋值
    • 最后调用 didChangeValueForKey
    • 最后调用 observer 的 observeValueForKeyPath 去告诉监听器属性值发生了改变
      4.重写了dealloc做一些 KVO 内存释放

3.如何手动触发KVO方法

  • 手动调用willChangeValueForKey和didChangeValueForKey方法
  • 键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangeValueForKey。在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就 会记录旧的值。而当改变发生后, didChangeValueForKey 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了 有人可能会问只调用didChangeValueForKey方法可以触发KVO方法,其实是不能的,因为willChangeValueForKey: 记录旧的值,如果不记录旧的值,那就没有改变一说了

4.通知和代理有什么区别

  • 通知是观察者模式,适合一对多的场景
  • 代理模式适合一对一的反向传值
  • 通知耦合度低,代理的耦合度高

5.block和delegate的区别

  • delegate运行成本低,block的运行成本高
  • block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是加计数,使用完或者block置nil后才消除。delegate只是保存了一个对象指针,直接回调,没有额外消耗。就像C的函数指针,只多做了一个查表动作。
  • delegate更适用于多个回调方法(3个以上),block则适用于1,2个回调时。

6.为什么Block用copy关键字

Block在没有使用外部变量时,内存存在全局区,然而,当Block在使用外部变量的时候,内存是存在于栈区,当Block copy之后,是存在堆区的。存在于栈区的特点是对象随时有可能被销毁,一旦销毁在调用的时候,就会造成系统的崩溃。所以Block要用copy关键字。

组件化

1.组件化有什么好处?

  • 业务分层、解耦,使代码变得可维护;
  • 有效的拆分、组织日益庞大的工程代码,使工程目录变得可维护;
  • 便于各业务功能拆分、抽离,实现真正的功能复用;
  • 业务隔离,跨团队开发代码控制和版本风险控制的实现;
  • 模块化对代码的封装性、合理性都有一定的要求,提升开发同学的设计能力;
    +在维护好各级组件的情况下,随意组合满足不同客户需求;(只需要将之前的多个业务组件模块在新的主App中进行组装即可快速迭代出下一个全新App)

2.你是如何组件化解耦的?

  • 分层
    1.基础功能组件:按功能分库,不涉及产品业务需求,跟库Library类似,通过良好的接口拱上层业务组件调用;不写入产品定制逻辑,通过扩展接口完成定制;
    2.基础UI组件:各个业务模块依赖使用,但需要保持好定制扩展的设计
    3.业务组件:业务功能间相对独立,相互间没有Model共享的依赖;业务之间的页面调用只能通过UIBus进行跳转;业务之间的逻辑Action调用只能通过服务提供;
  • 中间件:target-action,url-block,protocol-class

3.为什么CTMediator方案优于基于Router的方案?

Router的缺点:

  • 在组件化的实施过程中,注册URL并不是充分必要条件。组件是不需要向组件管理器注册URL的,注册了URL之后,会造成不必要的内存常驻。注册URL的目的其实是一个服务发现的过程,在iOS领域中,服务发现的方式是不需要通过主动注册的,使用runtime就可以了。另外,注册部分的代码的维护是一个相对麻烦的事情,每一次支持新调用时,都要去维护一次注册列表。如果有调用被弃用了,是经常会忘记删项目的。runtime由于不存在注册过程,那就也不会产生维护的操作,维护成本就降低了。 由于通过runtime做到了服务的自动发现,拓展调用接口的任务就仅在于各自的模块,任何一次新接口添加,新业务添加,都不必去主工程做操作,十分透明。
  • 在iOS领域里,一定是组件化的中间件为openURL提供服务,而不是openURL方式为组件化提供服务。如果在给App实施组件化方案的过程中是基于openURL的方案的话,有一个致命缺陷:非常规对象(不能被字符串化到URL中的对象,例如UIImage)无法参与本地组件间调度。 在本地调用中使用URL的方式其实是不必要的,如果业务工程师在本地间调度时需要给出URL,那么就不可避免要提供params,在调用时要提供哪些params是业务工程师很容易懵逼的地方。
  • 为了支持传递非常规参数,蘑菇街的方案采用了protocol,这个会侵入业务。由于业务中的某个对象需要被调用,因此必须要符合某个可被调用的protocol,然而这个protocol又不存在于当前业务领域,于是当前业务就不得不依赖public Protocol。这对于将来的业务迁移是有非常大的影响的。
    CTMediator的优点:
  • 调用时,区分了本地应用调用和远程应用调用。本地应用调用为远程应用调用提供服务。
  • 组件仅通过Action暴露可调用接口,模块与模块之间的接口被固化在了Target-Action这一层,避免了实施组件化的改造过程中,对Business的侵入,同时也提高了组件化接口的可维护性。
  • 方便传递各种类型的参数。

4.基于CTMediator的组件化方案,有哪些核心组成?

  • CTMediator中间件:集成就可以了
  • 模块Target_%@:模块的实现及提供对外的方法调用Action_methodName,需要传参数时,都统一以NSDictionary的形式传入
  • CTMediator+%@扩展:扩展里声明了模块业务的对外接口,参数明确,这样外部调用者可以很容易理解如何调用接口。

内存管理

1.什么情况使用weak关键字,相比assign有什么不同?

  • 什么情况使用 weak 关键字?
    在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性
    自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。在下文也有论述:《IBOutlet连出来的视图属性为什么可以被设置成weak?》
  • 不同点:
    weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。 而 assign 的“设置方法”只会执行针对“纯量类型” (scalar type,例如 CGFloat 或 NSlnteger 等)的简单赋值操作。
    assign 可以用非 OC 对象,而 weak 必须用于 OC 对象

2.如何让自己的类用copy修饰符?如何重写带copy关键字的setter?

  • 若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。
    具体步骤:
    需声明该类遵从 NSCopying 协议
    实现 NSCopying 协议。该协议只有一个方法:
- (id)copyWithZone:(NSZone *)zone;
1

注意:一提到让自己的类用 copy 修饰符,我们总是想覆写copy方法,其实真正需要实现的却是 “copyWithZone” 方法。

  • 重写带 copy 关键字的 setter,例如:
- (void)setName:(NSString *)name {
    //[_name release];
    _name = [name copy];
}

3.深拷贝与浅拷贝

浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
当对象中存在指针成员时,除了在复制对象时需要考虑自定义拷贝构造函数,还应该考虑以下两种情形:

  • 当函数的参数为对象时,实参传递给形参的实际上是实参的一个拷贝对象,系统自动通过拷贝构造函数实现;
  • 当函数的返回值为一个对象时,该对象实际上是函数内对象的一个拷贝,用于返回函数调用处。
    copy方法:如果是非可扩展类对象,则是浅拷贝。如果是可扩展类对象,则是深拷贝。
    mutableCopy方法:无论是可扩展类对象还是不可扩展类对象,都是深拷贝。

@property的本质是什么?ivar、getter、setter是如何生成并添加到这个类中的

  • @property 的本质是实例变量(ivar)+存取方法(access method = getter + setter),即 @property = ivar + getter + setter;
    “属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。
  • ivar、getter、setter 是自动合成这个类中的
    完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,这个过程由编译 器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码 getter、setter 之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。在前例中,会生成两个实例变量,其名称分别为 _firstName 与 _lastName。也可以在类的实现代码里通过 @synthesize 语法来指定实例变量的名字.

@protocol和category中如何使用@property

  • 在 protocol 中使用 property 只会生成 setter 和 getter 方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性
  • category 使用 @property 也是只会生成 setter 和 getter 方法的声明,如果我们真的需要给 category 增加属性的实现,需要借助于运行时的两个函数:objc_setAssociatedObject和objc_getAssociatedObject

6.简要说一下@autoreleasePool的数据结构??

简单说是双向链表,每张链表头尾相接,有 parent、child指针
每创建一个池子,会在首部创建一个 哨兵 对象,作为标记
最外层池子的顶端会有一个next指针。当链表容量满了,就会在链表的顶端,并指向下一张表。

BAD_ACCESS在什么情况下出现?

访问了悬垂指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。 死循环

8.使用CADisplayLink、NSTimer有什么注意点?

CADisplayLink、NSTimer会造成循环引用,可以使用YYWeakProxy或者为CADisplayLink、NSTimer添加block方法解决循环引用

9.iOS内存分区情况

  • 栈区(Stack)
    由编译器自动分配释放,存放函数的参数,局部变量的值等
    栈是向低地址扩展的数据结构,是一块连续的内存区域
  • 堆区(Heap)
    由程序员分配释放
    是向高地址扩展的数据结构,是不连续的内存区域
  • 全局区
    全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域
    程序结束后由系统释放
  • 常量区
    常量字符串就是放在这里的
    程序结束后由系统释放
    代码区
    存放函数体的二进制代码
  • 注:
    在 iOS 中,堆区的内存是应用程序共享的,堆中的内存分配是系统负责的
    系统使用一个链表来维护所有已经分配的内存空间(系统仅仅记录,并不管理具体的内容)
    变量使用结束后,需要释放内存,OC 中是判断引用计数是否为 0,如果是就说明没有任何变量使用该空间,那么系统将其回收
    当一个 app 启动后,代码区、常量区、全局区大小就已经固定,因此指向这些区的指针不会产生崩溃性的错误。而堆区和栈区是时时刻刻变化的(堆的创建销毁,栈的弹入弹出),所以当使用一个指针指向这个区里面的内存时,一定要注意内存是否已经被释放,否则会产生程序崩溃(也即是野指针报错)

10.循环引用

循环引用的实质:多个对象相互之间有强引用,不能释放让系统回收。
如何解决循环引用?

  • 1.避免产生循环引用,通常是将 strong 引用改为 weak 引用。 比如在修饰属性时用weak 在block内调用对象方法时,使用其弱引用,这里可以使用两个宏
    #define WS(weakSelf) __weak __typeof(&*self)weakSelf = self; // 弱引用
    #define ST(strongSelf) __strong __typeof(&*self)strongSelf = weakSelf; //使用这个要先声明weakSelf 还可以使用__block来修饰变量 在MRC下,__block不会增加其引用计数,避免了循环引用 在ARC下,__block修饰对象会被强引用,无法避免循环引用,需要手动解除。

  • 2.在合适时机去手动断开循环引用。 通常我们使用第一种。

  • 代理(delegate)循环引用属于相互循环引用
    delegate 是iOS中开发中比较常遇到的循环引用,一般在声明delegate的时候都要使用弱引用 weak,或者assign,当然怎么选择使用assign还是weak,MRC的话只能用assign,在ARC的情况下最好使用weak,因为weak修饰的变量在释放后自动指向nil,防止野指针存在

  • NSTimer循环引用属于相互循环使用
    在控制器内,创建NSTimer作为其属性,由于定时器创建后也会强引用该控制器对象,那么该对象和定时器就相互循环引用了。 如何解决呢? 这里我们可以使用手动断开循环引用: 如果是不重复定时器,在回调方法里将定时器invalidate并置为nil即可。 如果是重复定时器,在合适的位置将其invalidate并置为nil即可

  • 3.block循环引用
    一个简单的例子:

@property (copy, nonatomic) dispatch_block_t myBlock;
@property (copy, nonatomic) NSString *blockString;

- (void)testBlock {
    self.myBlock = ^() {
        NSLog(@"%@",self.blockString);
    };
}

由于block会对block中的对象进行持有操作,就相当于持有了其中的对象,而如果此时block中的对象又持有了该block,则会造成循环引用。 解决方案就是使用__weak修饰self即可

__weak typeof(self) weakSelf = self;

self.myBlock = ^() {
        NSLog(@"%@",weakSelf.blockString);
 };

并不是所有block都会造成循环引用。 只有被强引用了的block才会产生循环引用 而比如dispatch_async(dispatch_get_main_queue(), ^{}),[UIView animateWithDuration:1 animations:^{}]这些系统方法等 或者block并不是其属性而是临时变量,即栈block

[self testWithBlock:^{
    NSLog(@"%@",self);
}];

- (void)testWithBlock:(dispatch_block_t)block {
    block();
}

还有一种场景,在block执行开始时self对象还未被释放,而执行过程中,self被释放了,由于是用weak修饰的,那么weakSelf也被释放了,此时在block里访问weakSelf时,就可能会发生错误(向nil对象发消息并不会崩溃,但也没任何效果)。 对于这种场景,应该在block中对 对象使用__strong修饰,使得在block期间对 对象持有,block执行结束后,解除其持有。

__weak typeof(self) weakSelf = self;

self.myBlock = ^() {

        __strong __typeof(self) strongSelf = weakSelf;

        [strongSelf test];
 };
<<<<<<< HEAD

数据存储

1.iOS 开发中数据持久性有哪几种?

iOS本地数据保存有多种方式,比如NSUserDefaults、归档、文件保存、数据库、CoreData、KeyChain(钥匙串)等多种方式。其中KeyChain(钥匙串)是保存到沙盒范围以外的地方,也就是与沙盒无关。

2.FMDB数据结构变化升级

  • 使用columnExists:inTableWithName方法判断数据表中是否存在字段
  • 如果不存在,则添加, 如:向bbb表中添加aaa字段 -> ALTER TABLE bbb ADD ‘aaa’ TEXT

多线程

1.进程与线程

  • 进程:
    1.进程是一个具有一定独立功能的程序关于某次数据集合的一次运行活动,它是操作系统分配资源的基本单元.
    2.进程是指在系统中正在运行的一个应用程序,就是一段程序的执行过程,我们可以理解为手机上的一个app.
    3.每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内,拥有独立运行所需的全部资源

  • 线程
    1.程序执行流的最小单元,线程是进程中的一个实体.
    2.一个进程要想执行任务,必须至少有一条线程.应用程序启动的时候,系统会默认开启一条线程,也就是主线程

  • 进程和线程的关系
    1.线程是进程的执行单元,进程的所有任务都在线程中执行
    2.线程是 CPU 分配资源和调度的最小单位
    3.一个程序可以对应多个进程(多进程),一个进程中可有多个线程,但至少要有一条线程
    4.同一个进程内的线程共享进程资源

2.什么是多线程?

  • 多线程的实现原理:事实上,同一时间内单核的CPU只能执行一个线程,多线程是CPU快速的在多个线程之间进行切换(调度),造成了多个线程同时执行的假象。
  • 如果是多核CPU就真的可以同时处理多个线程了。
  • 多线程的目的是为了同步完成多项任务,通过提高系统的资源利用率来提高系统的效率。

3.多线程的优点和缺点

  • 优点:
    能适当提高程序的执行效率
    能适当提高资源利用率(CPU、内存利用率)

  • 缺点:
    开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
    线程越多,CPU在调度线程上的开销就越大
    程序设计更加复杂:比如线程之间的通信、多线程的数据共享

4.多线程的 并行 和 并发 有什么区别?

  • 并行:充分利用计算机的多核,在多个线程上同步进行
  • 并发:在一条线程上通过快速切换,让人感觉在同步进行

5.iOS中实现多线程的几种方案,各自有什么特点?

  • NSThread 面向对象的,需要程序员手动创建线程,但不需要手动销毁。子线程间通信很难。
  • GCD c语言,充分利用了设备的多核,自动管理线程生命周期。比NSOperation效率更高。
  • NSOperation 基于gcd封装,更加面向对象,比gcd多了一些功能。

6.多个网络请求完成后执行下一步

  • 使用GCD的dispatch_group_t
    创建一个dispatch_group_t
    每次网络请求前先dispatch_group_enter,请求回调后再dispatch_group_leave,enter和leave必须配合使用,有几次enter就要有几次leave,否则group会一直存在。
    当所有enter的block都leave后,会执行dispatch_group_notify的block。
NSString *str = @"http://xxxx.com/";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];

dispatch_group_t downloadGroup = dispatch_group_create();
for (int i=0; i<10; i++) {
    dispatch_group_enter(downloadGroup);
    
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"%d---%d",i,i);
        dispatch_group_leave(downloadGroup);
    }];
    [task resume];
}

dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
    NSLog(@"end");
});
  • 使用GCD的信号量dispatch_semaphore_t
    dispatch_semaphore信号量为基于计数器的一种多线程同步机制。如果semaphore计数大于等于1,计数-1,返回,程序继续运行。如果计数为0,则等待。dispatch_semaphore_signal(semaphore)为计数+1操作,dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)为设置等待时间,这里设置的等待时间是一直等待。
    创建semaphore为0,等待,等10个网络请求都完成了,dispatch_semaphore_signal(semaphore)为计数+1,然后计数-1返回
NSString *str = @"http://xxxx.com/";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];

dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i=0; i<10; i++) {
    
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"%d---%d",i,i);
        count++;
        if (count==10) {
            dispatch_semaphore_signal(sem);
            count = 0;
        }
    }];
    [task resume];
}
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"end");
});

7.多个网络请求顺序执行后执行下一步

  • 使用信号量semaphore
    每一次遍历,都让其dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER),这个时候线程会等待,阻塞当前线程,直到dispatch_semaphore_signal(sem)调用之后
NSString *str = @"http://www.xxx.com";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];

dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i=0; i<10; i++) {
    
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
        NSLog(@"%d---%d",i,i);
        dispatch_semaphore_signal(sem);
    }];
    
    [task resume];
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}

dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"end");
});

8.异步操作两组数据时, 执行完第一组之后, 才能执行第二组

  • 这里使用dispatch_barrier_async栅栏方法即可实现
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    NSLog(@"第一次任务的主线程为: %@", [NSThread currentThread]);
});

dispatch_async(queue, ^{
    NSLog(@"第二次任务的主线程为: %@", [NSThread currentThread]);
});

dispatch_barrier_async(queue, ^{
    NSLog(@"第一次任务, 第二次任务执行完毕, 继续执行");
});

dispatch_async(queue, ^{
    NSLog(@"第三次任务的主线程为: %@", [NSThread currentThread]);
});

dispatch_async(queue, ^{
    NSLog(@"第四次任务的主线程为: %@", [NSThread currentThread]);
});

9.多线程中的死锁?

死锁是由于多个线程(进程)在执行过程中,因为争夺资源而造成的互相等待现象,你可以理解为卡主了。产生死锁的必要条件有四个:

  • 互斥条件 : 指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
  • 请求和保持条件 : 指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
  • 不可剥夺条件 : 指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
  • 环路等待条件 : 指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
    最常见的就是 同步函数 + 主队列 的组合,本质是队列阻塞。
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"2");
});

NSLog(@"1");
// 什么也不会打印,直接报错

10.GCD执行原理?

  • GCD有一个底层线程池,这个池中存放的是一个个的线程。之所以称为“池”,很容易理解出这个“池”中的线程是可以重用的,当一段时间后这个线程没有被调用胡话,这个线程就会被销毁。注意:开多少条线程是由底层线程池决定的(线程建议控制再3~5条),池是系统自动来维护,不需要我们程序员来维护(看到这句话是不是很开心?) 而我们程序员需要关心的是什么呢?我们只关心的是向队列中添加任务,队列调度即可。
  • 如果队列中存放的是同步任务,则任务出队后,底层线程池中会提供一条线程供这个任务执行,任务执行完毕后这条线程再回到线程池。这样队列中的任务反复调度,因为是同步的,所以当我们用currentThread打印的时候,就是同一条线程。
  • 如果队列中存放的是异步的任务,(注意异步可以开线程),当任务出队后,底层线程池会提供一个线程供任务执行,因为是异步执行,队列中的任务不需等待当前任务执行完毕就可以调度下一个任务,这时底层线程池中会再次提供一个线程供第二个任务执行,执行完毕后再回到底层线程池中。
  • 这样就对线程完成一个复用,而不需要每一个任务执行都开启新的线程,也就从而节约的系统的开销,提高了效率。在iOS7.0的时候,使用GCD系统通常只能开58条线程,iOS8.0以后,系统可以开启很多条线程,但是实在开发应用中,建议开启线程条数:35条最为合理。

动画

1.UIView动画与核心动画的区别?

  • 核心动画只作用在layer.
  • 核心动画修改的值都是假像.它的真实位置没有发生变化.
  • 当需要与用户进行交互时用UIView动画,不需要与用户进行交互时两个都可以.

2.当我们要做一些基于 CALayer 的动画时,有时需要设置 layer 的锚点来配合动画,这时候我们需要注意什么?

  • 需要注意的是设置锚点会引起原来 position 的变化,可能会发生不符合预期的行为,所以要做一下转化,示例代码如下:
// 为 layer 的动画设置不同的 anchor point,但是又不想改变 view 原来的 position,则需要做一些转换。
- (void)setAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)view {
    // 分别计算原来锚点和将更新的锚点对应的座标点,这些座标点是相对该 view 内部座标系的。
    CGPoint oldPoint = CGPointMake(view.bounds.size.width * view.layer.anchorPoint.x,
                                   view.bounds.size.height * view.layer.anchorPoint.y);
    CGPoint newPoint = CGPointMake(view.bounds.size.width * anchorPoint.x,
                                   view.bounds.size.height * anchorPoint.y);
    
    // 如果当前 view 有做过 transform,这里要同步计算。
    oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform);
    newPoint = CGPointApplyAffineTransform(newPoint, view.transform);
    
    // position 是当前 view 的 anchor point 在其父 view 的位置。
    CGPoint position = view.layer.position;
    // anchor point 的改变会造成 position 的改变,从而影响 view 在其父 view 的位置,这里把这个位移给计算回来。
    position.x = position.x + newPoint.x - oldPoint.x;
    position.y = position.y + newPoint.y - oldPoint.y;
    
    view.translatesAutoresizingMaskIntoConstraints = YES;
    view.layer.anchorPoint = anchorPoint; // 设置了新的 anchor point 会改变位置。
    view.layer.position = position; // 通过在 position 上做逆向偏移,把位置给移回来。
}

图像处理

1.图像的压缩方式

压缩图片质量

  • 一般情况下使用UIImageJPEGRepresentation或UIImagePNGRepresentation方法实现。
  • 压缩图片尺寸
  • 一般通过指定压缩的大小对图像进行重绘

2.如何计算图片加载内存中所占的大小

  • 图片内存大小的计算公式 宽度 * 高度 * bytesPerPixel/8。
  • bytesPerPixel : 每个像素所占的字节数。
  • RGBA颜色空间下 每个颜色分量由32位组成
  • 所以一般图片的计算公式是 wxhx4

Runtime

RunLoop

网络

算法

项目框架

1.MVC、MVP、MVVM模式

MVC(Model、View、Controller)

MVC是比较直观的架构模式,最核心的就是通过Controller层来进行调控,首先看一下官方提供的MVC示意图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5md7GAWy-1591231390709)(evernotecid://2CC0A8C7-733A-43F8-8428-C60C9C6C63B2/appyinxiangcom/21128449/ENResource/p375)]

  • Model和View永远不能相互通信,只能通过Controller传递
  • Controller可以直接与Model对话(读写调用Model),Model通过NOtification和KVO机制与Controller间接通信

Controller可以直接与View对话,通过IBoutlet直接操作View,IBoutlet直接对应View的控件(例如创建一个Button:需声明一个 IBOutlet UIButton * btn),View通过action向Controller报告时间的发生(用户点击了按钮)。Controller是View的直接数据源

  • 优点:对于混乱的项目组织方式,有了一个明确的组织方式。通过Controller来掌控全局,同时将View展示和Model的变化分开
  • 缺点:愈发笨重的Controller,随着业务逻辑的增加,大量的代码放进Controller,导致
    Controller越来越臃肿,堆积成千上万行代码,后期维护起来费时费力

MVP(Model、View、Presenter)

MVP模式是MVC模式的一个演化版本,其中Model与MVC模式中Model层没有太大区别,主要提供数据存储功能,一般都是用来封装网络获取的json数据;View与MVC中的View层有一些差别,MVP中的View层可以是viewController、view等控件;Presenter层则是作为Model和View的中介,从Model层获取数据之后传给View。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9i9r5dOi-1591231390710)(evernotecid://2CC0A8C7-733A-43F8-8428-C60C9C6C63B2/appyinxiangcom/21128449/ENResource/p373)]
从上图可以看出,从MVC模式中增加了Presenter层,将UIViewController中复杂的业务逻辑、网络请求等剥离出来。

  • 优点 模型和视图完全分离,可以做到修改视图而不影响模型;更高效的使用模型,View不依赖Model,可以说VIew能做到对业务逻辑完全分离
  • 缺点 Presenter中除了处理业务逻辑以外,还要处理View-Model两层的协调,也会导致Presenter层的臃肿

MVVM(Model、Controller/View、ViewModel)

在MVVM中,view和ViewCOntroller联系在一起,我们把它们视为一个组件,view和ViewController都不能直接引用model,而是引用是视图模型即ViewModel。 viewModel是一个用来放置用户输入验证逻辑、视图显示逻辑、网络请求等业务逻辑的地方,这样的设计模式,会轻微增加代码量,但是会减少代码的复杂性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZRJIh1Gx-1591231390711)(evernotecid://2CC0A8C7-733A-43F8-8428-C60C9C6C63B2/appyinxiangcom/21128449/ENResource/p374)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PDmihaTY-1591231390712)(evernotecid://2CC0A8C7-733A-43F8-8428-C60C9C6C63B2/appyinxiangcom/21128449/ENResource/p372)]

  • 优点 VIew可以独立于Model的变化和修改,一个ViewModel可以绑定到不同的View上,降低耦合,增加重用
  • 缺点 过于简单的项目不适用、大型的项目视图状态较多时构建和维护成本太大
    合理的运用架构模式有利于项目、团队开发工作,但是到底选择哪个设计模式,哪种设计模式更好,就像本文开头所说,不同的设计模式,只是让不同的场景有了更多的选择方案。根据项目场景和开发需求,选择最合适的解决方案。

2.关于RAC你有怎样运用到解决不同API依赖关系

信号的依赖:使用场景是当信号A执行完才会执行信号B,和请求的依赖很类似,例如请求A请求完毕才执行请求B,我们需要注意信号A必须要执行发送完成信号,否则信号B无法执行

//这相当于网络请求中的依赖,必须先执行完信号A才会执行信号B
//经常用作一个请求执行完毕后,才会执行另一个请求
//注意信号A必须要执行发送完成信号,否则信号B无法执行
RACSignal * concatSignal = [self.signalA concat:self.signalB]
//这里我们是对这个拼接信号进行订阅
[concatSignal subscribeNext:^(id x) {
	NSLog(@"%@",x);
}];
1

3.微服务架构设想。

微服务架构具有以下优势:
1.灵活和独立的可扩展性
灵活扩展是微服务架构的主要优势之一。与单片架构不同,每个模块都可以水平扩展并独立于其他模块。因此,微服务架构非常适合大型项目。
2.独立技术堆栈
在微服务架构中,软件工程师有机会使用各种工具和技术构建APP。代码可以用不同的编程语言编写,这为APP开发过程增加了更多的灵活性。
3.更好的故障隔离
如果一个服务失败,它不会影响其他服务的功能。与其他体系结构样式相比,在微服务中,系统继续工作,单片模式下的问题会影响整个APP。
4.易于部署和集成
虽然即使是小代码更改的情况下,开发人员也必须再次部署APP,但在微服务架构中,部署变得更快更轻松。
由于所有服务都是围绕单一业务流程构建的,因此程序员不必修改和重新部署整个APP,只需要您需要的区域。因此,改进产品也比较简单。
微服务可以通过全自动部署机制独立部署。此外,通过使用开源持续集成工具,开发人员大大简化了与第三方服务的集成。
5.容易理解
微服务体系结构的另一个优点是很容易理解系统是如何工作的以及它是如何开发的。当一个新的团队成员来到这个项目并且必须快速钻研它时,这特别有用。
那么在iOS中如何借鉴这种思想去构建我们的App呢?是需要我们开发者自己去不断探索的

设计模式

1.iOS有哪些常见的设计模式?

  • 单例模式:单例保证了应用程序的生命周期内仅有一个该类的实例对象,而且易于外界访问.在ios sdk中,UIApplication, NSBundle, NSNotificationCenter, NSFileManager, NSUserDefault, NSURLCache等都是单例.
  • 委托模式:委托Delegate是协议的一种,通过@protocol方式实现,常见的有tableView,textField等。
  • 观察者模式:观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。在iOS中,观察者模式的具体实现有两种: 通知机制(notification)和KVO机制(Key-value Observing)

2.单例会有什么弊端?

  • 主要优点:
    1、提供了对唯一实例的受控访问。
    2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
    3、允许可变数目的实例。
  • 主要缺点:
    1、由於单利模式中没有抽象层,因此单例类的扩展有很大的困难。
    2、单例类的职责过重,在一定程度上违背了“单一职责原则”。
    3、滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
#import "InstanceManager.h"
@interface InstanceManager() <NSCopying,NSMutableCopying>
@end
static InstanceManager *_instance = nil;
@implementation InstanceManager
    
+ (instancetype)sharedInstance {
            return [[self alloc] init];
        }
        
+ (id)allocWithZone:(struct _NSZone *)zone {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
        if (!_instance) {
            _instance = [super allocWithZone:zone];
            }
        });
        return _instance;
    }
    
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
            return _instance;
        }
        
- (nonnull id)mutableCopyWithZone:(nullable NSZone *)zone {
            return _instance;
        }
        
@end

3.编程中的六大设计原则?

1.单一职责原则
通俗地讲就是一个类只做一件事
CALayer:动画和视图的显示。
UIView:只负责事件传递、事件响应。
2.开闭原则
对修改关闭,对扩展开放。 要考虑到后续的扩展性,而不是在原有的基础上来回修改
3.接口隔离原则
使用多个专门的协议、而不是一个庞大臃肿的协议,如 UITableviewDelegate + UITableViewDataSource
4.依赖倒置原则
抽象不应该依赖于具体实现、具体实现可以依赖于抽象。 调用接口感觉不到内部是如何操作的
5.里氏替换原则
父类可以被子类无缝替换,且原有的功能不受任何影响 如:KVO
6.迪米特法则
一个对象应当对其他对象尽可能少的了解,实现高聚合、低耦合

数据安全及加密

1.对称加密和非对称加密的区别?

1、对称加密又称公开密钥加密,加密和解密都会用到同一个密钥,如果密钥被攻击者获得,此时加密就失去了意义。常见的对称加密算法有DES、3DES、AES、Blowfish、IDEA、RC5、RC6。
2、非对称加密又称共享密钥加密,使用一对非对称的密钥,一把叫做私有密钥,另一把叫做公有密钥;公钥加密只能用私钥来解密,私钥加密只能用公钥来解密。常见的公钥加密算法有:RSA、ElGamal、揹包算法、Rabin(RSA的特例)、迪菲-赫尔曼密钥交换协议中的公钥加密算法、椭圆曲线加密算法)

2.简述 SSL 加密的过程用了哪些加密方法,为何这么作?

SSL 加密,在过程中实际使用了 对称加密 和 非对称加密 的结合。主要的考虑是先使用 非对称加密 进行连接,这样做是为了避免中间人攻击秘钥被劫持,但是 非对称加密 的效率比较低。所以一旦建立了安全的连接之后,就可以使用轻量的 对称加密。

3.iOS的签名机制是怎么样的

  • 签名机制:
    先将应用内容通过摘要算法,得到摘要
    再用私钥对摘要进行加密得到密文
    将源文本、密文、和私钥对应的公钥一并发布
  • 验证流程:
    查看公钥是否是私钥方的
    然后用公钥对密文进行解密得到摘要
    将APP用同样的摘要算法得到摘要,两个摘要进行比对,如果相等那么一切正常

性能优化

1.造成tableView卡顿的原因有哪些?

  • 1.最常用的就是cell的重用, 注册重用标识符
    如果不重用cell时,每当一个cell显示到屏幕上时,就会重新创建一个新的cell
    如果有很多数据的时候,就会堆积很多cell。
    如果重用cell,为cell创建一个ID,每当需要显示cell 的时候,都会先去缓冲池中寻找可循环利用的cell,如果没有再重新创建cell
  • 2.避免cell的重新布局
    cell的布局填充等操作 比较耗时,一般创建时就布局好
    如可以将cell单独放到一个自定义类,初始化时就布局好
  • 3.提前计算并缓存cell的属性及内容
    当我们创建cell的数据源方法时,编译器并不是先创建cell 再定cell的高度
    而是先根据内容一次确定每一个cell的高度,高度确定后,再创建要显示的cell,滚动时,每当cell进入凭虚都会计算高度,提前估算高度告诉编译器,编译器知道高度后,紧接着就会创建cell,这时再调用高度的具体计算方法,这样可以方式浪费时间去计算显示以外的cell
  • 4.减少cell中控件的数量
    尽量使cell得布局大致相同,不同风格的cell可以使用不用的重用标识符,初始化时添加控件,
    不适用的可以先隐藏
  • 5.不要使用ClearColor,无背景色,透明度也不要设置为0
    渲染耗时比较长
  • 6.使用局部更新
    如果只是更新某组的话,使用reloadSection进行局部更
  • 7.加载网络数据,下载图片,使用异步加载,并缓存
  • 8.少使用addView 给cell动态添加view
  • 9.按需加载cell,cell滚动很快时,只加载范围内的cell
  • 10.不要实现无用的代理方法,tableView只遵守两个协议
  • 11.缓存行高:estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同时存在,这两者同时存在才会出现“窜动”的bug。所以我的建议是:只要是固定行高就写预估行高来减少行高调用次数提升性能。如果是动态行高就不要写预估方法了,用一个行高的缓存字典来减少代码的调用次数即可
  • 12.不要做多余的绘制工作。在实现drawRect:的时候,它的rect参数就是需要绘制的区域,这个区域之外的不需要进行绘制。例如上例中,就可以用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判断是否需要绘制image和text,然后再调用绘制方法。
  • 13.预渲染图像。当新的图像出现时,仍然会有短暂的停顿现象。解决的办法就是在bitmap context里先将其画一遍,导出成UIImage对象,然后再绘制到屏幕;
  • 14.使用正确的数据结构来存储数据。

2.如何提升 tableview 的流畅度?

  • 本质上是降低 CPU、GPU 的工作,从这两个大的方面去提升性能。
    CPU:对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码、图像的绘制
    GPU:纹理的渲染
  • 卡顿优化在 CPU 层面
    尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用 CALayer 取代 UIView
    不要频繁地调用 UIView 的相关属性,比如 frame、bounds、transform 等属性,尽量减少不必要的修改
    尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性
    Autolayout 会比直接设置 frame 消耗更多的 CPU 资源
    图片的 size 最好刚好跟 UIImageView 的 size 保持一致
    控制一下线程的最大并发数量
    尽量把耗时的操作放到子线程
    文本处理(尺寸计算、绘制)
    图片处理(解码、绘制)
    卡顿优化在 GPU层面
  • 尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示
    GPU能处理的最大纹理尺寸是 4096x4096,一旦超过这个尺寸,就会占用 CPU 资源进行处理,所以纹理尽量不要超过这个尺寸
    尽量减少视图数量和层次
    减少透明的视图(alpha<1),不透明的就设置 opaque 为 YES
    尽量避免出现离屏渲染
  • iOS 保持界面流畅的技巧
    1.预排版,提前计算
    在接收到服务端返回的数据后,尽量将 CoreText 排版的结果、单个控件的高度、cell 整体的高度提前计算好,将其存储在模型的属性中。需要使用时,直接从模型中往外取,避免了计算的过程。
    尽量少用 UILabel,可以使用 CALayer 。避免使用 AutoLayout 的自动布局技术,采取纯代码的方式
    2.预渲染,提前绘制
    例如圆形的图标可以提前在,在接收到网络返回数据时,在后台线程进行处理,直接存储在模型数据里,回到主线程后直接调用就可以了
    避免使用 CALayer 的 Border、corner、shadow、mask 等技术,这些都会触发离屏渲染。
    3.异步绘制
    4.全局并发线程
    5.高效的图片异步加载

3.APP启动时间应从哪些方面优化?

App启动时间可以通过xcode提供的工具来度量,在Xcode的Product->Scheme–>Edit Scheme->Run->Auguments中,将环境变量DYLD_PRINT_STATISTICS设为YES,优化需以下方面入手
dylib loading time

  • 核心思想是减少dylibs的引用
    合并现有的dylibs(最好是6个以内)
    使用静态库
  • rebase/binding time
    核心思想是减少DATA块内的指针
    减少Object C元数据量,减少Objc类数量,减少实例变量和函数(与面向对象设计思想冲突)
    减少c++虚函数
    多使用Swift结构体(推荐使用swift)
  • ObjC setup time
    核心思想同上,这部分内容基本上在上一阶段优化过后就不会太过耗时
    initializer time
  • 使用initialize替代load方法
    减少使用c/c++的attribute((constructor));推荐使用dispatch_once() pthread_once() std:once()等方法
    推荐使用swift
    不要在初始化中调用dlopen()方法,因为加载过程是单线程,无锁,如果调用dlopen则会变成多线程,会开启锁的消耗,同时有可能死锁
    不要在初始化中创建线程

4.如何降低APP包的大小

降低包大小需要从两方面着手

  • 可执行文件
    编译器优化:Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default 设置为 YES,去掉异常支持,Enable C++ Exceptions、Enable Objective-C Exceptions 设置为 NO, Other C Flags 添加 -fno-exceptions 利用 AppCode 检测未使用的代码:菜单栏 -> Code -> Inspect Code
    编写LLVM插件检测出重复代码、未被调用的代码
  • 资源(图片、音频、视频 等)
    优化的方式可以对资源进行无损的压缩
    去除没有用到的资源: https://github.com/tinymind/LSUnusedResources

5.如何检测离屏渲染与优化

检测,通过勾选Xcode的Debug->View Debugging–>Rendering->Run->Color Offscreen-Rendered Yellow项。
优化,如阴影,在绘制时添加阴影的路径

6.怎么检测图层混合

1、模拟器debug中color blended layers红色区域表示图层发生了混合

2、Instrument-选中Core Animation-勾选Color Blended Layers

避免图层混合:

  • 确保控件的opaque属性设置为true,确保backgroundColor和父视图颜色一致且不透明
    如无特殊需要,不要设置低于1的alpha值
  • 确保UIImage没有alpha通道
    UILabel图层混合解决方法:
    iOS8以后设置背景色为非透明色并且设置label.layer.masksToBounds=YES让label只会渲染她的实际size区域,就能解决UILabel的图层混合问题
    iOS8 之前只要设置背景色为非透明的就行
    为什么设置了背景色但是在iOS8上仍然出现了图层混合呢?
    UILabel在iOS8前后的变化,在iOS8以前,UILabel使用的是CALayer作为底图层,而在iOS8开始,UILabel的底图层变成了_UILabelLayer,绘制文本也有所改变。在背景色的四周多了一圈透明的边,而这一圈透明的边明显超出了图层的矩形区域,设置图层的masksToBounds为YES时,图层将会沿着Bounds进行裁剪 图层混合问题解决了

7.日常如何检查内存泄露?

  • 目前我知道的方式有以下几种
    Memory Leaks
    Alloctions
    Analyse
    Debug Memory Graph
    MLeaksFinder
  • 泄露的内存主要有以下两种:
    Laek Memory 这种是忘记 Release 操作所泄露的内存。
    Abandon Memory 这种是循环引用,无法释放掉的内存。

调试技巧

1.LLDB常用的调试命令?

  • po:print object的缩写,表示显示对象的文本描述,如果对象不存在则打印nil。
  • p:可以用来打印基本数据类型。
  • call:执行一段代码 如:call NSLog(@"%@", @“yang”)
  • expr:动态执行指定表达式
  • bt:打印当前线程堆栈信息 (bt all 打印所有线程堆栈信息)
  • image:常用来寻找栈地址对应代码位置 如:image lookup --address 0xxxx

2.断点调试

  • 条件断点
    打上断点之后,对断点进行编辑,设置相应过滤条件。下面简单的介绍一下条件设置:
    Condition:返回一个布尔值,当布尔值为真触发断点,一般里面我们可以写一个表达式。
    Ignore:忽略前N次断点,到N+1次再触发断点。
    Action:断点触发事件,分为六种:
    • AppleScript:执行脚本。
    • Capture GPU Frame:用于OpenGL ES调试,捕获断点处GPU当前绘制帧。
    • Debugger Command:和控制台中输入LLDB调试命令一致。
    • Log Message:输出自定义格式信息至控制台。
    • Shell Command:接收命令文件及相应参数列表,Shell Command是异步执行的,只有勾选“Wait until done”才会等待Shell命令执行完在执行调试。
    • Sound:断点触发时播放声音。
    • Options(Automatically continue after evaluating actions选项):选中后,表示断点不会终止程序的运行。
  • 异常断点
    异常断点可以快速定位不满足特定条件的异常,比如常见的数组越界,这时候很难通过异常信息定位到错误所在位置。这个时候异常断点就可以发挥作用了。
    Exception:可以选择抛出异常对象类型:OC或C++。
    Break:选择断点接收的抛出异常来源是Throw还是Catch语句。
  • 符号断点
    符号断点的创建方式和异常断点一样一样的,在符号断点中可以指定要中断执行的方法:
    Symbol:[类名 方法名]可以执行到指定类的指定方法中开始断点

3.iOS 常见的崩溃类型有哪些?

  • unrecognized selector crash
  • KVO crash
  • NSNotification crash
  • NSTimer crash
  • Container crash
  • NSString crash
  • Bad Access crash (野指针)
  • UI not on Main Thread Crash

iOS App 稳定性指标及监测

稳定性指标及监测

源码理解

1…AFNetworking 底层原理分析

AFNetworking是封装的NSURLSession的网络请求,由五个模块组成:分别由NSURLSession,Security,Reachability,Serialization,UIKit五部分组成

  • NSURLSession:网络通信模块(核心模块) 对应 AFNetworking中的 AFURLSessionManager和对HTTP协议进行特化处理的AFHTTPSessionManager,AFHTTPSessionManager是继承于AFURLSessionmanager的
  • Security:网络通讯安全策略模块 对应 AFSecurityPolicy
  • Reachability:网络状态监听模块 对应AFNetworkReachabilityManager
  • Seriaalization:网络通信信息序列化、反序列化模块 对应AFURLResponseSerialization
  • UIKit:对于iOS UIKit的扩展库

2.SDWebImage加载图片过程

0、首先显示占位图
1、在webimagecache中寻找图片对应的缓存,它是以url为数据索引先在内存中查找是否有缓存;
2、如果没有缓存,就通过md5处理过的key来在磁盘中查找对应的数据,如果找到就会把磁盘中的数据加到内存中,并显示出来;
3、如果内存和磁盘中都没有找到,就会向远程服务器发送请求,开始下载图片;
4、下载完的图片加入缓存中,并写入到磁盘中;
5、整个获取图片的过程是在子线程中进行,在主线程中显示。

3.YYKit

YYKit 是一组庞大、功能丰富的 iOS 组件。
我们需要从组件架构,源码,设计思路,解决问题的方案策略等多方面去学习。

4.Masonry

代码管理

1.SVN与Git优缺点比较

SVN优缺点

  • 优点:
    1、管理方便,逻辑明确,符合一般人思维习惯。
    2、易于管理,集中式服务器更能保证安全性。
    3、代码一致性非常高。
    4、 适合开发人数不多的项目开发。
  • 缺点:
    1、服务器压力太大,数据库容量暴增。
    2、如果不能连接到服务器上,基本上不可以工作,看上面第二步,如果服务器不能连接上,就不能提交,还原,对比等等。
    3、不适合开源开发(开发人数非常非常多,但是Google app engine就是用svn的)。但是一般集中式管理的有非常明确的权限管理机制(例如分支访问限制),可以实现分层管理,从而很好的解决开发人数众多的问题。

Git的优缺点

  • 优点
    1、适合分布式开发,强调个体。
    2、公共服务器压力和数据量都不会太大。
    3、速度快、灵活。
    4、任意两个开发者之间可以很容易的解决冲突。
    5、离线工作。
  • 缺点
    1、学习周期相对而言比较长。
    2、不符合常规思维。
    3、代码保密性差,一旦开发者把整个库克隆下来就可以完全公开所有代码和版本信息。

2.Git与SVN的区别

  • 1.Git是分布式的,而SVN不是分布式的
  • 2.Git把内容按元数据方式存储,而SVN是按文件
  • 3、Git没有一个全局版本号,SVN有,目前为止这是SVN相比Git缺少的最大的一个特征
  • 4、Git的内容的完整性要优于SVN: GIT的内容存储使用的是SHA-1哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏
  • 5、Git下载下来后,在OffLine状态下可以看到所有的Log,SVN不可以
  • 6、SVN必须先Update才能Commit,忘记了合并时就会出现一些错误,git还是比较少的出现这种情况
  • 7、克隆一份全新的目录以同样拥有五个分支来说,SVN是同时复制5个版本的文件,也就是说重复五次同样的动作。而Git只是获取文件的每个版本的 元素,然后只载入主要的分支(master)在我的经验,克隆一个拥有将近一万个提交(commit),五个分支,每个分支有大约1500个文件的 SVN,耗了将近一个小时!而Git只用了区区的1分钟
  • 8、版本库(repository):SVN只能有一个指定中央版本库。当这个中央版本库有问题时,所有工作成员都一起瘫痪直到版本库维修完毕或者新的版本库设立完成。而 Git可以有无限个版本库。或者,更正确的说法,每一个Git都是一个版本库,区别是它们是否拥有活跃目录(Git Working Tree)。如果主要版本库(例如:置于GitHub的版本库)发生了什么事,工作成员仍然可以在自己的本地版本库(local repository)提交,等待主要版本库恢复即可。工作成员也可以提交到其他的版本库
  • 9、分支(Branch)在SVN,分支是一个完整的目录。且这个目录拥有完整的实际文件。如果工作成员想要开启新的分支,那将会影响“全世界”!每个人都会拥有和你一样的分支。如果你的分支是用来进行破坏工作(安检测试),那将会像传染病一样,你改一个分支,还得让其他人重新切分支重新下载,十分狗血。而 Git,每个工作成员可以任意在自己的本地版本库开启无限个分支。举例:当我想尝试破坏自己的程序(安检测试),并且想保留这些被修改的文件供日后使用, 我可以开一个分支,做我喜欢的事。完全不需担心妨碍其他工作成员。只要我不合并及提交到主要版本库,没有一个工作成员会被影响。等到我不需要这个分支时, 我只要把它从我的本地版本库删除即可。无痛无痒。
    Git的分支名是可以使用不同名字的。例如:我的本地分支名为OK,而在主要版本库的名字其实是master。
    最值得一提,我可以在Git的任意一个提交点(commit point)开启分支!(其中一个方法是使用gitk –all 可观察整个提交记录,然后在任意点开启分支。)
  • 10、提交(Commit)在SVN,当你提交你的完成品时,它将直接记录到中央版本库。当你发现你的完成品存在严重问题时,你已经无法阻止事情的发生了。如果网路中断,你根本没办法提交!而Git的提交完全属于本地版本库的活动。而你只需“推”(git push)到主要版本库即可。Git的“推”其实是在执行“同步”(Sync)

持续集成

1.你在项目中使用过什么持续集成方式?

Fastlane:一套用Ruby写的自动化工具集,可用于iOS和Android的打包、发布,节省了大量时间。Fastlane配置比较简单,主要编写集成的lane,然后在命令行操作即可

Jenkins:Jenkins比较受欢迎,插件众多,但对新手来说配置可能稍微麻烦点。

2.jenkins怎么备份恢复

只需要拷贝主home下面的 .jenkins打个包,下次要恢复就用这个覆盖,所有的东西就都一模一样了。其实就是配置的东西都在这里面,插件的话有个Plugin的文件夹下面就是所有的插件的东西。

3.jenkins你都用了哪些插件?

  • Keychains and Provisioning Profiles Management:管理本地的keychain和iOS证书的插件
  • Xcode integration:用于xcode构建
  • GIT plugin/SVN:代码管理插件

逆向(窃取密码,防护破解,算法加密)

你想证书的大建行卡号大 商会大厦是大科技哈卡仕达刷卡机换手机号 炬华科技和空间和
的撒后即可好看哈哈哈建行卡号是的撒萨科技哈克时间和大声道数据库和空间和空间和可靠进口好借好还健康科技师大会尽快口水都d’s’k’j’h’j’k’j’k’h’j’k’j’j’j’h

Objective-C语言特性

KVO

  • KVO-键值观察机制,原理如下:
    1.当给A类添加KVO的时候,runtime动态的生成了一个子类NSKVONotifying_A,让A类的isa指针指向NSKVONotifying_A类,重写class方法,隐藏对象真实类信息
    2.重写监听属性的setter方法,在setter方法内部调用了Foundation的_NSSetObjectValueAndNotify 函数
  1. ()_NSSetObjectValueAndNotify函数内部
    • 首先会调用 willChangeValueForKey
    • 然后给属性赋值
    • 最后调用 didChangeValueForKey
    • 最后调用 observer 的 observeValueForKeyPath 去告诉监听器属性值发生了改变
      4.重写了dealloc做一些 KVO 内存释放
3.如何手动触发KVO方法
  • 手动调用willChangeValueForKey和didChangeValueForKey方法
  • 键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangeValueForKey。在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就 会记录旧的值。而当改变发生后, didChangeValueForKey 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了 有人可能会问只调用didChangeValueForKey方法可以触发KVO方法,其实是不能的,因为willChangeValueForKey: 记录旧的值,如果不记录旧的值,那就没有改变一说了

KVC

通知

属性关键字

代理

代理模式是iOS中非常重要的一个模式,iOS SDK中的系统控件几乎都用到了代理模式。代理模式用来处理事件监听、参数传递功能。

属性delegate需定义成weak,不可以是strong,两者之间必须为“非拥有关系“。委托对象(assistant类)会持有本对象(也就是上面例子的”Boss类“),而定义boss类的delegate属性,如果delegate是strong定义的,则会被boss类所拥有。这样就变成相互拥有了,造就著名的”保留环“

函数

协议

分类

1.分类和扩展有什么区别?可以分别用来做什么?分类有哪些局限性?分类的结构体里面有哪些成员?

Category

  • 可以减少单个文件的体积
  • 把不同功能组织到不同的Category里
  • 可以由多个开发者共同完成一个类
  • 可以按需加载想要的category
  • 声明私有方法
  • 模拟多继承(一定要搞懂多继承概念)
  • 把framework的私有方法公开(先了解framework)

Extension

  • Extension可以添加属性和方法,且方法必须实现
  • 隐藏私有信息

区别

  • extension在编译器决议,属于类的一部分,category在运行时决议
  • extension必须在有一个类的源码时才能添加,category不需要有类的源码
  • extension可以添加实例变量,category不可以

Category的实现原理,以及Category为什么只能加方法不能加属性?

分类的实现原理是将category中的方法,属性,协议数据放在category_t结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。 Category可以添加属性,但是并不会自动生成成员变量及set/get方法。因为category_t结构体中并不存在成员变量。通过之前对对象的分析我们知道成员变量是存放在实例对象中的,并且编译的那一刻就已经决定好了。而分类是在运行时才去加载的。那么我们就无法再程序运行时将分类的成员变量中添加到实例对象的结构体中。因此分类中不可以添加成员变量。

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

Category中有load方法,load方法在程序启动装载类信息的时候就会调用。load方法可以继承。调用子类的load方法之前,会先调用父类的load方法

load、initialize的区别,以及它们在category重写的时候的调用的次序。

  • 区别在于调用方式和调用时刻 调用方式:load是根据函数地址直接调用,initialize是通过objc_msgSend调用 调用时刻:load是runtime加载类、分类的时候调用(只会调用1次),initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
  • 调用顺序:先调用类的load方法,先编译那个类,就先调用load。在调用load之前会先调用父类的load方法。分类中load方法不会覆盖本类的load方法,先编译的分类优先调用load方法。initialize先初始化父类,之后再初始化子类。如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次),如果分类实现了+initialize,就覆盖类本身的+initialize调用。

@protocol和category中如何使用@property

  • 在 protocol 中使用 property 只会生成 setter 和 getter 方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性
  • category 使用 @property 也是只会生成 setter 和 getter 方法的声明,如果我们真的需要给 category 增加属性的实现,需要借助于运行时的两个函数:objc_setAssociatedObject和objc_getAssociatedObject

Category 结构体 category_t

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods; // 对象方法
    struct method_list_t *classMethods; // 类方法
    struct protocol_list_t *protocols; // 协议
    struct property_list_t *instanceProperties; // 属性
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

扩展

面向对象三大特性(继承,封装,多态)

单例模式

实例方法和类方法

实例变量

属性和方法

属性和变量

类方法

通知

属性关键字

UI视图相关

UITableView相关

事件传递&视图相应

图像显示原理

离屏渲染

绘制原理&异步绘制

卡顿&掉帧

内存管理

引用系数

弱引用

自动释放池

循环引用

内存布局

内存管理方案

数据结构

ARC&MRC

多线程

NSThread

GCD

NSOPeration

多线程和锁

Runloop

概念

事件循环机制

RunLoop与NSTimer

RunLoop与多线程

数据结构

CFRunLoop

网络相关

HTTP协议 概念 超文本传输协议

请求/相应报文
链接创立流程
  • 三次握手
  • 四次握手
HTTP的特点
  • 无连接
  • 无状态

TCP/UDP传输层协议

TCP传输控制协议特点
UDP用户数据报协议

DNS解析

了解DNS解析吗?
DNS解析查询方式
DNS的解析存在哪儿些常见的问题

怎么解决DNS劫持

  • httpDNS
    使用HTTP协议向DNS服务器的80端口进行请求
  • 长链接

Session/Cookie

Session

Session也是用来记录用户状态,区分用户,状态保存在服务器端

Cookie
  • Cookie主要用来记录用户状态,区分用户,状态保存在客户端
  • 如何修改Cookie
  • 怎么保证Cookie的安全

设计模式(OC编程之道iOS设计模式解析)

  • 你的项目中用到了哪些设计模式,如何使用
  • 知道常用设计模式的优缺点
  • 能画出常用设计模式的UML图

01代理

02观察者

03MVC

04单例(Singleton)

05策略

工厂

MVVM

原型(Prototype)

工厂方法(Factory Method)

抽象工厂(Abstract Factory)

生成器(Builder)

接口适配

适配器(Adapter)
桥接(Bridge)
外观(Facade)

对象去耦

中介者(Mediator)
观察者(Observer)

抽象集合

组合(Composite)
迭代器(Iterator)

行为扩展

访问者(Visitor)
装饰(Decorator)
责任链(Chain of Responsibility)

算法封装

模块方法(Template Method)
策略(Strategy)
命令(Command)

性能与对象访问

享元(Flyweight)
代理(Proxy)

对象状态

备忘录(Memento)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UqjK6Bue-1591231390713)(evernotecid://2CC0A8C7-733A-43F8-8428-C60C9C6C63B2/appyinxiangcom/21128449/ENResource/p391)]

框架-架构

模块化
分层
解耦
降低代码重合度

图片缓存

如何设计一个图片缓存框架Manager
  • 图片来源
  • Code Manager
图片通过什么方式进行读写,过程是怎么样的

以图片的URL的单向Hash值作为key

内存设计上需要考虑哪些问题
  • 存储的Size
  • 淘汰策略
    1.列队
    2.LRU
磁盘设计需要考虑哪些问题
  • 存储方式
  • 大小限制
  • 淘汰策略:如某一个图片的存储时间距离今天已经超过了7天
网络设计部分需要考虑哪些问题
  • 图片请求的最大并发量
  • 请求超时策略
  • 请求的优先级
对于不同格式的图片,解码采用什么方式来做

应用策略模式对不同的图片格式进行解码

在哪个阶段做图片解码处理
  • 磁盘读取后
  • 网络请求返回后
线程处理

阅读时长统计

怎么样设计一个时长统计框架 记录器
  • 记录管理者
  • 页面式
  • 流式
  • 自定义式
为何有不同类型的记录器,你的考虑是什么

基于不同场景提供的关于记录的封装,适配

记录的数据会由于某种原因丢失,你怎么处理的
  • 定时写磁盘
  • 限制内存缓存的条数(10条),超过该条数,写入磁盘
关于延时上传的具体场景有哪些
  • 前后台切换
  • 有无网到有网的变化
  • 通过轻量接口捎带
上传时机是怎么把控的
  • 立刻上传
  • 延时上传
  • 定时上传

复杂页面架构

MVVM框架思想
  • View 视图层/ViewController/View
  • ViewModel 业务逻辑层
  • Model 数据层
ReactNative的数据流思想 -多叉树
系统UIView更新机制的思想
FaceBook的开源框架ASyncDisplayKit关于预排版的设计思想

客户端整体架构

概念
  • 业务A 业务B
  • 通用业务层
  • 独立App的通用层
  • 中间层
业务之间的解耦通讯方式 OpenUR 依赖注入

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MXp3oPlx-1591231390714)(evernotecid://2CC0A8C7-733A-43F8-8428-C60C9C6C63B2/appyinxiangcom/21128449/ENResource/p392)]

第三方库

AFNetworking

SDWebIMage

ReactiveCocoa

Masonry

JSPath

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RVnPfrIy-1591231390715)(evernotecid://2CC0A8C7-733A-43F8-8428-C60C9C6C63B2/appyinxiangcom/21128449/ENResource/p393)]

Runtime

数据结构

类对象与元类对象

消息转发

动态添加方法

动态方法的解析

Method-Swizzling

Block

Block介绍

截获变量

__block 修饰符

Block的内存管理

Block的循环引用

安全方案

本地数据存储安全(Keychain)

授权和身份验证

传输安全(对称, 非对称, SSL)

App代码安全

函数式编程

RxSwift

ReactiveCocoa

工程相关

无论是企业开发人员还是独立开发者,在需求、交互、视觉、开发一系列的步骤后,都会面对APP的测试、优化、上线、版本更新的问题,一个优秀的iOS开发者在面对这方面问题的时候,就需要有工程相关的知识,保证我们开发的APP能够长期运行,高效优化。这部分我们必须了解的内容有以下几部分:

版本管理工具和常用工作流

第三方库管理工具

debug技能

性能调优

单元测试

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章