iOS 面试题收录

  • 此贴只为收集,有好的面试题请在评论区回复或者加QQ:734566743。
  • 未按类别区分
  1. 请简单的介绍下APNS发送系统消息的机制
APNS优势:杜绝了类似安卓那种为了接受通知不停在后台唤醒程序保持长连接的行为,由iOS系统和APNS进行长连接替代。
APNS的原理:
    1). 应用在通知中心注册,由iOS系统向APNS请求返回设备令牌(device Token)
    2). 应用程序接收到设备令牌并发送给自己的后台服务器
    3). 服务器把要推送的内容和设备发送给APNS
    4). APNS根据设备令牌找到设备,再由iOS根据APPID把推送内容展示
  1. 通信底层原理(OSI七层模型)
物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
  1. UITableView 的优化
1). 正确的复用cell。
2). 设计统一规格的Cell
3). 提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;
4). 异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;
4). 滑动时按需加载,这个在大量图片展示,网络加载的时候很管用!
5). 减少子视图的层级关系
6). 尽量使所有的视图不透明化以及做切圆操作。
7). 不要动态的add 或者 remove 子控件。最好在初始化时就添加完,然后通过hidden来控制是否显示。
8). 使用调试工具分析问题。
9). cell的展示数据最好在展示之前处理,避免过多的逻辑处理 
  1. ios 内存几大区域
1). 栈区(stack): 栈区主要存放局部变量和函数参数等相关变量,超出作用域之后自动
释放。由编译器自动分配和释放。
2). 堆区(heap): 堆区存放alloc,new等关键字生成的对象。由程序员分配和释放,若
不释放,则程序结束由操作系统回收。
3). 全局静态区(global): 全局静态区主要存放静态数据,全局数据和常量,程序运行之后一直存在。由编译器管理(分配和释放)。
4). 文字常量区: 由编译器管理(分配释放),程序结束由系统释放;存放常量字
符。
5). 程序代码区: 存放函数的二进制代码。
注:我们平时关注最多的部分都是堆区的内存。
  1. 数据存储的优劣
1). NSUserDefaults
     优点:轻量级,易操作,保密性好
     缺点:
          1.不支持自定义对象的存储。
          2.存储的数据都是不可变的,想将可变数据存入需要先转为不可变才可以存储。
          3.定时把缓存中的数据写入磁盘的,而不是即时写入。
     场景:用于存储用户的偏好设置和用户信息,如用户名,是否自动登录,字体大小等。
2). plist(属性列表文件)
      优点:轻量级,易上手
      缺点:
           1.不支持自定义对象的存储。
           2.对数据的操作比较繁琐。需要全部读写。
      场景:用于存储程序中经常用到且数据量小而不经常改动的数据。
3). 归档(属性列表文件)
      优点:可以创建自己想要的数据模型,然后统一以模型方式存储
      缺点:
           1.速度相对较慢
           2.保存和读取都需要一次性读取和写入。不支持检索。
           3.需要对对象进行归档,结档操作,相对麻烦。
      场景:需要对自定义模型进行保存。比如通讯录分类排序。保存对象
4). FMDB
      优点:支持大批量的数据存储,支持检索,操作方便
      缺点:
           1.原生的支持类型不多。
           2.需要有sql基础。
      场景:即时聊天,社交。
5). CoreData
      优点:支持对象存储。无需任何sql语句。支持可视化管理
      缺点:
           1.操作繁琐
      场景:即时聊天,社交。
  1. 多线程(偏GCD)
1). 概念
      同一个进程中同时开启多个线程,每条线程执行不同的任务。
2). 优点
      1. 多条线程同时(并发)执行,提高程序的执行效率。
      2. 提高资源利用率,包括CPU、内存等。
3). 缺点
      1. 开启新线程会占用一定内存,线程过多会降低性能。
      2. 程序设计更加复杂,比如线程之间的通信、多条线程的数据共享。
4). 进程
      1. 进程是系统进行资源分配和调度的基本单位,是操作系统结构的基础,主要管理资源。
      2. 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。
5). 线程
      1. 线程是进程的基本执行单元,即进程想要执行任务,必须得有线程。
      2. 程序执行流的最小单元,线程是进程中的一个实体。
6). 线程的生命周期
      1. 新建:实例化线程对象。
      2. 就绪:向线程对象发送start消息,线程对象被加入可调度线程池等待CPU调度。
      3. 运行:CPU 负责调度可调度线程池中线程的执行。线程执行完成之前,状 态可能会在就绪和运行之间来回切换。就绪和运行之间的状态变化由CPU负责,程序员不能干预。
      4. 阻塞:当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)。
      5. 死亡:正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程内部中止执行/在主线程中止线程对象
      6. 还有线程的exit和cancel
      7. [NSThread exit]:一旦强行终止线程,后续的所有代码都不会被执行。
      8.[thread cancel]取消:并不会直接取消线程,只是给线程对象添加 isCancelled 标记。

7). GCD的组合方式
      1. 同步线程+并发队列:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
      2. 异步线程+并发队列:可以开启多个线程,任务交替(同时)执行。
      3. 同步执行 + 串行队列:不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。
      4. 异步执行 + 串行队列:会开启一条新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务。
      5. 同步执行 + 主队列:会在主线程调用。死锁卡住不执行。
      6. 异步执行 + 主队列:会在主线程调用。不会开启新线程,串行执行任务。
8). 同步执行(sync)和异步执行(async)的区别
      1. 两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
9). 串行队列和并发队列的区别
      1. 两者的主要区别是:执行顺序不同,以及开启线程数不同。
      2. 共同点:两者都符合 FIFO(先进先出)的原则。
  1. 静态库和动态库
1).  静态库和动态库的区别
      1. 系统创建的库为动态库,即在一次加载中,针对手机上的所有应用,该库都是通用的。
      2. 开发者创建的库为静态库。该库的作用域仅在该app范围内。多次使用则多次加载。
2). .framework  和 .a的区别
      1. 两者都是静态库。
      2. .a是一个纯二进制文件,而.framework里面除了二进制文件还包括资源文件。
      3. .a文件不能直接使用,需要.h文件配合使用,而.framework可独立使用。
      4. .a无需暴露内部文件,而.framework 需要至少暴露一个头文件。

  1. RunTime
1). runtime简介
      1. runtime简称运行时,OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制。
      2. 对于OC的函数,属于动态调用过程,在编译的时候不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
      3. 在编译阶段:OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。而C语言调用未实现的函数就会报错。
2). runtime的五个功能
      1. 发送消息
      2. 交换方法
      3. 动态添加方法
      4. 给分类添加属性
      5. 字典转模型
3). runtime的应用场景
      1. 关联对象(Objective-C Associated Objects)给分类增加属性
      2. 方法魔法(Method Swizzling)方法添加和替换和KVO实现
      3. 消息转发(热更新)解决Bug(JSPatch)
      4. 实现NSCoding的自动归档和自动解档
      5. 实现字典和模型的自动转换(MJExtension)
4). 运行时阶段的消息发送的详细步骤
      1. 检测selector 是不是需要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会retain,release 这些函数了。
      2. 检测target 是不是nil 对象。ObjC 的特性是允许对一个 nil对象执行任何一个方法不会 Crash,因为会被忽略掉。
      3. 如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,若可以找得到就跳到对应的函数去执行。
      4. 如果在cache里找不到就找一下方法列表methodLists。
      5. 如果methodLists找不到,就到超类的方法列表里寻找,一直找,直到找到NSObject类为止。
      6. 如果还找不到,Runtime就提供了如下三种方法来处理:动态方法解析、消息接受者重定向、消息重定向。三者关系见消息转发流程图
          

消息转发流程图.png
  1. GCD如何实现在多个请求都完成之后返回结果
1). 同步堵塞
2). 栅栏函数
3). 调度组
  1. iOS应用导航模式有哪些?
1). 平铺模式,一般由scrollView和pageControl组合而成的展示方式。手机自带的天气比较典型。
2). 标签模式,tabBar的展示方式,这个比较常见。
3). 树状模式,tableView的多态展示方式,常见的9宫格、系统自带的邮箱等展现方式。
  1. iOS单元测试框架有哪些?
1). OCUnit:  是 OC 官方测试框架, 现在被 XCTest 所取代。
2). XCTest:  是与 Foundation 框架平行的测试框架。
3). GHUnit:  是第三方的测试框架。[github地址](https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2Fgh-unit%2Fgh-unit)。
4). OCMock:  是第三方的测试框架。[github地址](https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2Ferikdoe%2Focmock)。
  1. MVC的理解
1). 数据管理者(M)、数据展示者(V)、数据加工者(C)
2). M 应该做的事:
      1. 给 ViewController 提供数据
      2. 给 ViewController 存储数据提供接口
      3. 提供经过抽象的业务基本组件,供 Controller 调度
3). C 应该做的事:
      1. 管理 View Container 的生命周期
      2. 负责生成所有的 View 实例,并放入 View Container
      3. 监听来自 View 与业务有关的事件,通过与 Model 的合作,来完成对应事件的业务。
2). V 应该做的事:
      1. 响应与业务无关的事件,并因此引发动画效果,点击反馈(如果合适的话,尽量还是放在 View 去做)等。
      2. 界面元素表达。
  1. TCP 和 UDP 有什么区别?
1). TCP 是面向连接的,建立连接需要经历三次握手,保证数据正确性和数据顺序
2). UDP 是非连接的协议,传送数据受生成速度,传输带宽等限制,可能造成丢包
3). UDP 一台服务端可以同时向多个客户端传输信息
4). TCP 报头体积更大,对系统资源要求更多
5). TCP注重数据安全,而UDP数据传输快点,但安全性一般。
  1. TCP 的三次握手
1). 第一次握手:客户端发送 syn 包到服务器,并进入 syn_send状态,等待服务器进行确认
2). 第二次握手:服务器收到客户端的 syn 包,必须确认客户的SYN,同时自己也发送一个 SYN 包,即 SYN + ACK 包,此时服务器进入 SYN_RECV 状态;
3). 第三次握手:客户收到服务器发送的 SYN+ACK 包之后,向服务器发送确认包, 此包发送完毕,客户端和服务器进入ESTABLISHED 状态,完成第三次握手。
  1. 常用的设计模式
1). 单例模式
2). 观察者模式
3). 代理模式
4). 享元模式
3). 工厂方法模式
3). 抽象工厂模式
  1. 沙盒目录结构是怎样的?各自用于那些场景?
1). Application:存放程序源文件,上架前经过数字签名,上架后不可修改
2). Documents:常用目录,iCloud 备份目录,存放数据
3). Library
      1. Caches:存放体积大又不需要备份的数据
      2. Preference:设置目录,iCloud 会备份设置信息
4). tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能
  1. pushViewController和presentViewController有什么区别
1). 两者都是在多个试图控制器间跳转的函数
2). presentViewController 提供的是一个模态视图控制器(modal)
3). pushViewController 提供一个栈控制器数组,push/pop
  1. SDWebImage里面给 UIImageView加载图片的逻辑
1). 入口setImageWithURL:placeholderImage:options: 会先把placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。
2). 进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo: 交给 SDImageCache 从缓存查找图片是否已经下载
3). 先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
4). SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到UIImageView+WebCache 等前端展示图片。
5). 如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。
6). 根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调notifyDelegate:
7). 如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调imageCache:didFindImage:forKey:userInfo: 进而回调展示图片。
8). 如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:
9). 共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。
10). 图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。
11). connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。
12). connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。
13). 图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。
14). 在主线程notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。
15). imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。
16). 通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。
17). 将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。
18). SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。
19). SDWebImage 也提供了UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。
20). SDWebImagePrefetcher 可以预先下载图片,方便后续使用。
  1. 类方法和实例方法的区别
1). 类方法:
     1. 类方法是属于类对象的
     2. 类方法只能通过类对象调用
     3. 类方法中的 self 是类对象
     4. 类方法可以调用其他的类方法
     5. 类方法中不能访问成员变量
     6. 类方法中不能直接调用对象方法
     7. 类方法是存储在元类对象的方法缓存中
2). 实例方法:
     1. 实例方法是属于实例对象的
     2. 实例方法只能通过实例对象调用
     3. 实例方法中的 self 是实例对象
     4. 实例方法中可以访问成员变量
     5. 实例方法中直接调用实例方法
     6. 实例方法中可以调用类方法(通过类名)
     7. 实例方法是存放在类对象的方法缓存中
  1. runloop的 mode是什么?作用是什么?
1). 用来控制一些特殊操作只能在指定模式下运行,一般可以通过指定操作的运行。
mode 来控制执行时机,以提高用户体验
2). 系统默认注册了 5 个 Mode,其作用
      1. kCFRunLoopDefaultMode:App 的默认 Mode,通常主线程是在这个 Mode下运行,对应 OC 中的:NSDefaultRunLoopMode
      2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
      3. kCFRunLoopCommonModes:这是一个标记 Mode,不是一种真正的 Mode,事件可 以 运 行 在 所 有 标 有 common modes 标 记 的 模 式 中 , 对 应 OC 中 的NSRunLoopCommonModes , 带 有    common  modes   标 记 的 模 式 有 :UITrackingRunLoopMode 和 kCFRunLoopDefaultMode
      4. UIInitializationRunLoopMode:在启动 App 时进入的第一个 Mode,启动完成后就不再使用
      5. GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到
  1. lldb(gdb)常用的调试命令?
1). po:打印对象,会调用对象 description 方法。是 print-object 的简写
2). expr:可以在调试时动态执行指定表达式,并将结果打印出来,很有用的命令
3). print:也是打印命令,需要指定类型
4). bt:打印调用堆栈,是 thread backtrace 的简写,加 all 可打印所有 thread 的堆栈
5). brl:是 breakpoint list 的简写
6). 更多详情可参见:(https://www.jianshu.com/p/50ce2153cb43)
  1. BAD_ACCESS在什么情况下出现?
1). 访问一个僵尸对象
2). 访问僵尸对象的成员变量
3). 向僵尸对象或其成员变量发送消息
  1. 分类
1). 分类的作用?
      声明私有方法,分解体积大的类文件,把framework的私有方法公开
2). 分类的特点
      运行时决议,可以为系统类添加分类
3). 分类可以添加哪些内容?
      实例方法,类方法,协议,属性(添加getter和setter方法,并没有实例变量,添加实例变量需要用关联对象)
4). 如果工程里有两个分类A和B,两个分类中有一个同名的方法,哪个方法最终生效?
      取决于分类的编译顺序,最后编译的那个分类的同名方法最终生效,而之前的都
      会被覆盖掉(这里并不是真正的覆盖,因为其余方法仍然存在,只是访问不到,因     
      为在动态添加类的方法的时候是倒序遍历方法列表的,而最后编译的分类的方法 
      会放在方法列表前面,访问的时候就会先被访问到,同理如果声明了一个和原类 
      方法同名的方法,也会覆盖掉原类的方法)。
5). 如果声明了两个同名的分类会怎样?
      会报错,所以第三方的分类,一般都带有命名前缀
6). 分类能添加成员变量吗?
      不能。只能通过关联对象(objc_setAssociatedObject)来模拟实现成员变量,但其实质是关联内容,所有对象的关联内容都放在同一个全局容器哈希表中:AssociationsHashMap,由AssociationsManager统一管理。
  1. 区分nil 、Nil、NULL、NSNUll
1). nil: 一般是指把一个对象置空,既完全是一个空对象,完全从内存中释放。
2). Nil: 和nil基本没有任何区别,也可以说只要是可以使用nil的地方都可以使用Nil,
         反之亦然。但是作为程序猿,我们应该更加严谨一些。nil和Nil的区别在于,
         nil表示置空一个对象,而Nil表示置空一个类。
3). NULL: 大家都知道oc 是基于c的,并且oc是完全兼容c的,NULL源于c,表示一个空指针. 即:int *p = NULL
4). NSNull: 很有意思,大家一般都会觉得,NSNull也是空,但是看着这货又是“NS”开
            头的很像一个对象,实质上NSNull的确是一个对象,它继承于NSObject。那它 
            和nil的区别在哪里呢?nil是把一对象完全释放,就是完全从内存中释放。但是
            当我想把一个对象置空但是又想要一个容器的时候,我们就可以使用NSNull。
            比如一瓶矿泉水,我们不想要里面的水,但是我们想保留瓶子一样。
  1. 区分深拷贝与浅拷贝
1). 浅拷贝:指针拷贝,不增加新的内存。只是新增加一个指针指向原来的内存区域。
2). 深拷贝:内容拷贝,同时拷贝指针和指针指向的内存。新增指针指向新增的内存。
3). 拷贝条件:
      iOS中并非所有的对象都支持copy和mutableCopy,只有遵循了NSCopy协议或者
      NSMutableCoy协议的类才行。如果遵循着两个协议就必须分别实现
      copyWithZone和mutableCopyZone方法
4). 拷贝原则:
      1. 非容器类:像NSString、NSNumber这样的不能包含其他对象的系统类
         不可变对象调用copy是浅拷贝;而调用muablecopy是深拷贝并得到可变对象
         可变对象调用copy和mutablecopy都是深拷贝, 区别在于copy返回不可变对    
         象,mutablecopy返回可变对象
      2. 容器类:像NSArray、NSMutableArray等系统类
         不可变对象调用copy是浅拷贝,而调用muablecopy是深拷贝并得到可变对象。
         可变对象调用copy和mutablecopy都是深拷贝,区别在于copy返回不可变对象, 
         mutablecopy返回可变对象
      3. 容器类与非容器类的拷贝原则相似,但需要注意的是:所有的容器类的拷贝,
         拷贝后新容器里的元素始终是浅拷贝,其指针都指向原来对象。

  1. imageName和mageWithContextOfFile的区别?哪个性能高
1). 用imageNamed的方式加载时,图片使用完毕后缓存到内存中,内存消耗多,
加载速度快。即使生成的对象被autoReleasePool释放了,这份缓存也不释放,
如果图像比较大,或者图像比较多,用这种方式会消耗很大的内存。imageNamed
采用了缓存机制,如果缓存中已加载了图片,直接从缓存读就行了,每次就不
用再去读文件了,效率会更高。

2). ImageWithContextOfile加载, 图片是不会缓存的,加载速度慢。
3). 大量使用imageNamed方式会在不需要缓存的地方额外增加开销CPU的时间
当应用程字需要加载- -张比较大的图片并且使用一次性,那么其实是没有必要
去缓存这个图片的,用imageWithContentsOfile是 最为经济的方式,这样不会
因为Ullmage元素较多情况下,CPU会被逐个分散在不必要缓存上浪费过多时间
  1. 我们说的OC是动态运行时语言是什么意思?
1). 主要是将数据类型的确定由编译时,推迟到了运行时。简单来说, 运行时机制使我们直到运行时才去决定一个对象的类别,以及调用该类别对象指定方法。
  1. Category(类别)、 Extension(扩展)和继承的区别
1). 分类有名字,类扩展没有分类名字,是一种特殊的分类。
2). 分类只能扩展方法(属性仅仅是声明,并没真正实现),类扩展可以扩展属性、成员变量和方法。
3). 继承可以增加,修改或者删除方法,并且可以增加属性。
  1. KVC的底层实现?
1). 检查是否存在相应的key的set方法,如果存在,就调用set方法。
2). 如果set方法不存在,就会查找与key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值。
3). 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值。
4). 如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方
法。这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。
  1. ViewController生命周期
1). initWithCoder:通过nib文件初始化时触发。
2). awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每个对象。
3). loadView:开始加载视图控制器自带的view。
4). viewDidLoad:视图控制器的view被加载完成。  
5). viewWillAppear:视图控制器的view将要显示在window上。
6). updateViewConstraints:视图控制器的view开始更新AutoLayout约束。
7). viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。
8). viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。
9). viewDidAppear:视图控制器的view已经展示到window上。 
10). viewWillDisappear:视图控制器的view将要从window上消失。
11). viewDidDisappear:视图控制器的view已经从window上消失。
  1. delegate 和 notification 的区别
1). 二者都用于传递消息,不同之处主要在于一个是一对一的,另一个是一对多的。
2). notification通过维护一个array,实现一对多消息的转发。
3). delegate需要两者之间必须建立联系,不然没法调用代理的方法;notification不需要两者之间有联系。
  1. 什么是进程和线程?有什么区别?
1). 进程: 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配
和调度的基本单位。
2). 线程: 操作系统能够进行运算调度的最小单位
3). 区别: 程被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一
个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任
务。
  1. UIView和CALayer的区别?
1). UIView 和 CALayer 都是 UI 操作的对象。两者都是 NSObject 的子类,发生在 
UIView 上的操作本质上也发生在对应的 CALayer 上。
2). UIView 是 CALayer 用于交互的抽象。UIView 是 UIResponder 的子类( 
UIResponder 是 NSObject 的子类),提供了很多 CALayer 所没有的交互上的接口,
主要负责处理用户触发的种种操作。
3). CALayer 在图像和动画渲染上性能更好。这是因为 UIView 有冗余的交互接口,而
且相比 CALayer 还有层级之分。CALayer 在无需处理交互时进行渲染可以节省大量时
间。
4). CALayer的动画要通过逻辑树、动画树和显示树来实现。
  1. isEqual和“==”的区别?
1). ==:比较两个指针本身,而不是其所指向的对象。
2). isEqual:当且仅当指针值也就是内存地址相等;若重写该方法则要保证isEqual相
等则hash相等,hash相等isEqual不一定相等;若指针值相等则相等,值不相等就判断
对象所属的类,不属于同一个类则不相等,同属一个类时再判断每个属性是否相等。
  1. static和const的区别?
1). const:声明全局只读变量,(若前面没有static修饰,在另外一个文件中声明同名的常量会报错)。
2). static:修饰变量的作用域(本文件内),被修饰的变量只会分配一份内存,在上一次修改的基础上进行修改。
3). 一般两者配合使用,如:static const NSTimeInterval kAnimationDuration = 1.0;不会创建外部符合,编译时预处理指令会把变量替换成常值。
  1. 如何保证线程安全?
1). 原子操作(Atomic Operation):是指不会被线程调度机制打断的操作;这种操作
一旦开始,就一直运行到结束,中间不会有任何的上下文切换(context switch)。
2). 内存屏障(Memory Barrier):确保内存操作以正确的顺序发生,不阻塞线程(锁
的底层都会使用内存屏障,会减少编译器执行优化的次数,谨慎使用)。
3). 锁。
      1. 互斥锁(pthread_mutex):原理与信号量类似,但其并非使用忙等,而是阻塞
线程和休眠,需切换上下文。
      2. 递归锁(NSRecursiveLock):本质是封装了互斥锁的
PTHREAD_MUTEX_RECURSIVE类型的锁,允许同一个线程在未释放其拥有的锁时反
复对该锁进行加锁操作。
      3. 自旋锁(OSSpinLock):通过全局变量,来判断当前锁是否可用,不可用就忙
等。
      4. @synchronized(self):系统提供的面向OC的API,通过把对象hash当做锁来
用。
      5. NSLock:本质是封装了互斥锁的PTHREAD_MUTEX_ERRORCHECK类型的锁,
它会损失一定性能换来错误提示,因为面向对象的设计,其性能稍慢。
      6. 条件变量(NSConditionLock):底层通过(condition variable)pthread_cond_t
来实现的,类似信号量具有阻塞线程与信号机制,当某个等待的数据就绪后唤醒线
程,比如常见的生产者-消费者模式。
     7. 信号量(dispatch_semaphore)
  1. 什么是死锁?如何避免死锁?
1). 死锁:两个或两个以上的线程,互相等待彼此停止以获得某种资源,但是没有一方会提前退出的情况。
2). 避免在串行队列中执行同步任务。
3). 避免Operation相互依赖。
  1. layoutIfNeeded、layoutSubviews和setNeedsLayout的区别?
1). layoutIfNeeded:方法调用后,在主线程对当前视图及其所有子视图立即强制更新
布局。
2). layoutSubviews:方法只能重写,我们不能主动调用,在屏幕旋转、滑动或触摸界
面、子视图修改时被系统自动调用,用来调整自定义视图的布局。
3). setNeedsLayout:方法与layoutIfNeeded相似,不同的是方法被调用后不会立即强
制更新布局,而是在下一个布局周期进行更新。
  1. dynamic和synthesis的区别?
1). dynamic:告诉编译器不要帮我自动合成setter和getter方法,自己来实现,若没有
实现,当方法被调用时会导致消息转发。
2). synthesis:指定实例变量的名字,子类重载父类属性也需要synthesis(重新set和
get、使用dynamic,在Protocol定义属性、在category定义属性,默认不会自动合
成)。
  1. array为何用copy修饰?mutableArray为何用strong修饰?
1). array:若用strong修饰,在其被赋值可变的子类后,内容可能会在不知不觉中修
改,用copy防止被修改。
2). mutableArray若用copy修饰会返回一个NSArray类型,若调用可变类型的添加、删
除、修改方法时会因为找不到对应的方法而crash。
  1. iOS中有哪些设计模式?
1). 【单例】保证应用程序的生命周期内仅有一个该类的实力对象,易于外界访问。如:UIApplication、NSBundle、NSNotificationCenter、NSFileManager、NSUserDefault、NSURLCache等;
2). 【观察者】定义了一种一对多的依赖关系,可以让多个观察者同时监听一个主题对象,当主题对象状态或值发生改变,会通知所有的观察者;KVO当对象属性变化时,通知观察此属性的对象。案例代表:通知和KVO;
3). 【类簇】(隐藏抽象基类背后的实现细节)如:UIButton、NSNumber、NSData、NSArray、NSDictionary、NSSting。用isMemberOfClass和isKindOfClass来判断。
4). 【命令模式】(运行时可以调用任意类的方法),代表:NSInvocation,封装一个请求或行为作为对象,包含选择器、方法名等。
5). 【委托模式】“我想知道列表中被选中的内容在第几行”?可以,接受我的委托就可以知道;只是接受我的委托就要帮我完成这几件事情,有必须要完成的,有不必要完成的,至于你怎么完成我就不关心了。
6). 【装饰器模式】:装饰器模式在不修改原来代码的情况下动态的给对象增加新的行为和职责,它通过一个对象包装被装饰对象的方法来修改类的行为,这种方法可以做为子类化的一种替代方法。 案例代表:Category和Delegation
  1. 简述iOS中的内存管理方式?
1). iOS的内存管理用的是引用计数的方法,分为MRC(手动引用计数)和ARC(自动引用
计数)。
2). MRC:开发者手动地进行retain和release操作,对每个对象的retainCount进行+1,-1
操作,当retainCount为0时,系统会自动释放对象内存。
3). ARC:开发者通过声明对象的属性为strong,weak,retain,assign来管理对象的引用计
数,被strong和retain修饰的属性变量系统会自动对所修饰变量的引用计数进行自增自
减操作,同样地,retainCount为0时,系统会释放对象内存。
  1. 通知,代理,block,KVO的使用场景分别是什么,有什么区别?
1). 通知: 适用于毫无关联的页面之间或者系统消息的传递,属于一对多的信息传递关
系。例如系统音量的改变,系统状态的改变,应用模式的设置和改变,都比较适合用
通知去传递信息。
2). 代理: 一对一的信息传递方式,适用于相互关联的页面之间的信息传递,例如push
和present出来的页面和原页面之间的信息传递。
3). block: 一对一的信息传递方式,效率会比代理要高(毕竟是直接取IMP指针的操作方
式)。适用的场景和代理差不多,都是相互关联页面之间的页面传值。
4). KVO: 属性监听,监听对象的某一属性值的变化状况,当需要监听对象属性改变的
时候使用。例如在UIScrollView中,监听contentOffset,既可以用KVO,也可以用代
理。但是其他一些情况,比如说UIWebView的加载进度,AVPlayer的播放进度,就只能
用KVO来监听了,否则获取不到对应的属性值。
  1. 七层网络协议?
1). 由低到高:物理层、数据链路层、网络层、传输层、表示层、会话层、应用层。
  1. 三次握手协议?
1). 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入
SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence 
Numbers)。
2). 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发
送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
3). 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包
ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成
功)状态,完成三次握手。
  1. 四次挥手协议?
1). 第一次挥手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number
和Acknowledgment Number,向主机2发送一个FIN报文段;此时,主机1进入
FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
2). 第二次挥手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,
Acknowledgment Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2
告诉主机1,我也没有数据要发送了,可以进行关闭连接了;
3). 第三次挥手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入
CLOSE_WAIT状态;
4). 第四次挥手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后
主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主
机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以
关闭连接了。
  1. get请求与post请求的区别?
1). get是向服务器发索取数据的一种请求,而post是向服务器提交数据的一种请求
2). get没有请求体,post有请求体;
3). get请求的数据会暴露在地址栏中,而post请求不会,所以post请求的安全性比get请求号;
4). get请求对url长度有限制,而post请求对url长度理论上是不会收限制的,但是实际上各个服务器会
规定对post提交数据大小进行限制。
  1. http 和 https 的区别
1). https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
2). http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
3). http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4). http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
  1. 什么是指针常量和常量指针
1). 指针常量的本质是一个常量,而用指针修饰它,那么说明这个常量的值应该是一个指针。
指针常量的值是指针,这个值因为是常量,所以不能被赋值
 int a; int *const b = &a;
2). 常量指针本质是指针,常量修饰它,表示这个指针乃是一个指向常量的指针(变量)。
指针指向的对象是常量,那么这个对象不能被更改。
 const int *p;。
  1. @synthesize、@dynamic的理解
1). synthesize是指系统自动生成setter和getter方法;
2). dynamic是告诉编译器,属性的获取(getter)和赋值(setter)方法需要用户自己实现,系统不会自动生成。
  1. 单例模式详解
  1. 什么是单例模式

在整个程序中只有一个实例,并且提供一个类方法供全局调用,在编译时初始化这个类,然后一直保存在内存中,到程序(APP)退出时由系统自动释放这部分内存。

  1. 在哪些地方会用到单例模式(个人理解)

一般在我的程序中,经常调用的类,如工具类、公共跳转类,用户信息等,我都会采用单例模式;

  1. 单例类的生命周期

一个单例类在程序中只能初始化一次,为了保证在使用中始终都是存在的,所以单例是在存储器的全局区域,在编译时分配内存,只要程序还在运行就会一直占用内存,在APP结束后由系统释放这部分内存内存。

  1. 单例模式的优缺点

优点:

  1. 在整个程序中只会实例化一次,所以在程序如果出了问题,可以快速的定位问题所在
  2. 由于在整个程序中只存在一个对象,节省了系统内存资源,提高了程序的运行效率;
    缺点:
  3. 不能被继承,不能有子类;
  4. 不易被重写或扩展(可以使用分类)
  5. 由于单例对象只要程序在运行中就会一直占用系统内存,该对象在闲置时并不能销毁,在闲置时也消耗了系统内存资源;
  1. 注意点
  • 单例类只能被初始化一次。如果多次初始化会导致程序闪退
  1. 内容管理的基本原则
  • iOS 5.0之前:MRC(Manual Reference Counting)
    1. 遵循OC内存管理“谁创建,谁释放,谁引用,谁管理”的机制
    1. 当创建或引用一个对象的时候,需要向她发送alloc、copy、retain消息
    1. 当释放该对象时需要发送release消息
    1. 当对象引用计数为0时,系统将释放该对象
  • iOS 5.0之后:ARC(Automatic Reference Counting)
    1. 管理机制与手动机制一样,只是不再需要调用retain、release、autorelease
    1. 它编译时的特性,当你使用ARC时,在适当位置插入release和autorelease
    1. 它引用strong和weak关键字,当使用strong修饰的指针变量指向对象时,当指针指向新值或者指针不复存在,相关联的对象就会自动释放。当weak修饰的指针变量指向对象,当对象的拥有者指向新值或者不存在时,weak修饰的指针会自动置为nil
    1. 如果使用alloc、copy(mutableCopy)或者retian一个对象时,你就有义务,向它发送一条release或者autorelease消息。其他方法创建的对象,不需要由你来管理内存。
    1. 向一个对象发送一条autorelease消息,这个对象并不会立即销毁, 而是将这个对象放入了自动释放池,待池子释放时,它会向池中每一个对象发送 一条release消息,以此来释放对象
    1. 向一个对象发送release消息,并不意味着这个对象被销毁了,而是当这个对象的引用计数为0时,系统才会调用dealloc方法,释放该对象和对象本身它所拥有的实例
  • 注意事项
  1. 如果一个对象有一个_strong类型的指针指向着,那这个对象就不会被释放。
  2. 如果一个指针指向超出了它的作用域,就会被指向nil。
  3. 如果一个指针被指向nil,那么它原来指向的对象就被释放了。
  4. 当一个视图控制器被释放时,它内部的全局指针会被指向nil。
  5. block中为了避免循环引用问题,使用_weak描述
  1. Category(类别)剖析
  • 简介
  • category是Objective-C 2.0之后添加的语言特性,category的主要作用是为已经存在的类添加方法和属性 。
  • 实现原理
    1. 在编译时期,会将分类中实现的方法生成一个结构体 method_list_t 、将声明的属性生成一个结构体 property_list_t ,然后通过这些结构体生成一个结构体 category_t 。
    1. 然后将结构体 category_t 保存下来
    1. 在运行时期,Runtime 会拿到编译时期我们保存下来的结构体 category_t
    1. 然后将结构体 category_t 中的实例方法列表、协议列表、属性列表添加到主类中
    1. 将结构体 category_t 中的类方法列表、协议列表添加到主类的 metaClass 中
  • 注意点:category_t 中的方法列表是插入到主类的方法列表前面(类似利用链表中的 next 指针来进行插入),所以这里 Category 中实现的方法并不会真正的覆盖掉主类中的方法,只是将 Category 的方法插到方法列表的前面去了。运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会停止查找,这里就会出现覆盖方法的这种假象了。
  • Category 为什么不能添加实例变量
    1. 通过结构体 category_t ,我们就可以知道,在 Category 中我们可以增加实例方法、类方法、协议、属性。这里没有 objc_ivar_list 结构体,代表我们不可以在分类中添加实例变量。
    1. 在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这个就是 Category 中不能添加实例变量的根本原因。
  • 怎么实现属性添加
    1. 使用runtime的关联对象,并重写setter和getter方法。
  • 使用场景
    1. 通过分类来为已知的类扩展方法和属性,Category 不会为我们的属性添加实例变量和存取方法,我们可以通过关联对象这个技术来实现对象绑定
    1. 通过实现分类的 load 方法来实现 Method Swizzling
    1. 将一个类拆分成多个实现文件,典型的就是将项目中 AppDelegate 拆分。 AppDelegate 作为程序的入口,一般都会实现各种第三方 SDK 的初始化、写各种版本的容错代码、实现通知、支付逻辑等等功能,所以 AppDelegate 这个类很容易臃肿,这个时候可以通过实现 AppDelegate 分类来将不同的业务代码分离。
  1. NSArray与NSSet的区别?
  • NSArray内存中存储地址连续,而NSSet不连续
  • NSSet效率高,内部使用hash查找;NSArray查找需要遍历
  • NSSet通过anyObject访问元素,NSArray通过下标访问
  1. 属性关键字assign、retain、weak、copy
  • assign:用于基本数据类型和结构体。如果修饰对象的话,当销毁时,属性值不会自动置nil,可能造成野指针。
  • weak:对象引用计数为0时,属性值也会自动置nil
  • retain:强引用类型,ARC下相当于strong,但block不能用retain修饰,因为等同于assign不安全。
  • strong:强引用类型,修饰block时相当于copy。
  1. weak属性如何自动置nil的?
  • Runtime会对weak属性进行内存布局,构建hash表:以weak属性对象内存地址为key,weak属性值(weak自身地址)为value。当对象引用计数为0 dealloc时,会将weak属性值自动置nil。
  1. runtime 中,SEL和IMP的区别?
  • 每个类对象都有一个方法列表,方法列表存储方法名、方法实现、参数类型,SEL是方法名(编号),IMP指向方法实现的首地址
  1. iOS 消息传递有几种方式
    1. 通知(notification):在iOS中由通知中心进行消息接收和消息广播,是一种一对多的消息传递方式。
    1. 代理(delegate):是一种通用的设计模式,iOS中对代理支持的很好,由代理对象、委托者、协议三部分组成。
    1. block:在iOS 4.0中开始引入的一种回调方法,可以将回调处理代码直接写在block代码块中,看起来逻辑清晰代码整齐。
    1. target action:通过将对象传递到另一个类中,在另一个类中将该对象当做target的方式,来调用该对象方法,从内存角度来说和代理类似。
  • 5.KVO:NSObject的Category(分类)-NSKeyValueObserving,通过对属性进行监听的方式来监测某个值的变化,当值发生变化时调用KVO的回调方法。
  1. NSArray 和 NSSet 的区别
    1. NSArray内存中存储地址连续,而NSSet不连续
    1. NSSet效率高,内部使用hash查找;NSArray查找需要遍历
    1. NSSet通过anyObject访问元素,NSArray通过下标访问
  1. frame 和 bounds 有什么不同?
    1. frame指的是:该view在父view坐标系统中的位置和大小。(参照点是父view的坐标系统)
    1. bounds指的是:该view在本身坐标系统中的位置和大小。(参照点是本身坐标系统)
  1. 编程中的六大设计原则?
    1. 单一职责原则
    1. 开闭原则
    1. 接口隔离原则
    1. 依赖倒置原则
    1. 里氏替换原则
    1. 迪米特法则
  • 具体讲解可见
  1. New作用是什么?
    1. 向设备(堆区)申请内存空间;
    1. 给实例变量初始化;
    1. 返回所申请空间的首地址;
  1. @proprety的作用
    1. 在.h文件中帮我们自动生成get和set方法声明
    1. 在.m文件中帮我们生成私有的实例变量(前提是没有在.h文件中没有手动生成)
    1. 在.m文件中帮我们是实现get和set方法的实现
    1. @property = ivar + getter + setter;
  1. NSObject和id的区别?
    1. NSObject和id都可以指向任何对象
    1. NSObject对象会在编译时进行检查,需要强制类型转换
    1. id类型不需要编译时检查,不需要强制类型转换
  1. 结构体与数组有什么区别?
    1. 结构体可以存不同类型的元素,而数组只能存同一类型
    1. 结构体类型需要我们自已定义.数组是用别的类型加[元素个数]
    1. 结构体内存分配方式很特别,使用对齐原则,不一定是所有元素的字节数和,而数组一定是所有元素的字节数和.
    1. 结构体指针可以指针名->结构体元素名(取元素);数组不行.
  • 继续更新中,以上答案部分为个人理解,若有错误欢迎指出。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 1. iOS开发中的加密方式 iOS加密相关算法框架:CommonCrypto。 对称加密: DES、3DES、A...
    Dezi阅读 1,219评论 0 7
  • 最近由于换工作的原因,需要参加面试,然后就到处收集了一些当下的面试题供自己复习使用。下面就分享出来,希望能够帮到你...
    wuyukobe阅读 2,115评论 3 26
  • 设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的...
    小歪子go阅读 554评论 0 0
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,474评论 16 22
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,548评论 0 11