问: 关键字static的作用是什么?
- 函数体内
static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
- 函数体内
- 在模块内的
static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
- 在模块内的
- 在模块内的
static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
- 在模块内的
问: 关键字const是什么含义? 分别解释下列语句中const的作用?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
含义
- 欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
- 对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
- 在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
- 对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;
作用
const int a; //a是一个常整型数
int const a; //a是一个常整型数
const int *a; //a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)
int * const a; //a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)
int const * a const; //a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)
问: 使用nonatomic一定是线程安全的吗?
-
nonatomic: 非原子性,set方法的实现不加锁,不安全,性能高
-
-
atomic:atomic性能低,atomic通过锁定机制来确保其原子性,但只是读/写安全,不能绝对保证线程的安全,当多线程同时访问的时候,会造成线程不安全。可以使用线程锁来保证线程的安全。
-
问: 对于语句NSString *obj = [[NSData alloc] init]; ,编译时和运行时obj分别是什么类型?
- 编译时是
NSString类型
- 编译时是
- 运行时是
NSData类型
- 运行时是
问: Objective-C如何对内存管理的,说说你的看法和解决方法?
- 每个对象都有一个引用计数器,每个新对象的计数器是1,当对象的计数器减为0时,就会被销毁
- 通过retain可以让对象的计数器+1、release可以让对象的计数器-1
- 还可以通过autorelease pool管理内存
- 如果用ARC,编译器会自动生成管理内存的代码
注意:不管是MRC还是ARC都是在编译时完成的
问: iOS数据持久化有哪些?
为何要持久化:iOS 开发可以没有持久化,持久化更多的是业务需求;比如记录用户是否登陆,下次进应用不需要再登陆。
因为 iOS 的 沙盒机制,所以持久化分为两类:沙盒内 和 沙盒外。
- 沙盒内
(1)NSKeyedArchiver: 只要遵循了NSCoding协议并正确实现了initWithCoder和encodeWithCoder方法的类都可以通过NSKeyedArchiver来序列化。
归档使用archiveRootObject,解归档使用unarchiveObjectWithFile;需要指定文件路径。
(2)NSUserDefaults:[NSUserDefaults standardUserDefaults]获取NSUserDefaults对象,以key-value方式进行持久化操作。
(3)plist: 写入使用writeToFile,读取使用xxxWithContentsOfFile;需要指定文件路径。
(4) 数据库:sqlite、CoreData和Realm等
(5) 文件: 这里要和plist区分一下,plist方式是字典/数组数据格式写入文件;而这里的文件方式不限数据格式。
- 沙盒内
- 沙盒外
沙盒内的方式在应用被删除后数据都会丢失,如果想要不丢失则需要使用KeyChain。
KeyChain本质是一个sqlite数据库,其保存的所有数据都是加密过的。
KeyChain分为私有和公有,公有则需要指定group,一个group中的应用可以共享此KeyChain。
使用KeyChain过程中要理解下面几个问题:
①:自己使用的KeyChain和系统自带的KeyChain数据是隔离的,内部应该是不同数据库文件;
②:KeyChain数据可备份到iCloud中;
③:不需要联网,也不用登陆iCloud账号;一个设备一个sqlite数据库,但是不同应用组不共享数据;
④:要在另一台设备上使用当前设备存储的KeyChain信息,需要当前设备进行数据备份,再在另一设备上复原数据;比较常用的是iCloud备份方式;
⑤:系统自带的KeyChain中账号密码分类数据可在系统设置->账号与密码里面看到,你退出iCloud账号还是存在,只是iCloud会帮你备份如果你设置了的话;这个和照片是一样的道理。
- 沙盒外
问: id和NSObject*的区别?
-
id可以指向OC中的任何对象,而NSObject*只能指向NSObject及子类对象
问: strong 和 weak 的区别?
问: (堆和栈) 哪些数据是放在堆上的,哪些是放在栈上的?
- 栈:由系统自动分配,速度较快,不会产生内存碎片,
- 堆:是由alloc分配的内存,速度比较慢,而且容易产生内存碎片,不过用起来最方便。
问: UITableView 优化?
- cell复用
我们经常在注意cellForRowAtIndexPath:中为每一个cell绑定数据,实际上在调用cellForRowAtIndexPath:的时候cell还没有被显示出来,为了提高效率我们应该把数据绑定的操作放在cell显示出来后再执行,可以在tableView:willDisplayCell:forRowAtIndexPath:(以后简称willDisplayCell)方法中绑定数据。
注意willDisplayCell在cell在tableview展示之前就会调用,此时cell实例已经生成,所以不能更改cell的结构,只能是改动cell上的UI的一些属性(例如label的内容等)。
- cell复用
- cell高度的计算
(1)定高的cell,应该采用如下方式:self.tableView.rowHeight = 88;
(2)动态高度的cell:tableView: tableViewheightForRowAtIndexPath:,该方法实现后,上面的rowHeight的设置将会变成无效。在这个方法中,我们需要提高cell高度的计算效率,来节省时间。
自从iOS8之后有了self-sizing cell的概念,cell可以自己算出高度,使用self-sizing cell需要满足以下三个条件:
① 使用Autolayout进行UI布局约束(要求cell.contentView的四条边都与内部元素有约束关系)。
② 指定TableView的estimatedRowHeight属性的默认值。
③ 指定TableView的rowHeight属性为UITableViewAutomaticDimension。
- cell高度的计算
- 渲染
(1)当有图像时,预渲染图像,在bitmap context先将其画一遍,导出成UIImage对象,然后再绘制到屏幕,这会大大提高渲染速度。具体内容可以自行查找“利用预渲染加速显示iOS图像”相关资料。
(2)渲染最好时的操作之一就是混合(blending)了,所以我们不要使用透明背景,将cell的opaque值设为Yes,背景色不要使用clearColor,尽量不要使用阴影渐变等。
(3)由于混合操作是使用GPU来执行,我们可以用CPU来渲染,这样混合操作就不再执行。可以在UIView的drawRect方法中自定义绘制。
- 渲染
- 减少视图的数目
我们在cell上添加系统控件的时候,实际上系统都会调用底层的接口进行绘制,大量添加控件时,会消耗很大的资源并且也会影响渲染的性能。当使用默认的UITableViewCell并且在它的ContentView上面添加控件时会相当消耗性能。所以目前最佳的方法还是继承UITableViewCell,并重写drawRect方法。
- 减少视图的数目
- 减少多余的绘制操作
在实现drawRect方法的时候,它的参数rect就是我们需要绘制的区域,在rect范围之外的区域我们不需要进行绘制,否则会消耗相当大的资源。
- 减少多余的绘制操作
- 不要给cell动态添加subView
在初始化cell的时候就将所有需要展示的添加完毕,然后根据需要来设置hide属性显示和隐藏。
- 不要给cell动态添加subView
- 异步化UI,不要阻塞主线程
我们时常会看到这样一个现象,就是加载时整个页面卡住不动,怎么点都没用,仿佛死机了一般。原因是主线程被阻塞了。所以对于网路数据的请求或者图片的加载,我们可以开启多线程,将耗时操作放到子线程中进行,异步化操作。这个或许每个iOS开发者都知道的知识,不必多讲。
- 异步化UI,不要阻塞主线程
- 滑动时按需加载对应的内容
如果目标行与当前行相差超过指定行数,只在目标滚动范围的前后指定3行加载。
滑动很快时,只加载目标范围内的cell,这样按需加载(配合SDWebImage),极大提高流畅度。
- 滑动时按需加载对应的内容
问: 消息列表页面如何优化?
首先我们发消息时候观察一下消息列表的特性,当发送一条消息时候,消息的数量会变化,列表会出现在最上边的位置,列表内的内容会发生变化。从消息列表的特性,我们就可以分析出要优化的点了。通过这些点,我们做了一些优化:
- 如果列表消息从没显示过需要刷新列表,创建好一个
cell后,将cell插入到第一位上,cell插入的性能要高于刷新tableview的性能。
- 如果列表消息从没显示过需要刷新列表,创建好一个
- 如果消息已经显示过了,但是并不是第一位,则需要刷新列表。
- 如果消息已经显示,并且是第一位,则只需要
cell的内容变化。
- 如果消息已经显示,并且是第一位,则只需要
- 只修改
cell里的内容,不进行刷新cell整体,这里要注意的是,一定要最小化刷新。刷新点越小,性能损耗越小。我们项目架构是MVVM,采用了ReactiveCocoa框架,针对每个cell上的可变化的控件数据进行了监听,每一个cell上对应一个vm,这样当vm上的数据变化时候,cell上的数据也就跟着变了。做到了最小化刷新。
- 只修改
- 避免使用
autolayout计算位置,这个很重要,在性能要求高的情况下,autolayout计算会很耗时间,尤其在算tableview高度的时候可见一斑。可喜的是消息列表的高度是固定的,所以在计算高度时候我们并未花费时间。
- 避免使用
- 使用
(__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath而不使用-(nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier来查找cell,因为下边的方法会多查一次,耗时更长一点。
- 使用
-
cellForRowAtIndexPath方法只负责创建cell,willDisplayCell方法才给cell进行赋值操作。从方法名字就可以看出来原因
-
- 当来消息轰炸时候,必然会是不同的人发来的消息,会导致
tableview不可避免的刷新,如果不加处理必然会卡顿,要知道,机器也是有瓶颈的。这里我们做的优化是根据cpu的使用率选择性的刷新tableview。后来我们发现微信也是有这个现象,并不是实时的刷新,我们猜测也是类似处理。
- 当来消息轰炸时候,必然会是不同的人发来的消息,会导致
- 重绘制系统控件,相信你也发现了消息列表里,主要有两个控件,一个是头像,一个是
label。而系统的UIImageView用来显示头像,未免有点重。我们的处理是使用UIView,设置View得layer.content来处理。针对layer层做的setImageWithUrl的第三方库也不少,大家可以自行查询。另一个就是label,如果你能集成UIView自己绘制一个label,我想也许会有一点效果。
- 重绘制系统控件,相信你也发现了消息列表里,主要有两个控件,一个是头像,一个是
问: 聊天界面如何优化?
聊天界面的优化算是比较繁琐的了,但是优化点跟回话列表的优化差不多。上边提到回话列表里最耗时的tableview的高度是固定的,而聊天界面的几乎每条消息的高度都可能不一样,所以我们在优化聊天界面时候最重要的一点就是计算tableviewcell的高度。而我们在计算tableview的高度是怎么做的呢?
主要有两个准则:
(1) 第一个是能在后台线程执行的都放在后台线程里。
(2) 第二个计算高度要放在显示之前。
- 巧妙的选择控件。(比如上个问题提到的用
UIView的layer.content来代替UIImageView, 图片加点击也可以用view,然后监听view的touch事件,像button这种重量级的控件在性能为主的app面前,我对他们都是弃之如敝履。)
- 巧妙的选择控件。(比如上个问题提到的用
- 减少使用
layer层的cornerRadius,mask等圆角的绘制,这会引发离屏渲染,增加cpu的占用率。如果业务需要的话,我们可以通过UIBezierPath来drawInRect它。
- 减少使用
- 避免设置透明
- 避免
autolayout设定控件位置
- 避免
- 尽可能的减少视图的层级,如果你能把所有的控件都绘制到一个
View上,可想而知性能会爆棚。
- 尽可能的减少视图的层级,如果你能把所有的控件都绘制到一个
问: Swift的可选类型?
问: Swift 中 Struct 和 Class的区别 ?
-
property初始化的不同:
主要的差別就是class在初始化时不能直接把property放在 默认的constructor的参数里,而是需要自己创建一个带参数的constructor
-
- 变量赋值方式不同(深浅copy)
struct赋值“=”的时候,会copy一份完整相同的內容给另一個变量 -> 【开辟了新的内存地址】(深拷贝)
class赋值“=”的时候,不会copy一份完整的内容给另一個变量,只是增加了原变量内存地址的引用而已 -> 【没有开辟了新的内存地址】(浅拷贝)
- 变量赋值方式不同(深浅copy)
-
immutable变量
Swift语言的特色之一就是可变动内容和不可变内容用var和let來甄别,如果初始为let的变量再去修改会发生编译错误。
struct也遵循这一特性
class不存在这样的问题
-
-
struct和class的差別是struct的function要去改变property的值的时候要加上mutating,而class不用。
-
-
struct不能继承,class可以继承。
-
-
struct分配在栈中,class分配在堆中。
-
Swift 用 Struct 作为数据模型时需要注意什么问题?
优点:
- 安全性:因为
Struct是用值类型传递的,它们没有引用计数。
- 安全性:因为
- 内存:由于他们没有引用数,他们不会因为循环引用导致内存泄漏。
- 速度:值类型通常来说是以栈的形式分配的,而不是用堆。因此他们比
Class要快很多!
- 速度:值类型通常来说是以栈的形式分配的,而不是用堆。因此他们比
- 拷贝:
Objective-C里拷贝一个对象,你必须选用正确的拷贝类型(深拷贝、浅拷贝),而值类型的拷贝则非常轻松!
- 拷贝:
- 线程安全:值类型是自动线程安全的。无论你从哪个线程去访问你的
Struct,都非常简单。
- 线程安全:值类型是自动线程安全的。无论你从哪个线程去访问你的
缺点: (需要注意的地方)
-
Objective-C:当你的项目的代码是Swift和Objective-C混合开发时,你会发现在Objective-C的代码里无法调用Swift的Struct。因为要在Objective-C里调用Swift代码的话,对象需要继承于NSObject。
Struct不是Objective-C的好朋友。
-
- 继承:
Struct不能相互继承。
- 继承:
-
NSUserDefaults:Struct不能被序列化成NSData对象。
-
