OC 和 Swift 问题 (随时补充)
2019-01-02 本文已影响5人
一欧Yiou
问: 关键字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
对象。
-