iOS面试题总结-未完待续

2023-05-16

iOS面试总结

1. 网络

  1. HTTP协议(HyperText Transfer Protocol)的请求和响应

    • 请求: 请求头, 请求行, 请求体
      • 请求行: 指定请求方法, 请求路径 协议版本等信息
      • 请求头: 描述客户端环境, 例如: host要请求的主机地址, UserAgent客户端类型, Accept 可接受数据类型, Accept_language可接受语言
      • 请求体: 客户端要发送的具体数据, 例如上传时的上传数据
    • 响应: 状态行,响应头, 响应数据
      • 状态行: 包含http协议版本, 状态码
      • 响应头: 服务器描述, 数据类型描述例如 Sever, Content-Type
      • 响应数据: 请求所获得的后台数据
  2. HTTP的特点

    • 无状态: 服务器不会保留客户端状态, 不会记忆上次的状态, 不受前面请求的影响, 客户端每次请求独立, 每次请求需带上自己的状态

    • 持久链接: 每个链接可以处理多个事务, 特殊强调http1.0及其之前版本都是非持久链接(每个链接只能处理一个事务)

      持久链接如何结束?

      • 如果有Content-length字段, 可以判断接受数据是否完成,
      • 如果是分块传输, 响应头中不包含Content-length, 服务器传输完成万发送一个空数据块, 当客户端收到空数据块时算作数据接受完成
  3. HTTP的请求方式

    GET、POST、PUT、DELETE、HEAD、OPTIONS等

  4. GET和和POST的区别:

    • get请求参数拼接在url后面, post的参数拼接在请求体里面,相对安全(在http抓包情况下依然不安全)

    • get参数长度有限制2048, post无限制

    • get获取资源是安全的(不会修改服务器资源), 幂等的(多次执行结果相同), 可缓存的(可以直接有CDN缓存, 减轻服务器负担), post相反

  5. HTTP常用状态码含义 参考文档点击 HTTP状态码总结

    • 1xx 表示消息

      • 100 初始的请求已经接受,客户应当继续发送请求的其余部分, 例如post请求(两段)的第一段请求收到的就是100
      • 101 服务器将遵从客户的请求转换到另外一种协议
    • 2xx 表示成功

      • 200: OK, 完全正常
    • 3xx 表示重定向

    • 4xx 表示请求错误

      • 400 请求出现语法错误
      • 401 访问收到密码保护的页面
      • 403 服务器拒绝执行,
      • 404 服务器资源未找到
      • 405 请求方法(get, post等)对指定资源不适用,
    • 5xx 表示服务器错误

      • 500 服务器遇到意料不到的错误, 不能完成客户端请求
  6. TCP和UDP的特点, 参考链接 简单理解TCP/IP协议

    • TCP协议(Transport Control Protocol,传输控制协议): 是一种面向连接、可靠的、基于字节流的传输层协议,采用了确认机制、超时重传机制,还会对接收到的TCP报文段进行重新排列整理。(TCP报头含20字节定长、选项和填充<选项和填充小于等于40字节>)(TCP是一种面向连接的传输层协议。它可以保证两端通信主机之间的通信可达。TCP能够正确处理传输过程中丢包、传输顺序乱掉等异常情况。)

      TCP能保证可靠性、稳定性, 适用于可靠性较高的服务

    • UDP协议:(User Datagram Protocol,用户数据报协议)是一种不可靠无连接的传输层协议,不考虑流控制、错误控制,没有重传机制,不会对分组进行顺序检查和排序。

      UDP控制选项少,无须建立连接,从而使得数据传输过程中的延迟小、数据传输效率高, 适用于实时性要求高的程序

  7. 网络七层模型

    应用层, 表示层, 会话层, 传输层, 网络层, 数据链路层, 物理层

    • 应用层: 网络应用如: http ftp, pop, smtp等应用协议
    • 表示层: 定义数据格式及加密, 例如,FTP允许你选择以二进制或ASCII格式传输。如果选择二进制,那么发送方和接收方不改变文件的内容。如果选择ASCII格式,发送方将把文本从发送方的字符集转换成标准的ASCII后发送数据。在接收方将标准的ASCII转换成接收方计算机的字符集。示例:加密,ASCII等。对应网络协议: Telnet, Rlogin, SNMP, Copher等
    • 会话层: 它定义了如何开始、控制和结束一个会话,包括对多个双向消息的控制和管理,以便在只完成连续消息的一部分时可以通知应用,从而使表示层看到的数据是连续的,在某些情况下,如果表示层收到了所有的数据,则用数据代表表示层。示例:RPC,SQL等
    • 传输层: 这层的功能包括是否选择差错恢复协议还是无差错恢复协议,及在同一主机上对不同应用的数据流的输入进行复用,还包括对收到的顺序不对的数据包的重新排序功能。示例:TCP,UDP,SPX。
    • 网络层: 这层对端到端的包传输进行定义,它定义了能够标识所有结点的逻辑地址,还定义了路由实现的方式和学习的方式。为了适应最大传输单元长度小于包长度的传输介质,网络层还定义了如何将一个包分解成更小的包的分段方法。示例:IP,IPX, UUCP等。
    • 数据链路层: 它定义了在单个链路上如何传输数据。这些协议与被讨论的各种介质有关。示例:ATM,FDDI等。
    • 物理层: OSI的物理层规范是有关传输介质的特性,这些规范通常也参考了其他组织制定的标准。连接头、帧、帧的使用、电流、编码及光调制等都属于各种物理层规范中的内容。物理层常用多个规范完成对所有细节的定义。示例:Rj45,IEEE 802.3 等。
  8. Socket

    客户端: socket()->connect()->write()/read()->close()

    服务端: socket()->bind()->listen()->accept()->read()/write()->close()

  9. 数据解析:

    • JSON: 使用NSJSONSerialization解析
    • XML: SAX解析, DOM解析
      • SAX: NSXMLParser 实现代理方法解析, 特点从上往下依次解析
      • DOM: 特点一次性读取解析
  10. 网络安全

    • Base64加密, 可逆加密

    • MD5/SHA1/SHA256等 不可逆加密

    • 对称加密和非对称加密

      • 对称加密: 加解密使用同一个秘钥, 代表: DES, AES等, 有点效率高, 缺点, 秘钥交换时安全不能保障

      • 非对称加密: 加解密双方使用不同的秘钥, 加密使用公钥加密, 解密是用私钥解密, 公钥是公开的 代表: RSA, ECC, DSA(数字签名用)

  11. HTTPS

    • HTTPS中的S含义

      S代表的是SSL/TLS协议, 即: Secure Sockets layer(安全套接层)和Transport Layer Security(安全传输层)

    • HTTPS的建立流程

      HTTPS为了兼顾安全与效率,同时采用了对称加密算法和非对称加密算法, 通信过程主要会设计三个秘钥: 服务器端的公钥和私钥, 用来进行非对称加密, 客户端随机生成的随机秘钥, 用来进行对称加密

      1. 客户端访问HTTPS链接, 告诉服务器客户端支持的加密算法列表, 和随机数C
      2. 服务器选择一种对称算法(如AES), 一种非对称算法, 一种MAC算法发送给客户端, 同时把数字证书和随机数S发给客户端
      3. 客户端验证服务器的数字证书, 客户端生成前主秘钥, 并使用服务器的公钥加密发送个服务器, 并使用前主秘钥/随机数C/随机数S, 生成会话秘钥
      4. 服务器解密得到前主秘钥, 通过前主秘钥/随机数C/随机数S生成会话秘钥
      5. 数据加密传输
  12. NSURLSession

    • NSURLSession的优势

      • 支持后台上传下载
      • 可以暂停, 停止, 重启网络任务
      • 可以对缓存策略,session类型、任务类型(比如上传、下载等任务)进行单独的配置
      • 支持block回调使用方便
      • 支持IPV6网络
    • NSURLsession的使用

      NSURLsession 是一个管理类, 可以通过NSURLSessionConfiguration进行配置

      URLSessionTask是任务的父类, 包含两个子类URLSessionDataTask和URLSessionDownloadTask, 其中URLSessionDataTask有一个子类URLSessionUploadTask, URLSessionDataTask用来处理一般网络请求

      URLSessionDownloadTask处理下载任务, URLSessionUploadTask上传任务

2. 多线程

  1. iOS开发常用的多线程方式

    • pthread: C语言实现, 可以跨平台, 线程生命周期需要手动管理
    • NSThread: OC实现, 线程生命周期需要手动管理
    • GCD: 苹果对多核性能优化, C语言, 线程生命周期自动管理
    • NSOperation: 对GCD封装, 线程生命周期自动管理

  2. NSThread简单介绍

    • 创建线程对象: 显示创建(alloc init)和隐式创建(performSelector…)

    • 线程状态:新建, 就绪(start), 阻塞(sleep), 运行, 死亡(exit)

    • 常用属性: name(当前线程名字), threadPriority(线程优先级), isMainThread等

  3. GCD和NSOperation对比

    • GCD执行效率更高,而且由于队列中执行的是由block构成的任务,这是一个轻量级的数据结构,写起来更方便
    • GCD只支持FIFO的队列,而NSOperationQueue可以通过设置最大并发数,设置优先级,添加依赖关系等调整执行顺序
    • NSOperationQueue甚至可以跨队列设置依赖关系,但是GCD只能通过设置串行队列,或者在队列内添加barrier(dispatch_barrier_async)任务,才能控制执行顺序,较为复杂
    • NSOperationQueue因为面向对象,所以支持KVO,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished)、是否取消(isCanceled)
  4. 什么情况下会出现死锁

    • 在主线程中将同步任务添加到主队列会导致死锁

      - (void)viewDidLoad {
          [super viewDidLoad];
          
          dispatch_sync(dispatch_get_main_queue(), ^{
             
              NSLog(@"deallock");
          });
          // Do any additional setup after loading the view, typically from a nib.
      }
      
    • 在一个串行队列任务中将 同步任务添加到当前的队列中

      dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
      
      dispatch_async(serialQueue, ^{
      	dispatch_sync(serialQueue, ^{
      		NSLog(@"deadlock");
        });
      });
      
  5. GCD中的线程栅栏

    dispatch_barrier_async/dispatch_barrier_sync 分别表示同步栅栏和异步栅栏

    栅栏的作用是可以将任务分块执行, 条件是任务必须在同一个队列上, 否则无效

    dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
        
        for (NSInteger i = 0; i < 10; i++) {
            
            dispatch_async(concurrentQueue, ^{
                NSLog(@"%zd",i);
            });
        }
        
        dispatch_barrier_async(concurrentQueue, ^{
            NSLog(@"barrier");
        });
        NSLog(@"哈哈哈");
        for (NSInteger i = 10; i < 20; i++) {
            
            dispatch_async(concurrentQueue, ^{
                
                NSLog(@"%zd",i);
            });
        }
     执行结果: 
     哈哈哈
     0-9 乱序打印
     barrier
     10-19乱序打印
     
    

    由于采用的是异步栅栏, 所以栅栏后的任务会"哈哈哈"会提起执行, 如果将栅栏换成同步栅栏则 "哈哈哈"一定是在"barrier"之后执行

    通过线程栅栏可以实现多度单写, 即允许多个地方同时读取数据, 但是在写入数据时只允许一个地方写入

    - (id)readDataForKey:(NSString *)key {
        __block id result;
        
        dispatch_sync(_concurrentQueue, ^{
            result = [self valueForKey:key];
        });
        
        return result;
    }
    
    - (void)writeData:(id)data forKey:(NSString *)key {
        dispatch_barrier_async(_concurrentQueue, ^{
            [self setValue:data forKey:key];
        });
    }
    
    
  6. GCD线程组的使用

    需求多个网络请求完成之后一并刷新UI

    - (void)testGCDGroup {
        __block NSInteger number = 0;
        
        dispatch_group_t group = dispatch_group_create();
        
        //A耗时操作
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(3);
            number += 2222;
            NSLog(@"A:%zd", number);
        });
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [self sendRequestWithCompletion:^(id response) {
                number += [response integerValue];
                NSLog(@"B:%zd", number);
            }];
        });
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [self sendRequestWithCompletion:^(id response) {
                number += [response integerValue];
                NSLog(@"C:%zd", number);
            }];
        });
        
        //        //B网络请求
        //        dispatch_group_enter(group);
        //        [self sendRequestWithCompletion:^(id response) {
        //            number += [response integerValue];
        //            NSLog(@"B:%zd", number);
        //            dispatch_group_leave(group);
        //
        //        }];
        //
        //        //C网络请求
        //        dispatch_group_enter(group);
        //        [self sendRequestWithCompletion:^(id response) {
        //            number += [response integerValue];
        //            NSLog(@"C:%zd", number);
        //            dispatch_group_leave(group);
        //        }];
        //
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"finish: %zd", number);
        });
        
    }
    
    - (void)sendRequestWithCompletion:(void (^)(id response))completion {
        //模拟一个网络请求
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(queue, ^{
            sleep(2);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(@1111);
            });
        });
    }
    
    调用结果:
    B:1111
    C:2222
    A:4444
    finish: 4444
    
    

    使用方式如代码所示, 先创建线程组, 然后使用dispatch_group_async, 将任务绑定到线程组, 最后使用dispatch_group_notify接受线程组任务完成的回调. 其中也可以使用dispatch_group_enter(group)这种方式将任务添加到线程组, 不过任务结束之后要配合使用 dispatch_group_leave(group);

  7. Dispatch Semaphore 信号量

    Dispatch Semaphore 提供了三个函数

    • dispatch_semaphore_create: 创建一个Semaphore并初始化总量
    • dispatch_semaphore_signal: 发送信号, 让信号总量加1
    • dispatch_semaphore_wait: 可以是信号总量减1, 信号总量为0是进入等待

    Dispatch Semaphore 在实际开发中主要用于:

    • 保持线程同步,将异步执行任务转换为同步执行任务

      - (void)testSemaphore {
          dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
          
          __block NSInteger number = 0;
          
          dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
              number = 100;
              NSLog(@"执行异步任务");
              dispatch_semaphore_signal(semaphore);
          });
          
          dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
          NSLog(@"semaphore---end,number = %zd",number);
      }
      //打印结果如下: 
      //执行异步任务
      //semaphore---end,number = 100
      
      

      创建信号量初始化总量为0, 由于是异步执行,所以直接执行到dispatch_semaphore_wait, 如果信号量为0则一直等待阻断线程, 当异步任务执行完之后将信号量加1, 此时信号量等待函数终结, 继续执行后面的任务

    • 保证线程安全,为线程加锁

      - (void)testSemaphore2 {
          _semaphore = dispatch_semaphore_create(1);
       
          for (NSInteger i = 0; i < 100; i++) {
              
              dispatch_async(dispatch_get_global_queue(0, 0), ^{
                  
                  [self asyncTask];
              });
          }
      }
      
      - (void)asyncTask {
          dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
          _count++;
          sleep(1);
          NSLog(@"执行任务:%zd",_count);
          dispatch_semaphore_signal(_semaphore);
      }
      
      ///执行结果发现, 是顺序从1-100执行
      

      原因: 在子线程执行并发任务时, 由于第一次执行任务将信号量减1, 信号总量变为0, 当第二个任务进来时需要由于信号总量为0所以进入等待状态, 任务一执行完之后将信号量加1, 任务二开始执行, 并立即将信号总量减1变为0, 任务三继续等待, 依次类推, 实现了线程加锁目的

  8. 使用dispatch_once实现单例

    //手写单例
    - (id)sharedInstance {
    		static id instance = nil;
    		static dispatch_once_t onceToken;
    		dispatch_once(&onceToken, ^{
    				instance = [[self alloc] init];
    		})
    		return instance;
    }
    
  9. NSOperationQueue/NSOperation简介

    • 优势

      • 可以添加任务依赖,方便控制执行顺序

      • 可以设定操作执行的优先级

      • 可以设置最大并发量

      • 任务执行状态控制:isReady,isExecuting,isFinished,isCancelled

        如果只是重写NSOperation的main方法,由底层控制变更任务执行及完成状态,以及任务退出
        如果重写了NSOperation的start方法,自行控制任务状态
        系统通过KVO的方式移除isFinished==YES的NSOperation

    • 基本操作

      NSOperation是抽象类, 主要使用两个子类: NSBlockOperation和NSInvocationOperation

      可以直接添加Block任务NSOperation到队列中执行, 默认在当前线程执行, NSBlockOperation添加多任务时会自动开启新线程执行

      也可以将NSOperation添加到NSOperationQueue中执行操作, 当maxConcurrentOperationCount = 1时为串行队列, 大于1时为并发队列

    • 示例代码

      /**
       * 设置 MaxConcurrentOperationCount(最大并发操作数)
       */
      - (void)setMaxConcurrentOperationCount {
      
          // 1.创建队列
          NSOperationQueue *queue = [[NSOperationQueue alloc] init];
      
          // 2.设置最大并发操作数
          queue.maxConcurrentOperationCount = 1; // 串行队列
      // queue.maxConcurrentOperationCount = 2; // 并发队列
      // queue.maxConcurrentOperationCount = 8; // 并发队列
      
          // 3.添加操作
          [queue addOperationWithBlock:^{
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                  NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
              }
          }];
          [queue addOperationWithBlock:^{
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                  NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
              }
          }];
          [queue addOperationWithBlock:^{
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                  NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
              }
          }];
          [queue addOperationWithBlock:^{
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                  NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
              }
          }];
      }
      

      执行结果为顺序执行1,1,2,2,3,3,4,4

      如果将maxConcurrentOperationCount改为2, 则并并发执行, 这里就不再验证

    • NSOperation的操作依赖设置

      /**
       * 操作依赖, 操作2依赖操作1
       * 使用方法:addDependency:
       */
      - (void)addDependency {
      
          // 1.创建队列
          NSOperationQueue *queue = [[NSOperationQueue alloc] init];
      
          // 2.创建操作
          NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                  NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
              }
          }];
          NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                  NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
              }
          }];
      
          // 3.添加依赖
          [op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,在执行op2
      
          // 4.添加操作到队列中
          [queue addOperation:op1];
          [queue addOperation:op2];
      }
      
    • NSOperation 提供了queuePriority属性, 新建操作的默认优先级是Normal

      // 优先级的取值
      typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
          NSOperationQueuePriorityVeryLow = -8L,
          NSOperationQueuePriorityLow = -4L,
          NSOperationQueuePriorityNormal = 0,
          NSOperationQueuePriorityHigh = 4,
          NSOperationQueuePriorityVeryHigh = 8
      };
      
    • 线程间通信示例

      /**
       * 线程间通信
       */
      - (void)communication {
      
          // 1.创建队列
          NSOperationQueue *queue = [[NSOperationQueue alloc]init];
      
          // 2.添加操作
          [queue addOperationWithBlock:^{
              // 异步进行耗时操作
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                  NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
              }
      
              // 回到主线程
              [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                  // 进行一些 UI 刷新等操作
                  for (int i = 0; i < 2; i++) {
                      [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                      NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
                  }
              }];
          }];
      }
      
  10. RunLoop

    • 什么是RunLoop

      RunLoop是通过内部维护的事件循环来对事件进行管理的一个对象

    • 为什么main函数不会退出

      UIApplicationMain函数内部默认开启了主线程的RunLoop,并执行了一段无限循环的代码

    • RunLoop的数据结构

      NSRunLoop(Foundation)CFRunLoop(CoreFoundation)的封装,提供了面向对象的API
      RunLoop 相关的主要涉及五个类:

      • CFRunLoop:RunLoop对象

      • CFRunLoopMode:运行模式

        1. kCFRunLoopDefaultMode:默认模式,主线程是在这个运行模式下运行
        2. UITrackingRunLoopMode:跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)
        3. UIInitializationRunLoopMode:在刚启动App时第进入的第一个 Mode,启动完成后就不再使用
        4. GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到
        5. kCFRunLoopCommonModes:伪模式,不是一种真正的运行模式,是同步Source/Timer/Observer到多个Mode中的一种解决方案

        苹果对外开放的主要有kCFRunLoopDefaultModekCFRunLoopCommonModes

        一个比较常见的问题:滑动tableView时,定时器还会生效吗?
        默认情况下RunLoop运行在kCFRunLoopDefaultMode下,而当滑动tableView时,RunLoop切换到UITrackingRunLoopMode,而Timer是在kCFRunLoopDefaultMode下的,就无法接受处理Timer的事件。 怎么去解决这个问题呢?把Timer添加到UITrackingRunLoopMode上并不能解决问题,因为这样在默认情况下就无法接受定时器事件了。
        所以我们需要把Timer同时添加到UITrackingRunLoopModekCFRunLoopDefaultMode上。
        那么如何把timer同时添加到多个mode上呢?就要用到NSRunLoopCommonModes

        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
        
      • CFRunLoopSource:输入源/事件源

      • CFRunLoopTimer:定时源

      • CFRunLoopObserver:观察者

    • RunLoop常见问题

      - (void)test {
      	NSLog(@"1");
      	dispatch_async(dispatch_get_global_queue(0, 0), ^{
              
              NSLog(@"2");
              
              [[NSRunLoop currentRunLoop] run];
              
              [self performSelector:@selector(test) withObject:nil afterDelay:10];
        
              NSLog(@"3");
      	});
      	NSLog(@"4");
      }
      
      - (void)test {
      	NSLog(@"5");
      }
      

      输出顺序? 答案是: 1423, 5不会执行, 原因是runloop开启时应该有任务执行mode中应该有item才行, 否则会退出, 所以应调整成如下代码

      dispatch_async(dispatch_get_global_queue(0, 0), ^{
              
              NSLog(@"2");
              //先添加任务
               [self performSelector:@selector(test) withObject:nil afterDelay:10];
               //再开启循环
              [[NSRunLoop currentRunLoop] run];
              NSLog(@"3");
      	});
      
    • 如何创建一个常驻线程

         @autoreleasepool {
              
              NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
              
              [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
              
              [runLoop run];
              
          }
      
    • 怎样保证子线程数据更新回到主线程更新UI时不影响滑动操作

      答: 将更新UI的任务添加到主线程的NSDefaultRunLoopMode 上执行即可, 这样主线程将会在用户停止滑动之后由UITrackingRunLoopMode切换到NSDefaultRunLoopMode之后更新UI

      [self performSelectorOnMainThread:@selector(reloadData) 
      											 withObject:nil waitUntilDone:NO
                                  modes:@[NSDefaultRunLoopMode]];
      
  11. 自旋锁与互斥锁

    • 自旋锁

      当任务被另一个线程锁定是, 尝试执行的线程会进入等待(不会休眠), 等上一个线程解除锁定时, 立即执行下一个线程任务,

      优点: 因为自旋锁不会引起调用者睡眠,所以不会进行线程调度,CPU时间片轮转等耗时操作。所有如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁

      缺点: 自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。自旋锁不能实现递归调用

      常用自旋锁: atomic, dispatch_semaphore_t

    • 互斥锁

      当上一个线程锁定时, 下一个尝试执行的线程会进入休眠, 等上一个线程解除锁定, 下一个线程自动唤醒然后执行任务

      常用互斥锁: NSLock, NSCondition, @ synchronized

3. UI

  1. UIView与CALayer的关系

    UIView为CALayer提供内容,以及负责处理触摸等事件,参与响应链
    CALayer负责显示内容contents

  2. 事件传递与响应者链

    • 事件传递过程

      • 触摸事件发生后, 系统会将事件加入到UIApplication管理的事件队列

      • UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常 先发送事件给应 用的主窗口 keyWindow,主窗口再把事件发送给rootViewController

      • rootViewController再把事件发送给他的根View,然后会在View的层次结构中找到一个最合适的视图来处 理触摸事件, 找到合适的视图控件后,就会调用视图控件的touches方法来做具体的事件处理, 注意如果

      • 找到合适的视图控件后,就会调用视图控件的touches方法来做具体的事件处理

            touchesBegan
            touchesMoved
            touchesEnded
        
    • 响应者链, 找到最合适处理事件的view

      • 判断当前view是否能够接收触摸事件
      • 判断当前的点,是否在当前view中
      • 如果点在当前的view中 遍历当前view的所有子view,遍历的过程是从后往前遍历的 如果找到子控件,返回子控件 如果没有子控件满足条件,返回当前view
      //    hitTest:withEvent:方法的处理流程如下:
      //    • hitTest:withEvent:会忽略隐藏、不和用户交互的、透明度小于0.01的视图
      //    • 首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内;
      //    • 若返回NO,则hitTest:withEvent:返回nil;
      //    • 若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,所有
      //    子视图的遍历顺序是从top到bottom,即从subviews数组的末尾向前遍历,直到有子视图
      //      返回非空对象或者全部子视图遍历完毕;
      //    • 若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束;
      //    • 如所有子视图都返回非,则hitTest:withEvent:方法返回自身(self)。
      
      
      - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
      //1 判断当前view,是否可以接收事件
      	if (!self.userInteractionEnabled || self.alpha <= 0.01 || self.hidden) {
      			return nil; 
        }
      //2 判断点击的点,是否在当前view中
      	if (![self pointInside:point withEvent:event]) {
      			return nil; 
        }
      //3 从后往前遍历当前view的子view,是否是最合适的view,如果不是返回self 
        NSInteger count = self.subviews.count;
      	for (NSInteger i =count-1;i>=0 ; i--) {
      			UIView *childView = self.subviews[i]; //把当前坐标系的点,转换成子view坐标系的点
      			CGPoint subPoint = [self convertPoint:point toView:childView]; //寻找最合适的view
      		//        //判断点是否在子控件中
      			if ([self pointInside:subPoint withEvent:event]) {
         			 	UIView *fitView = [childView hitTest:subPoint withEvent:event]; 
         				if (fitView) {
      						return fitView; 
                }
         		}
      	}
      	return self;
      }
      
  3. 图像显示原理

    • CPU工作

      • Layout: 布局
      • Display: 绘制
      • Prepare: 图片解码
      • Commit: 提交位图
    • GPU工作

      顶点着色,图元装配,cell光栅化,片段着色,片段处理

  4. 滚动视图优化:

    • CPU角度

      • 对象创建销毁,调整, 对于开销大的对象进行优化处理
      • 预排版: 布局计算, 文本计算, 采用缓存高度等
      • 预渲染: 文本图片异步绘制等
    • GPU角度

      • 考虑是有有不必要的CPU渲染

      • 是否有太多的离屏渲染

      • 是否有图层的混合操作(透明度尽量都不要使用)

      • view的层次结构是否合理

      • cell光栅化处理

        // 光栅化
        layer.shouldRasterize = true
        layer.rasterizationScale = UIScreen.mainScreen().scale
        
        // 异步绘制
        layer.drawsAsynchronously = true
        
  5. 什么是离屏渲染(Off-Screen Rendering)

    • 与之对应的就是On-Screen Rendering(在屏渲染), GPU的渲染操作主要用于显示当前屏幕缓冲去进行的操作, 而离屏渲染指的是GPU在当前屏幕缓冲区外新开辟缓冲区进行渲染操作

    • 离屏渲染什么时候会触发?

      答案: 圆角, 蒙版, 阴影, 等

4. OC语言特性

  1. 分类category

    • 分类作用: 声明私有方法, 给已存在类扩展方法(实例方法, 类方法, 协议, 属性), 属性需要借助运行时的关联对象, 不能直接添加属性

    • 分类特点: 运行时决议

  2. 扩展

    声明私有属性,声明方法(没什么意义),声明私有成员变量

  3. 代理

    代理是一种设计模式,以@protocol形式体现,一般是一对一传递。
    一般以weak关键词以规避循环引用。

  4. 通知

    使用观察者模式来实现的用于跨层传递信息的机制。传递方式是一对多的。

  5. KVO

    • 实现原理 :

      KVO的实现依赖于Objective-C强大的runtime,KVO的底层实现是监听setter方法。当观察某对象A时,KVO动态机制会动态创建一个A类的子类NSKVONotifying_A,并为这个新的子类重写父类的属性的setter方法, 方法内容如下

      [super setAge:age]; `
      [self willChangeValueForKey:@"age"];
      [self didChangeValueForKey:@"age"];
      ///其中后面两个方法会调用
      -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
        
      
    • 作用:

      能够监听某个对象属性值的改变

  6. KVC

    通过Key值直接访问对象属性,或者给属性赋值的操作, 常用操作valueforkey: / setValue: forKey:

    底层执行机制: (属性name举例子)

    • 程序优先调用属性setName: 方法,代码通过setter方法完成设置。
    • 如果没有属性name, 会按照_name,_iskey,key,iskey的顺序搜索成员变量
    • 如果以上都没有会调用setValue:forUndefinedKey: 方法抛出错误

5 RunTime

RunTime是OC语言底层运行原理, OC代码最终都要通过runtime去调用,

  1. 常见的runtime应用如下

    • 交换方法

      通过交换方法可以实现拦截系统方法, 添加自己的需求

      //常用方法
      class_getClassMethod 获取类方法
      class_getInstanceMethod 获取实例方法
      method_exchangeImplementations 方法交换
      
      在load方法中执行狡猾
      
    • 关联对象(给分类添加属性)

      const char* name = "rylsj";
      - (void) setNick:(NSString *)nick {
          objc_setAssociatedObject(self, &name, nick, OBJC_ASSOCIATION_COPY_NONATOMIC);
      }
      - (NSString*) nick {
          return objc_getAssociatedObject(self, &name);
      }
      
    • 字典转模型

      • 获取属性列表
    • 动态添加方法

      - (void) rylsj_AddMethod {
        //"v@:@": v表示void, @表示id, :表示 SEL
          class_addMethod([self.persion class], @selector(run:), (IMP)runMethod, "v@:@");
      }
      
      void runMethod(id self, SEL _cmd, NSString* rylsj) {
          NSLog(@"%@", rylsj);
      }
      
      //调用
       if ([self.persion respondsToSelector:@selector(run:)]) {
              [self.persion performSelector:@selector(run:) withObject:@"66 rylsj"];
       } else {
              NSLog(@"方法没有实现!!");
       }
      
  2. OC 消息转发机制 (文中代码有错误, 可以c)

    1. 首先根据receiver对象的isa指针获取它对应的class

    2. 优先在class的cache查找message方法,如果找不到,再到
      methodLists查找

    3. 如果没有在class找到,再到super_class查找

    4. 一旦找到message这个方法,再依据receiver 中的self 指针找到当前的对象,调用当前对象的具体实现的方法(IMP),然后传递参数,调用实现方法。

    5. 当对象收到无法解读的消息后, 就会启动"消息转发(Message forwarding)"机制, 程序员可以通过消息转发告诉对象应该如何处理位置消息, 过程如下:

      1. 系统会调用resolveInstanceMethod方法, 如果是类方法则会调用resolveClassMethod, 在这个方法中我们可以增加调用方法的实现, 我们可以通过一个Person类来实践一下

        @interface Person : NSObject
        - (void)run;
        @end
        @implementation Person
        ///要添加的方法
        void run (id self, SEL _cmd) {
        		NSLog(@"人跑");
        }
        //在这个方法中添加自己的方法实现
        + (BOOL)resolveInstanceMethod:(SEL)sel {
            if(sel == @selector(run)){
              //关于生成签名的类型"v@:"解释一下。每一个方法会默认隐藏两个参数,self、_cmd,self代表方				法调用者,_cmd代表这个方法的SEL,签名类型就是用来描述这个方法的返回值、参数的,v代表				  返回值为void,@表示self,:表示_cmd。 
                class_addMethod(self, sel, (IMP)run, "v@:");
                return YES;
            }
            return [super resolveInstanceMethod:sel];
        }
         
        @end
          
          
        @implementation ViewController
        - (void)viewDidLoad {
            [super viewDidLoad];
          	Person *person = [Person new];
            [person run];
          
        }
        @end
          
        //调用结果打印: "人跑"
          
        

        如果未实现以上方法, 则会进入步骤二

      2. forwardingTargetForSelector: 调用

        在这个方法中可以返回你需要转发消息的对象, 这里我们可以新建一个对象Car来演示, 在Person中不是现方案一中的resolveInstanceMethod方法 改成实现forwardingTargetForSelector方法 如下:

        @interface Car : NSObject
        - (void)run;
        @end
          
        @implementation Car
        - (void)run {
            NSLog(@"车跑");
        }
        @end
          
        @implementation Person
          
        - (id)forwardingTargetForSelector:(SEL)aSelector {
          	return [Car new];
        }
          
        //执行结果: 车跑
        
        
        

        通过forwardingTargetForSelector把消息转发给我们认为合适的对象去执行, 如果此步骤未实现, 则会进入下一步

      3. 通过forwardInvocation函数 设置我们自己生成的函数签名和对象

        //生成签名
        - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
            NSString *sel = NSStringFromSelector(selector);
            if([sel isEqualToString:@"run"]) {
                return [NSMethodSignature signatureWithObjCTypes:"v@:"];
            }
            return [super methodSignatureForSelector:selector];
        }
        
        //设置
        - (void)forwardInvocation:(NSInvocation *)invocation {
            SEL selector = [invocation selector];
            //新建需要转发消息的对象
            Car *car = [[Car alloc] init];
            if([car respondsToSelector:selector]){
                [invocation invokeWithTarget:car];
            }
        }
        
        //执行结果: 车跑
        
        

        以上是当前类未实现方法的三种不就措施, 调用顺序从前往后, 如果实现了第一种, 后面就不会执行以此类推, 如果都没实现程序就会crash

6. 内存

  1. 内存布局, 从代码区到栈区地址一次从低到高

    • 栈区(stack): 方法调用,局部变量等,是连续的,高地址往低地址扩展, 系统自动管理
    • 堆区(heap): 通过alloc等分配的对象,是离散的,低地址往高地址扩展,需要我们手动控制
    • 未初始化数据(bss): 未初始化的全局变量等
    • 已初始化数据(data): 已初始化的全局变量等
    • 代码段(text): 程序代码 (常量区)
  2. static关键字的作用

    • 用于修饰存储类型使之成为静态存储类型

      在函数内定义的静态局部变量,该变量存在内存的静态区,所以即使该函数运行结束,静态变量的值不会被销毁,函数下次运行时能仍用到这个值。
      在函数外定义的静态变量——静态全局变量,该变量的作用域只能在定义该变量的文件中,不能被其他文件通过extern引用。
      
    • 用于修饰链接属性使之成为内部链接属性

      静态函数只能在声明它的源文件中使用。
      
  3. const关键字

    • 声明常变量,使得指定的变量不能被修改。

      const int a = 5;/*a的值一直为5,不能被改变*/
      const int b; b = 10;/*b的值被赋值为10后,不能被改变*/
      const int *ptr; /*ptr为指向整型常量的指针,ptr的值可以修改,但不能修改其所指向的值*/
      int *const ptr;/*ptr为指向整型的常量指针,ptr的值不能修改,但可以修改其所指向的值*/
      const int *const ptr;/*ptr为指向整型常量的常量指针,ptr及其指向的值都不能修改*/
      
    • 修饰函数形参,使得形参在函数内不能被修改,表示输入参数

      int fun(const int a);int fun(const char *str);
      
    • 修饰函数返回值,使得函数的返回值不能被修改。

      const char *getstr(void);使用:const *str= getstr();
      
      const int getint(void);  使用:const int a =getint();
      
  4. iOS 得内存管理机制?

    采用的是引用计数的方式进行内存管理, MRC下需要用户手动管理引用计数, ARC下, 系统禁用retain,release,retainCount,autorelease等关键字

  5. 自动释放池

    在当次runloop将要结束的时候调用objc_autoreleasePoolPop,并push进来一个新的AutoreleasePool, AutoreleasePoolPage是以栈为结点通过双向链表的形式组合而成,是和线程一一对应的。内部属性有parent,child对应前后两个结点,thread对应线程 ,next指针指向栈中下一个可填充的位置。

    实现原理:

    编译器会将 @autoreleasepool {} 改写为:

    void * ctx = objc_autoreleasePoolPush;
        {}
    objc_autoreleasePoolPop(ctx);
    
    • objc_autoreleasePoolPush:

      把当前next位置置为nil,即哨兵对象,然后next指针指向下一个可入栈位置,
      AutoreleasePool的多层嵌套,即每次objc_autoreleasePoolPush,实际上是不断地向栈中插入哨兵对象。

    • objc_autoreleasePoolPop:

      根据传入的哨兵对象找到对应位置。给上次push操作之后添加的对象依次发送release消息。回退next指针到正确的位置。

  6. 循环引用场景

    • 代理(delegate)引起的相互循环引用

      解决方案: 声明地阿里是,使用weak关键字修饰

    • NSTimer引起的循环引用

      解决方案: 在合适的时候进行invalidate 并将指针指向nil

    • Block引起的循环引用

      对block中使用的self进行弱化 __weak typeof(self) weakSelf = self;

      注意: 并不是所有block都会造成循环引用, 只有被强引用了的block才会产生循环引用

  7. Block的内存问题

    • Block的三种形式

      • 全局Block(_NSConcreteGlobalBlock), 存储在已初始化的数据区(.data)

        不使用外部变量的是全局Block, 如下:

        NSLog(@"%@",[^{
        		NSLog(@"globalBlock");
        } class]);
        
        //输出: __NSGlobalBlock__
        
      • 栈Block(_NSConcreteStackBlock), 存储在栈区(Stack)

        使用外部变量但是未进行copy操作的Block是栈Block, 如下:

        NSInteger num = 10;
        NSLog(@"%@",[^{
        		NSLog(@"stackBlock:%zd",num);
        } class]);
        
        //输出: __NSStackBlock__
        
      • 堆Block(_NSConcreteMallocBlock), 存储在堆区(Heap)

        对栈Block进行copy操作,就是堆block,而对全局Block进行copy,仍是全局Block, 如下:

        //对堆Block copy操作, 依然是全局Block
        void (^globalBlock)(void) = ^{
        		NSLog(@"globalBlock");
        };
        NSLog(@"%@",[globalBlock class]);
        //输出: __NSGlobalBlock__
        
        
        
        //对栈Block进行copy,是堆Block
        NSInteger num = 10;
        void (^mallocBlock)(void) = ^{
        		NSLog(@"stackBlock:%zd",num);
        };
        NSLog(@"%@",[mallocBlock class]);
        //输出: __NSMallocBlock__
        
        

        注意: 对栈Block copy之后,并不代表着栈Block就消失了,左边的mallock是堆Block,右边被copy的仍是栈Block

        - (void)testWithBlock:(dispatch_block_t)block {
            block();
            
            dispatch_block_t tempBlock = block;
            
            NSLog(@"%@,%@",[block class],[tempBlock class]);
        }
        //输出: __NSStackBlock__,__NSMallocBlock__
        

7. 数据存储

  1. iOS中常用数据持久化方式

    • NSUserDefaults, 适用于轻量数据存储

      偏好设置会将所有数据保存到同一个文件中。即preference目录下的一个以此应用包名来命名的plist文件。

      NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
      [defaults setObject:@"jack" forKey:@"firstName"];
      [defaults setInteger:10 forKey:@"Age"];
      ///现在可不手动调用同步方法
      [defaults synchronize];
      
    • 文件写入本地(数组, 字典, 字符串等) 如下:

      NSString *filePath = [[self getDocumentPath] stringByAppendingString:@"fileTest.txt"];
      NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:@"798293@qq.com", @"email", @"787907@qq.com", @"emailDisplay", nil];
      [dictionary writeToFile:filePath atomically:YES];
      
      
    • 对象归档

      需要对象实现NSCoding协议

      NSString *filePath = [[self getDocumentPath] 
      - (void)encodeWithCoder:(NSCoder *)aCoder {
        [aCoder encodeObject:_name forKey:kAddressCardName];
      }
      - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
        _name = [aDecoder decodeObjectForKey:kAddressCardName];
      }
      //存储
      [NSKeyedArchiver archiveRootObject:obj toFile:filePath];
      
    • 数据库(SQLite, CoreData)

      • SQLite 常用函数

        sqlite3_open() //创建并打开数据库连接
        sqlite3_exec() //执行数据库操作(crud)
        
      • SQLite常用语句

        • DDL(Data Definition Language) 数据库定义语句

          --create创表,
          create table t_student (id integer, name text, age inetger, score real);
          
          --drop删除表
          drop table if exists t_student; 
          
          
        • DML(Data Manipulation Language) 数据库操作语句

          --insert、
          insert into t_student (name, age) values (‘wg’, 10);
          --update、
          update t_student set name = ‘jack’, age = 20;
          --delete
          delete from t_student;
          
          
        • DQL(Data Query Language) 数据库查询语句

          -- select
          -- 查指定字段
          select name, age from t_student;
          -- 查全部
          select * from t_student ;
          -- 查数量
          select count (age) from t_student ;
          
          
          
        • 常用关键字: where,order by,group by和having等

          --where关键字示例
          delete from t_student where age <= 10 or age > 30 ;
          select * from t_student where age > 10 ;  
          -- 排序
          select * from t_student order by age desc 
          -- limit条件限制
          select * from t_student limit 4, 8 ;
          
          
        • 别名

          -- select 字段1 别名 , 字段2 别名 , … from 表名 别名 ; 
          select name myname, age myage from t_student;
          

8. 音视频开发

  1. 音频

    • 音效播放 (AVFoundation), 支持acc , wav 等格式, 特征: 比较短 30秒以内

      //创建音效
       NSString *path = [[NSBundle mainBundle]pathForResource:"music.acc" ofType:nil];
      NSURL *url  = [NSURL fileURLWithPath:path];
      AudioServicesCreateSystemSoundID((__bridge CFURLRef _Nonnull)(url), &soundID);
      //播放音效, 静音无振动
      AudioServicesPlaySystemSound(soundID);
      //静音有震动
      //AudioServicesPlayAlertSound(soundID);
      
      
      
    • 音乐播放 (AVAudioPlayer: 只能播放本地音乐, 不支持网络媒体)

      //1.创建一个音乐对象
      NSString *path = [[NSBundle mainBundle]pathForResource:@"music.mp3" ofType:nil];
      NSURL *url = [NSURL fileURLWithPath:path];
      AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil];
      
      //2. 准备, 播放
      [audioPlayer prepareToPlay];
          //播放
      [audioPlayer play];
      
    • 音频录制(AVAudioRecorder)

      //保存路径
       NSURL *url = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject]stringByAppendingPathComponent:@"recoder001.wav"]];
      //录音参数设置: AVSampleRateKey采样率 AVNumberOfChannelsKey音频通道数, AVLinearPCMBitDepthKey线性音频深度, 
      NSMutableDictionary *settings = [NSMutableDictionary dictionary];
      // 音频采样率
      settings[AVSampleRateKey] = @(8000.0);
      
      //创建音频录音机
      AVAudioRecorder *recorder = [[AVAudioRecorder alloc]initWithURL:url settings:settings error:&error];
      
      //录音
      [recorder record];
      //停止录音
      [self.recorder stop];
      
      
      
    • 网络媒体流播放(AVPlayer, 见下文视频播放描述)

  2. 视频

    • 几种视频播放

      首先在iOS平台使用播放视频,可用的选项一般有这四个,他们各自的作用和功能如下:

      使用环境优点缺点
      MPMoviePlayerControllerMediaPlayer简单易用, 自带页面不可定制
      AVPlayerViewControllerAVKit简单易用, 自带页面不可定制
      AVPlayerAVFoundation可定制度高,功能强大不支持流媒体
      IJKPlayerIJKMediaFramework定制度高,支持流媒体播放使用稍复杂
    • AVPlayer

      • 播放音频

        NSURL *playUrl = [NSURL URLWithString:@"http://www.xxx.com/music.mp3"];
        self.player = [[AVPlayer alloc] initWithURL:playUrl];
        [self.player play];//播放
        [self.player pause];//暂停
        self.player.rate = 1.5//倍速
        
        
      • 播放视频 需要创建显示层AVPlayerLayer

    • 直播

      • 主播端:

        1. 获取音视频授权

        2. 配置采样参数

          • 音频: 码率和采样率
          • 视频: 分辨率, 帧率, 码率
        3. 采集数据

          • 音频采集: AVAudioSession
          • 视频采集: GPUImageVideoCamera
        4. 编码

          • 音频编码: 软编码(h.264, mpeg等,第三方SDK)/硬编码(AudioToolbox, 原生)
          • 视频编码: 软编码(mp3, acc等, 第三方sdk)/硬编码(VideoToolbox, 原生)
        5. 推流

          • 音视频封装(flv或者TS格式)
          • 协议(RTMP, HLS, FLV)
          • 数据推送
      • 服务器端

        • 数据分发(CDN)
        • 截屏
        • 录制
        • 转码
      • 用户端

        1. 拉流: RTMP, HLS, FLV
        2. 解码: 软解码和硬解码
        3. 播放: IJKPlayer 开源播放器播放
      • 直播常见问题 请参考文章 iOS开发之移动直播技术秒开、直播优化经验、直播问题解析、直播知识解惑

9. iOS开发中的动画

  1. 核心动画(CoreAnimation)

    • 核心动画的分类(CAAnimation类的继承结构)

      CAAnimation有三个子类: CAAnimationGroup(组动画), CAPropertyAnimation(属性动画), CATranstion(转场动画), 其中属性动画又分为: CABasicAnimation(基础动画)和CAKeyframeAnimation(关键帧动画)

  2. 基础动画

    
    
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

iOS面试题总结-未完待续 的相关文章

随机推荐

  • 分布式系统核心—日志

    分布式系统的核心组件 日志 有时也叫write ahead logs commit logs 或者事物 logs 通常指在应用所有的修改之前先写入日志 xff0c 一般会将重放日志 撤销日志都写进去 NoSQL数据库 KV存储 Hadoop
  • ctags使用方法 ctags的使用方法

    用 ctags 看代码时 xff0c 检索函数及变量使用的文件是 tags 文件 有时我们会定制检索的文件范围 xff0c 这时候就可以通过 ctags 命令的一些参数来控制 tags 文件的内容 1 xff1a 递归检索当前目录以下所有默
  • AFNetworking 介绍和简单实用

    AFNetworking github AFNetworking AFNetworking 是一个网络请求封装框架 xff0c 使用简单 xff0c 功能强大 xff1b 在AFNetworking 3 x版本 通过封装NSURLSessi
  • Tomcat端口冲突的解决方法

    1 输入以下两条命令 span class ruby span class hljs number 1 span dos窗口中输入 xff1a netstat ano findstr span class hljs number 8080
  • Spring 框架介绍和使用

    微信公众号 xff1a 运维开发故事 xff0c 作者 xff1a 老郑 历史的选择 Spring 作为一个基础的框架 xff0c 是在 Java EE 开发历史中 xff0c 是成千上万公司选择 单独使用 Spring 的非常少了 xff
  • xshell无法调用图形化的解决方法

    在xshell无法调用图形化界面 xff0c 使用VNC服务 xshell中调用图形化界面需要2个地址互通 xff0c 只有一端通无法传输图形化界面 xff08 本地没有获取IP xff09 但是vnc只需要和服务器端连通即可 1 首先我们
  • python3+requests请求方式application/x-www-form-urlencoded传递数组Arrary

    python3 43 requests传递比较简单的key value格式数据比较简单 导入 requests 包 import requests 表单参数 xff0c 参数名为 fname 和 lname myobj 61 39 fnam
  • 用Java远程执行shell命令出现java: command not found

    一 问题发现 xff1a 在使用jsch远程调用shell命令时 xff0c 提示java command not found 这个错误的意思是linux的环境变量里没有配置JAVA HOME的内容 但是我在Linux上查看了一下环境变量
  • Android Design Support Library

    1 Navigation View 对于应用程序 xff0c 它代表着一个标准的导航菜单 菜单内容可以由菜单资源文件填充 NavigationView通常放在一个DrawerLayout里面 lt xml version 61 34 1 0
  • 日常问题:解决nested exception is org.apache.ibatis.executor.ExecutorException: No constructor found问题

    今天在调试上周编写好得代码程序的时候 xff0c 在执行到mybatis获取某行数据转换成自定义的类型时 xff0c 抛出了异常 xff1a nested exception is org apache ibatis executor Ex
  • Abort message: ‘FORTIFY: FD_SET: file descriptor 1070 >= FD_SETSIZE 128‘

    问题现象 压力测试骁龙相机 xff0c 发现camera provicer 进程崩溃 无法正常打开相机 xff0c 只有重新启动设备 相关的log xff1a 03 23 08 17 08 592 15634 15634 F DEBUG s
  • 滚动校验(Rolling Checksum)算法

    滚动校验 Rolling Checksum 算法 Rsync中使用了一种滚动检验 Rolling Checksum 算法 xff0c 用于快速计算数据块的检验值 它是一种弱校验算法 xff0c 采用的是Mark Adler的adler 32
  • Android GMS认证总结01

    测试项 失败项 备注 GTS com google android media gts WidevineYouTubePerformanceTests testL3Cenc720P30 pass com google android per
  • ZYNQ 在linux 通过AXI_GPIO操作电平

    在petalinux 通过AXI GPIO操作电平 以zynq为例 xff0c vivado工程 xff1a axi gpio n都是选择的一位输出 xff1a 管脚约束 xff1a set property SEVERITY Warnin
  • FSK,PSK,ASK,BPSK调制

    信号调制常用的三种基本方法是 xff1a 调幅 调频和调相 1 振幅调变 xff0c 简称为调zhi幅 xff0c 通过改变输出dao信号的振幅 xff0c 来实现传送信息的目的 一般在调制端输出的高频信号的幅度变化与原始信号成一定的函数关
  • ZYNQ移植vxworks系统

    版本 xff1a ZYNQ7010 xff0c VxWorks 6 9 ZYNQ PL端有灵活性好 xff0c 资源丰富 xff0c 可反复编程速度快的优势 xff0c 通过 PS的外设并行 AXI总线外挂 PL接口 xff0c 使用 FP
  • OpenCV-Python 3.X: cv2.xfeatures2d 无法使用问题解决

    由于专利的问题surf和sift特征已经被移到xfeatures2d里面 xff0c 这个模块需要安装opencv contrib python opencv的dnn模块可以加载深度学习模型 xff0c 但是dnn需要3 4以上 xff0c
  • 旧款Mac开启随航(sideCar)功能

    看到这个标题 不得不吐槽苹果 命名软硬件都可以支持 但是故意让旧设备不支持这个功能 真是鸡贼 吐槽完毕 本文参考博客 http dev zeppel eu luca SidecarCorePatch 里面是英文版的 所以我总结翻译以下几点
  • Masonry框架源码分析

    相信大多数iOS开发者对Masonry框架并不陌生 本文是笔者通读Masonry的代码之后的一篇总结 也希望可以帮助大家更好的理解该框架 怎奈笔者才疏学浅 如有遗漏或错误也欢迎大家评论区指出 大家一起进步 iOS布局的演进 在说Masonr
  • iOS面试题总结-未完待续

    iOS面试总结 1 网络 HTTP协议 HyperText Transfer Protocol 的请求和响应 请求 请求头 请求行 请求体 请求行 指定请求方法 请求路径 协议版本等信息请求头 描述客户端环境 例如 host要请求的主机地址