iOS开发面试小记

403 阅读32分钟

*********************** Objective-C ***********************

  • MRC和ARC简单理解(内存的手动和自动管理模式)

OC的内存管理有两种模式:
MRC manual reference counting 手动引用计数  在2011年、iOS5之前,OC的开发只支持MRC模式:也就是支持手动引用计数 当时每当一个新的指针引用了一块空间(也就是对象) 就必须手动的把此块堆空间内的 retainCount + 1。

  • ARC automatic reference counting 自动引用计数
  1. 所用堆空间在新指针引用时的retainCount+1,在指针不引用时retainCount-1
  2. 自动维护引用计数,并标记释放当前堆空间,不需要程序员去关心
  • 补充一点: 当一块堆空间被标记可以删除之后,即使暂时没有新的数据填充这块空间,原来的数据也不能重新使用
  • 什么是Block? 1.一段未来可能执行的代码块,类似于懒加载 2.block可以是闭包,闭包就是保存其他函数局部变量的匿名函数

匿名函数:没有方法名称和标识的函数

  • 为什么Block外部要使用weakSelf,内部要使用strongSelf

外部使用了weakSelf,里面使用strongSelf却不会造成循环,究其原因就是因为weakSelf是block截获的属性,而strongSelf是一个局部变量会在“函数”执行完释放。

1、系统自带block不会引发强引用

如图,使用系统自带的UIView 的Blcok,控制器能被销毁-->

说明没有发送循环引用。 原理: UIView的调用的是类方法,当前控制器不可能强引用一个类 ,所以循环无法形成 --> 动画block不会造成循环引用的原因

2、AFNetworking不会引发强引用

原理:AFN无循环是因为绝大部分情况下,你的网络类对象是不会被当前控制器引用的,这时就不会形成引用环。(查阅资料得知

3、自定义的block容易引发强引用

block中的循环引用是这样的:某个对象有一个copy或者strong成员变量或者属性,这时block内部直接引用了成员变量或者self,这样就产生了self持有block成员,block成员持有self,就会导致循环引用。

  • 为什么block声明要用copy而不是strong

我们会使用关键字copy修饰他,因为他在栈区,我们没办法控制他的消亡,当我们用copy修饰的时候,系统会把该 block的实现拷贝一份到堆区,这样我们对应的属性,就拥有的该block的所有权。就可以保证block代码块不会提前消亡。

__weak和__block的作用和区别

作用:
1.由于block会复制外部的变量,所以如果不注意,就会造成循环引用。对于这种情况,需要将引用的一方变成weak,从而避免循环引用。

1)__block对象在block中是可以被修改、重新赋值的。

2)__block对象在block中不会被block强引用一次,从而不会出现循环引用问题。

3)blocks可以访问局部变量,但是不能修改。

注意:如果修改局部变量,需要加__block,所以__block是让修改外部变量的值.

区别:

1)__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。

2)__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。

3)__block对象可以在block中被重新赋值,__weak不可以。

  • 手势的传递过程,响应过程

1)手势的传递是从上到下(由父控件到子控件)

2)事件的响应从下至上(顺着响应者链条向上传递:子控件到父控件)

  1. 当UIView中的isUserInteractionEnabled = NO、isHidden = YES、alpha <= 0.01时,hitTest方法不会被调用;
  2. UIResponder 中的touches三个方法都是发生在找到最终的响应事件的view之后;
  3. 二是寻找hit-test view的事件链传导了两遍,具体原因不明;
  • 常用的多线程方式和区别

NSTheard、GCD、NSOptionQueue(NSOption)

1.NSTheard

轻量的线程操作,需要我们自己创建线程,调度任务,销毁线程

2.GCD

基于C语言的异步执行任务的技术/方式之一,一般将应应用程序中记述的线程管理代码在系统几种实现。

引子:

如果我现在问你GCD里,队列+执行函数的组合怎么产生子线程。

你的回答是异步函数+并行队列吗?

  • 主队列是串行队列
  • 同步派遣 + 串行队列,不产生子线程
  • 异步派遣 + 串行队列,产生子线程
  • 同步派遣 + 并行队列,没有产生子线程
  • 异步派遣 + 并行队列,产生子线程

结论:

由此我们可以得出:串行与并行针对的是队列,而同步与异步,针对的则是线程。

死锁形成的原因:

  1. 系统资源不足
  2. 进程(线程)推进的顺序不恰当;
  3. 资源分配不当

死锁形成的条件:

  1. 互斥条件:所谓互斥就是进程在某一时间内独占资源。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

例子:

- (void)syncMain { 
   dispatch_queue_t queue = dispatch_get_main_queue();
   NSLog(@"task1-%@",[NSThread currentThread]); 
   dispatch_sync(queue, ^{ NSLog(@"task2-%@",[NSThread currentThread]);
   
   }); 
   NSLog(@"task3-%@",[NSThread currentThread]); 
}

打印结果:
2017-07-10 17:54:43.623 beck.wang[1405:182548] task1-<NSThread: 0x608000066000>{number = 1, name = main}

分析: 死锁 原因(主线程遇上主队列 <=> 同步遇上串行队列):

从打印结果可以看出,task1是在主线程中执行,而主线程是串行队列,定义的queue队列也是主队列, dispatch_sync是同步执行的标志,意思是必须等待block返回,才能执行task3,而当前主队列中正在被task1执行,必须等待完成task3完成后才能释放,这就造成了task3等待block完成返回,block等待task3完成释放主队列而相互等待的循环中死锁;

1、主线程有一个特点:主线程会先执行主线程上的代码片段,然后才会去执行放在主队列中的任务

2、同步执行dispatch_sync函数的特点:该函数只有在该函数中被添加到某队列的某方法执行完毕之后才会返回。及方法会等待task(任务)执行完再返回

3、这样dispatch_sync为了返回会等task执行完也就是主线程执行完,而task执行又会等着主线程上代码执行完,也就是主线程上 dispatch_sync代码执行完

dispatch_sync函数 <------------ 互相等待 ------------> task任务

3.NSOperation

纯OC代码,操作队列,对GCD的高度抽离和封装,他是一个抽象类(面向对象),只能使用其子类对象。系统提供了两个子类对象,分别是 NSInvocationOperation 和 NSBlockOperation。通常我们自定义 NSOperation 的子类,重写子类的 main 方法,把需要在分线程执行的任务放在 main 方法里。然后把NSOperation 对象添加到 NSOperationQueue 中,就会自动在分线程执行 main 方法。

4.NSOperationQueue和GCD区别联系

区别:

1.NSOperationQueue没有串行/并发队列,但可以设置最大并发数量 2.NSOperationQueue支持方法和block,GCD只支持block 3.NSOperationQueue可以暂停、取消操作 4.NSOperationQueue支持更多的功能,比如KVO和自定义操作 5.NSOperationQueue可以设置队列和操作的优先级,GCD只能设置队列的优先级

联系: 1.提供的功能是相似的 2.NSOperationQueue是GCD的高度封装和抽离

什么是runtime?

  • IMP函数指针 IMP 的实质是一个函数指针,所指向的就是方法的实现。IMP用来找到函数地址,然后执行函数。

  • isa指针 isa指针是和Class同类型的objc_class结构指针,类对象的指针指向其所属的类,即元类。元类中存储着类对象的类方法,当访问某个类的类方法时会通过该isa指针从元类中寻找方法对应的函数指针。

应用场景: (1)字典转模型:

  • 借助 Runtime 可以动态获取成员列表的特性,遍历模型中所有属性
  • 然后以获取到的属性名为 key,在JSON或字典中寻找对应的值value
  • 再使用KVC 或直接调用Getter / Setter将每一个对应 value 赋值给模型就完成了字典转模型的目的

(2)更改修改私有属性(设置UITextField占位文字的颜色):

  • 通过获取类的属性列表和成员变量列表的方法打印UITextfield 所有属性和成员变量;
  • 找到私有的成员变量 _placeholderLabel;
  • 利用 KVC 对 _placeholderLabel进行修改。

(3)动态的交换两个方法的实现 (4)动态的给类添加方法,添加属性 (5)通过分类为UIControl/UIButton添加点击事件方法

方法的执行交换实例:

+ (void)load { 
   Method originalMethod = class_getInstanceMethod(self, @selector(viewWillAppear:)); 
   Method swizzledMethod = class_getInstanceMethod(self, @selector(fd_viewWillAppear:));
   method_exchangeImplementations(originalMethod, swizzledMethod); 
}
  • Runloop的作用
  1. 保持应用程序的持续运行
  2. 处理app种的各种事件(如:触摸事件,滚动事件,方法调用)等
  3. 节省CPU的资源,提高程序性能
  • Runloop和线程有什么关系? 前言: 一般来说,一个线程一次只能执行一个任务,执行完成后线程就会退出,如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑是这样的:
function loop() { 
  initialize(); 
  do { 
    var message = get_next_message(); 
    process_message(message); 
   } while (message != quit); 
}
  1. 每个线程,包括程序的主线程(main thread)都有与之对应的runloop对象
  2. runloop和线程的关系:对于主线程来说runloop在程序一启动就默认创建好了;对于子线程来说,runloop是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器时要注意(确保子线程runloop被创建,不然定时器不会回调)
  3. 处在当前线程就只能操作当前线程的runloop,但可以给其它的runloop发送消息
  4. runloop同一时间只能处理一种runloopMode,其他mode无法响应
  5. runloop执行完毕之后,就会进入休眠,只有在某个情况下触发了,才会再次调用

Runloop内部逻辑: runloop.png

  • Runtime 1.OC的运行时机制的,把一些决定性的工作从编译阶段、链接阶段推迟的运行时阶段的机制 2.主要的作用是消息的转发和传递objc_msgSend 3.实际上是一个库,这个库可以在程序运行时动态的创建对象,检查对象,修改类的对象的方法 4.热更新(JSPatch)的底层也是基于runtime

结论: JSPatch 能做到通过 JS 调用和改写 OC 方法最根本的原因是 Objective-C 是动态语言,OC 上所有方法的调用/类的生成都通过 Objective-C Runtime 在运行时进行,我们可以通过类名/方法名反射得到相应的类和方法;也可以替换某个类的方法为新的实现,还可以新注册一个类,为类添加方法;理论上你可以在运行时通过类名/方法名调用到任何 OC 方法,替换任何类的实现以及新增任意类。所以 JSPatch 的基本原理就是:JS 传字符串给 OC,OC 通过 Runtime 接口调用和替换 OC 方法。

  • Copy和MuteableCopy的理解

不可变对象:

  1. 不可变对象使用copy时,只拷贝对象的指针,不拷贝对象本身,不产生新的对象,为浅拷贝
  2. 不可变对象使用MuteableCopy时,拷贝对象本身,产生新的对象,为深拷贝

可变对象:

  1. 可变对象使用copy时,产生新的对象,视为深拷贝
  2. 可变对象使用MuteableCopy时,产生新的对象,视为深拷贝
  • 什么是组件化开发? 组件化开发,就是将一个臃肿,复杂的单一工程的项目,根据功能或者属性进行分解,拆分成各个独立的功能组件模块或者组件;然后根据项目和业务需求,按照某种方式,任意组织成一个拥有完整业务逻辑的工程;这就是所谓的组件化。

组件化的优点
1.组件之间相互独立。各组件开发成员之间的代码相互独立编写的,独立编译,独立运行和独立测试 2.资源的重复利用,尤其是功能性,工具性的代码,可以很轻松的重复利用 3.迭代的效率提高。通过迭代进行功能的增减,只需要进行组件的拆分和组合。很方便也很高效 4.方便多人多团队开发(这就是大团队为什么那么热衷于组件化的原因,对开发的效率的提升是十分显著的) 5.快速集成、代码的利用率高、迭代效率高

组件化的缺点
1.代码耦合严重,依赖严重 2.项目复杂、臃肿、庞大,编译时间过长 3.难以做集成测试 4.对开发人员,只能使用相同的开发模式

  • 你是否接触过OC中的反射机制?简单聊一下概念和使用

1). class反射

通过类名的字符串形式实例化对象。

Class class = NSClassFromString(@"student");

Student *stu = [[class alloc] init];

将类名变为字符串。

Class class = [Student class];

NSString *className = NSStringFromClass(class);

2). SEL的反射

通过方法的字符串形式实例化方法。

SEL selector = NSSelectorFromString(@"setName");

[stu performSelector:selector withObject:@"Mike"];

将方法变成字符串。

NSStringFromSelector(@selector*(setName:));

  • iOS开发 - WKWebView与JS的交互

iOS8以后,Apple公司推出了WKWebView,对比之前的UIWebView不论是处理速度还是内存性能,都有了大幅度的提升!

WkWebView的代理有三种:WKUIDelegate,WKNavigationDelegate,WKScriptMessageHandler

1、JS调用OC可以通过WKNavigationDelegate代理方法监听,判断响应的协议头,进行相应的业务操作

2、JS给OC传值,可以通过WKWebViewConfiguration,先注册config.userContentController.add(self, name: "callAppSBlockMethod"),再通过WKScriptMessage代理方法回调到传过来的值

3、OC给JS传值,比如token,Language等,通过config.userContentController.addUserScript(script)传入值

4、OC给JS传值,可以通过evaluateJavaScript,js注入方式:

  if let noticeId = LWUserInfoManager.shared.noticeId { 
  let jsScript = "getNoticeId('\(noticeId)')"    
  self.wkWebView.wkWebView.evaluateJavaScript(jsScript, completionHandler: { (result, error) in })
  
  }
  • iOS开发 - WebView与JS交互

1、iOS7之前,我们只能使用UIWebview的stringByEvaluatingJavaScriptFromString方法执行js代码,想要js调用OC代码我们只能通过拦截的方式,代码如下:

  • 原生调JS:- stringByEvaluatingJavaScriptFromString:
  • JS调原生:- webView:shouldStartLoadWithRequest:navigationType:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { 
     NSString *url = request.URL.absoluteString; 
     if ([url rangeOfString:@"myProtocol://" ].location != NSNotFound) { 
     NSLog(@"拦截成功了"); 
     return NO; 
    }
   return YES; 
}

2、在iOS7之后可用通过JavaScriptCore方式实现与JS网页的通信

实现步骤:

  1. 先创建一个JSProtocol协议,遵循JSExport协议,添加一个打印方法
  2. 然后当web页面加载完毕的时候,通过[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]获取JSContext / 上下文对象
-(void)webViewDidFinishLoad:(UIWebView *)webView { 
// 获取网页的标题 
self.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"]; 
// 获取到js的上下文 JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; 

NSLog(@"context=%@",context); 
self.context = context; 
// 为js增加log方法 context[@"log"] = ^(NSString *str){ NSLog(@"log:%@",str); }; 
// 为js增加obj对象 遵循该协议jsExport MyJSObject *obj = [[MyJSObject alloc] init]; context[@"obj"] = obj; 
// 为js增加changeLabel的函数 context[@"changeLabel"] = ^(NSString *str){
    self.text.text = str; 
};

context.exceptionHandler = ^(JSContext *context, JSValue *exception) { 
    NSLog(@"异常%@",exception); 
  }; 
}
  • WebViewJavascriptBridge 

WebViewJavascriptBridge 框架提供了一种更优雅的方式,用来在 WKWebView、UIWebView(iOS) 以及 WebView(OSX)中,建立一个 Objective-C 和 JavaScript 之间互相“发送消息”的机制,让我们可以在 JS 中像直接调 JS 方法那样发消息给 Objective-C,同时可以在 Objective-C 中像直接调 Objective-C 方法那样发消息给 JS。

特点:

  1. iOS开发Objective-C 中发送消息给 webView 中的 JavaScript
  2. webView 中的 JavaScript 发送消息给 Objective-C
  3. 不论是原生还是 JavaScript,发送消息的过程就像平时调用同一语言/环境的方法一样简单
  4. 发送消息时不仅可以带参数,还可以传 callback 用于回调

*********************** Swift ***********************

  • discardableResult

1、当有返回值的方法未得到接收和使用时通常会出现图片中的提示:

2、在正式编译中不会影响编译结果,但是也妨碍代码的美观整洁,在方法上加上“@discardableResult”就可以取消这个警告

  • class和struct有什么区别?

Class是引用类型,struct是值类型,class可以实现继承。

  • 类(class)和结构体(struct)有什么区别?

Swift 中,类是引用类型,结构体是值类型。值类型在传递和赋值时将进行复制,而引用类型则只会使用引用对象的一个"指向"。所以他们两者之间的区别就是两个类型的区别。

  • try?和try!有什么区别?
  1. try是可以作为函数抛出异常的作用
  2. try?当函数抛出异常不存在时,try?会默认抛出一个nil
  3. try!当函数抛出异常返回值不存在时,try!会崩溃
  • Swift有哪些高阶函数,作用是什么?
  1. map主要用于处理和映射,对集合进行循环,并对集合中的每个元素采取相同的操作。
  2. filter用于过滤,可以对数组中的元素按照某一个规则进行一次过滤
  3. reduce主要用户合并,创建一个单一的值,也可以计算数组中元素的长度
  • map和flatmap的区别?
  1. map可以对数组中的每一个元素做一次处理
  2. flatmap对数组中的每个元素做处理时,会过滤掉为nil值的元素
  3. flatmap对数组中的每个元素做处理时,还可以把列表的元素进行合并处理

示例: 当在序列上实现时:对集合的集合进行平化。

假设我们现在有一个数组,其元素是两个数组,每个数组内的元素是一些数字,我们需要将这些数字放到一个数组中:

var arrayInArray = [[1,2,3],[6,7,8]] 
var flattenedArray = arrayInArray.flatMap{$0} //flattenedArray = [1, 2, 3, 6, 7, 8]
  • Objective-C和Swift有什么联系和区别?

联系:

  1. Swift和Objective-C共用一套运行时环境,Swift的类型可以桥接到Objective-C
  2. Swift和Objective-C都是使用ARC内存自动管理机制

区别:

Objective-C

  1. OC为动态语言,消息转发机制,数据类型的检测由编译,链接阶段,推迟到运行时阶段
  2. OC不持支持命名空间,不支持多继承,和运算符重载
  3. 能够更好的支持runtime机制,实现动态给类添加属性,动态检查方法及类的属性列表,方法交换实现等
  4. OC可以强制转换类型不同的数据

Swift

  1. Swift是静态语言,相对来说比较严谨和安全的语言
  2. Swift支持多继承,和运算符重载,还支持泛型
  3. Swift运行时机制不够OC灵活,底层通过桥接objc来时间运行时
  4. Swift支持高阶函数,如map,flatmap,filter,reduce等
  5. Swift代码简洁,速度更快,开发和运算效率更高
  6. Swift支持数据类型自动推导
  • Swift中的!和?
  1. 这两个符号是用来标记这个变量的值是否可选
  2. !表示可选变量的必须保证转换能够成功,否者报错,但定义的变量可以直接使用
  3. ?表示可选变量的即使转换不成功也不会被报错,变量值为nil,如果转换成功,要使用该变量时,后面要加!进行修饰
  • Swift中的?? ?? 被命名为空合运算
let username = liginName ?? "xiaoming"
  • Swift的访问权限变更

具体的访问权限由大到小:open、public、internal(默认)、fileprivate、private

  • open:公共权限,最高的权限,可以被其他模块访问,继承及复写
  • public:公有的访问权限,类或者类的公有属性或者公有方法,可以从文件或者模块的任何地方访问。
  • internal:顾名思义,internal是内部的意思,即有着internal访问权限的属性和方法说明在模块内部可以访问
  • fileprivate:文件私有访问权限,被fileprivate修饰的类或者类的属性或方法可以在同一个物理文件中访问。如果超出该物理文件,那么有着fileprivate访问权限的类, 属性和方法就不能被访问。
  • private:私有访问权限,被private修饰的类或者类的属性或方法可以在同一个物理文件中的同一个类型(包含extension)访问。如果超出该物理文件或不属于同一类型,那么有着private访问权限的属性和方法就不能被访问。
  • Swift泛型
  1. Swift提供了泛型让你写出灵活且可重用的函数和类型
  2. Swift标准库是通过泛型代码构建出来的
  3. Swift的数组和字典类型都是泛型集 实例:两个Int值数据的交换等,两个数的最大公约数等
  • Swift中遍历方法for in 和 forEach的区别
  1. for in 能使用 return、break、continue关键字,forEach不能使用 break、continue关键字
  2. for in 和 forEach 在 return关键字 的使用上有着本质的区别
  3. 一般情况下,两者都可通用,都方便、敏捷
  4. for in 使用范围比 forEach更广
  • Swift4之Codable全面解析

1、Swift4中苹果引入了全新的编码与解码支持,开发者可以更加方便的将数据转化为JSON或者存入磁盘;通过查看定义可以看到,Codable 其实是一个组合协议,由 Encodable(编码) 和 Decodable(解码) 两个协议组成;

2、目标是取代现有的NSCoding协议,它对结构体、枚举和类都支持,能够把JSON这种弱类型数据转换成代码中使用的强类型数据,同时由于编译器的帮助,可以使开发者少写很多重复代码。

3、使用示例:

比如我们有下面这样一个学生信息的 JSON 字符串:

let jsonString = """ { "name": "小明", "age": 12, "weight": 43.2 } """

这时候,只需要定义一个 Student 类型,声明实现 Decodable 协议即可,Swift 4 已经为我们提供了默认的实现:

struct Student: Decodable { var name: String var age: Int var weight: Float }

然后,只需要一行代码就可以将 小明 解析出来了:

let xiaoming = try JSONDecoder().decode(Student.self, from: jsonString.data(using: .utf8)!)

背景:

很多时候,我们从服务端请求下的数据都是Json格式,我们需要拿这些数据显示到我们的UI界面。

因此,我们的做法基本都会先将json转为方便使用的数据模型,或者也可以直接转字典解决。

在OC中,我们有很多优秀的第三方库帮助我们实现,比如MJExtension、JSONModel等,这些库基本都是利用runtime实现读取属性名并利用kvc重新赋值属性。

在Swift中,由于runtime的局限,比较出名的有SwiftyJSON、ObjectMapper等。

其中:

1、SwiftyJSON本质上仍然是根据JSON结构去取值,使用起来顺手、清晰;

但这种做法没能妥善解决上述的几个问题,因为它不是基于model的,我们使用的时候,依然必须制定key去获取value,这在一定程度上不是很友好。

2、ObjectMapper实现了JSON直接转Model的功能,不过使用起来,代码量会多一点,因为我们必须遵循Mappable协议,制定json内的每一个key和model属性的对应关系。

比如:构造的class必须满足这三个红框的内容,这对于使用习惯了直接定义Model属性的同学来说,可能会有点不习惯。 handyJson.png

3、那么,今天的主角 HandyJSON就出现了,这个库是阿里一位大神推出的,能够做到JSON转Model一步到位,而且使用起来,非常简洁方便。

关于HandyJSON原理:

摘自网上一段说明如下:

HandyJSON另辟蹊径,采用Swift反射+内存赋值的方式来构造Model实例,保持原汁原味的Swift类定义。

正文: 

1、HandyJSON支持 JSON直接转Model,定义class时,有两点注意:

  • 必须遵循HandyJSON协议        
  • 需要实现空的initializer  (当然Struct结构体 可以不需要init(),下文有说明)
class BasicTypes: HandyJSON { 
  var int: Int = 2 
  var doubleOptional: Double? 
  var stringImplicitlyUnwrapped: String! 
  
  required init() {} 
} 

let jsonString = "{\"doubleOptional\":1.1,\"stringImplicitlyUnwrapped\":\"hello\",\"int\":1}" 
if let object = BasicTypes.deserialize(from: jsonString) { 
  // … 
}

Moya的优点

1)Moya是一个高度抽象的网络库,他的理念是让你不用关心网络请求的底层实现细节,只用定义你关心的业务;

2)Moya采用桥接和组合来进行封装(默认桥接了Alamofire)使得Moya非常好拓展,让你不用修改Moya源代码就可以轻易定制

3)官方给出Moya主要优点:

  1. 编译时检车API endpoint权限
  2. 让你使用枚举定义各种不同的Target,endpoints
  3. 把stubs当做一等公民对待,因此测试超级简单
  • RxSwift冷热信号

冷信号:(例如网络请求)

  • 是被动的,只有当你订阅的时候,它才会发布消息
  • 只能一对一,当有不同的订阅者,消息会重新完整发送。

热信号:(例如 UI 交互)

  • 是主动的,尽管你并没有订阅事件,但是它会时刻推送,
  • 可以有多个订阅者,是一对多,集合可以与订阅者共享信息。
  • RxSwift - subscribe

讲一subscribe。Observable产生的信号有Next,Complete,Error三种类型(ReactiveCocoa还多了一个Interrupted),因此subscribe需要对这三种信息进行监听。比较常用的一个是subscribeNext。

*********************** 网络相关 ***********************

  • TCP和UDP
  1. TCP是面向连接可靠的用户传输控制协议,UDP是面向非连接的用户数据报协议
  2. TPC(三次握手保证相对可靠性)可传输大量数据,速度相对较慢
  3. UDP一次性传输少量数且对可靠性要求不高的数据,速度比较快
  4. UDP传输过程容易出现数据丢失
  • HTTP和HTTPS的区别
  1. https协议需要申请到ca认证证书,一般免费证书很少,需要交费
  2. http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议
  3. http和https使用是完全不同的连接方式用的端口也不一样,前者是80,后者是443

4、http的连接简单,是无状态的。https协议是用HTTP +SSL/TLS协议构建的可进行加密传输、身份认证的网络协议,要比http协议更安全

  • GET和POST请求
  1. GET请求可被缓存,POST请求不会被缓存
  2. GET请求比POST请求要快
  3. POST请求比GET请求安全
  4. GET请求把参数包含在URL中,如果是明文,则容易暴露;POST通过request body传递参数
  • Socket原理

Socket简介:

1、Socket描述了一个IP、端口对。它简化了程序员的操作,知道对方的IP已经PORT就可以给对方发消息,再有服务器端来处理发送的这些消息。所以,Socket一定包含了通信的双发,即客户端(Client)和服务端(server)。

2、Socket是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

3、Socket又称“套接字”,应用程序通过“套接字”向网络发送请求或应答,它是一个针对TCP和UDP编程的接口,借助它建立TCP/UDP连接。Socket连接就是所谓的长连接,理论上客户端和服务端一旦建立连接将不会主动断开。

具体步骤:

1)服务端利用Socket监听端口
2)客户端发起连接
3)服务端返回信息,建立连接,开始通信
4)客户端,服务器 主动断开连接

1、套接字(socket)概念
套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。

应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个 TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应 用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。

2 、建立socket连接

建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket,另一个运行于服务器端,称为ServerSocket。

套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。

客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发 给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

 

3、SOCKET连接与TCP连接

创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。

 

4、Socket连接与HTTP连接

由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网 络应用中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。

而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。

Socket和WebSocket的区别?

socket是TCP/IP进程间通讯的底层实现(当然,socket只是ipc中的一种,此外还有消息队列、信号灯、共享内存等很多手段)。

http(s)是在socket之上封装的一种上层通讯协议,其特点是:

  • 服务端监听通讯,被动提供服务;客户端主动向服务端发起连接请求,建立起通讯。
  • 每一次交互都是:客户端主动发起请求(request),服务端被动应答(response)。
  • 服务端不能主动向客户端推送数据。
  • 通信的数据是基于文本格式的。二进制数据(比如图片等)要利用base64等手段转换为文本后才能传输。

websocket也是在socket之上封装的一种上层通讯协议,其特点是:

  • websocket通讯的建立阶段是依赖于http协议的。最初的握手阶段是http协议,握手完成后就切换到websocket协议,并完全与http协议脱离了。
  • 建立通讯时,也是由客户端主动发起连接请求,服务端被动监听。
  • 通讯一旦建立连接后,通讯就是“全双工”模式了。也就是说服务端和客户端都能在任何时间自由得发送数据,非常适合服务端要主动推送实时数据的业务场景。
  • 交互模式不再是“请求-应答”模式,完全由开发者自行设计通讯协议。
  • 通信的数据是基于“帧(frame)”的,可以传输文本数据,也可以直接传输二进制数据,效率高。当然,开发者也就要考虑封包、拆包、编号等技术细节。

*********************** App优化 ***********************

  • App卡顿优化 1.尽可能的减少CPU、GPU的资源的消耗

根据卡顿的思路,主要优化的方法有
1、CPU的优化

  • 尽量使用轻量级的视图对象,比如用不到交互事件的地方可以用CALayer取代UIView
  • 不要频繁调用UIView相关属性,比如frame、bounds、transform等属性,尽量减少不必要的修改
  • 尽量提前计算好布局,在有需要的时候一次性调整对象的属性,不要多次修改属性
  • Autolayout会比直接计算消耗更多的CPU资源
  • 图片的size最好跟UIImageView的size保持一致
  • 控制一下线程的最大并发数
  • 尽量把耗时操作放到主线程(文本处理(尺寸计算、绘制),图片处理(绘制、解码),视图的刷新等等)
  • 离屏渲染 (1)在OpenGL中,GPU有2种渲染方式
    on-screen rendering:当前屏幕渲染,在当前用于显示的屏幕缓冲去进行渲染操作 off-screen rendering:离屏幕渲染,在当前屏幕缓冲区之外新开辟一个缓冲区进行渲染操作

(2)离屏渲染消耗性能的原因

需要创建新的缓冲区。 离屏渲染的整个过程,需要多次切换上下文环境,先是从当前切换到离屏;等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到当前屏幕上,有需要将上下文环境从离屏切换到当前屏幕。

(3)哪些操作会触发离屏渲染?

光栅化:layer.shouldRasterize = YES; 遮罩:layer.mask

圆角:同时设置layer.masksToBound=YES、layer.cornerRadius大于0,解决方法可以考虑通过CoreGraphics绘制裁剪圆角或者叫美工提供圆角图片

阴影:layer.shadowXXX

如果设置了layer.shadowPath就不会产生离屏渲染

卡顿检测

1)平时所有的卡顿主要是因为在主线程执行了比较耗时的操作。

2)可以添加Observer到主线程RunLoop中,通过监听RunLoop切换的耗时,以达到监控卡顿的目的(具体实现可以百度即有或者直接使用bugly也可以实现)

  • 耗电优化
  1. 耗电的来源:CPU处理、网络、定位、图像
  2. 优化
  • 尽可能降低CPU、GPU功耗
  • 少用定时器
  • 优化I/o操作

尽量减少频繁写入小数据,最好批量一次性写入 、读写大量重要数据时,考虑使用dispatch_io。其提供了基于GCD的异步操作文件I/O的API,用dispatch_io系统会优化磁盘访问

数据量比较大的,建议使用数据库

4)网络优化

减少、压缩网络数据

如果多次请求的结果是相同的,尽量使用缓存

使用断点续传,否则网络不稳定时可能多次传输相同的内存

网络不可用时,不要尝试执行网络请求

让用户可以取消长时间运行或者速度很慢的网络操作,设置合适的超时时间

批量传输,比如下载视频流时,不要传输很小的数据包,直接下载整个文件或者一大块一大块的下载。如果 下载广告,一次性下载一些,然后再慢慢展示。如果下载电子邮件。一次性多封,不要一封一封下载

5)定位优化

如果只是需要快速确定用户的位置,最好使用CLLocarionManager的requesrLocation方法。定位完成后,会 自动定位意见断电

如果不是导航应用,尽量不要实时更新位置,定位完毕后就关掉定位服务

尽量降低定位精度,比如尽量不要使用精最高的kCLLocationAccuracyBest

需要后台定位时,尽量设置pauseLocationUpdatesAutomatically为YES,如果用户不太可能移动的时候系统会自动暂停位置更新

APP的启动

1、APP的启动可以分为2种:

冷启动:从零开始启动APP

热启动:APP已经在内存中,在后台存活着,再次点击图标启动APP

2、APP启动时间的优化主要针对冷启动进行优化

3、通过添加环境变量可以打印出APP的启动时间分析(Edit scheme -> Run -> Arguments)

DYLD_PRINT_STATISTICS 设置为1查看mian函数之后的信息

DYLD_PRINT_STATISTICS_DETAILS设置为1查看更详细的信息

4、APP的冷启动可以概括为3大阶段(dyld、runtime、main)

dyld,APPle的动态连接器,可以用来装载Mach-o文件(可执行文件、动态库等)

启动APP时,dyld所做的事情有

1)装载APP的可执行文件,同时会递归加载所有的依赖的动态库

2)当dyld把可执行文件、动态库都装载完毕后,会通知Runtime进行下一步的处理

*APP启动的优化

按照不同的阶段:

1、dyld

1)减少动态库、合并一些动态库(定期清理不必要的动态库)

2)减少OBjc类、分类的数量、减少Selecto数量(定期清理不必要的类、分类)

3)减少C++虚函数数量

2、runtime

用+initialize方法和dispatch_once取代所有的attribute((constructor))、C++静态构造器、Objc的+load

3、main

1)在不影响用户体验的前提下,尽可能将一些操作延迟,不要全部放在finishLaunching发放中

2)按需求加载响应的业务

  • 启动时间检测

在App性能优化中,有一块就是启动时间的优化。那如何获取App冷启动所需要的时间呢?

找到 Edit scheme -> Run -> Auguments 将环境变量 DYLD_PRINT_STATISTICS 设为 1,如图一,然后运行。

运行后,能看到控制台打印出日志。如图二。可以看到在进入 main() 函数之前,一共耗时452.61ms。并且列举了加载比较慢的文件

  • 安装包瘦身

安装包主要由可执行文件、资源组成

1、资源(图片、音频、视频等)

采取无损压缩(ImageOptim)

通过工具LSUnusedResources,去除没有用到的资源

2、可执行文件瘦身

编译器优化

1、Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default设置为YES

2、去掉异常支持,Enable C++ Exceptions、Enable Objective-C Exceptions设置为NO,Other C Flags添加-fno-exceptions 利用AppCode检测未使用的代码:菜单栏 -> Code -> Inspect Code 编写LLVM插件检测出重复代码、未被调用的代码 生成LinkMap文件,可以查看可执行文件的具体组成

  • 简单的描述app程序应用启动原理 1.打开应用程序 ---> 2.执行main函数 ---> 3.执行UIApplicationMain函数 ---> 4.初始化UIApplication(创建和设置代理对象,开启事件循环) ---> 5.结束/杀掉应用