iOS 面试题总结
最近项目做完了 比较空闲 在网上看了一份面试题 想自己整理一下
一.为什么说Objective-C是一门动态的语言?NSUInteger和NSInteger 的区别?
静态 动态是相对的,这里的动态语言指的是不需要在编译时确定所有的东西,在运行的时候还可以动态的添加变量 方法 和 类
OC可以通过RunTime这个运行时机制,在运行的时候动态的添加变量,方法和类等,所以OC是一门动态的语言。
OC是C语言的超集,在C语言的基础上添加了面向对象的特性,并且利用RunTime运行时机制,为OC添加了动态的特性。
C语言是静态语言,它的工作方式是通过函数调用,这样在编译时我们就已经确定程序是如何运行的。而OC是动态语言,它并非通过调用类的方法来执行功能,而是通过给对象发送消息,对象在接收到消息之后会去找匹配的方法来运行。由于这个机制,在你调用一个方法时,它的最终实现是可以改变的,而且你可以在运行时给类添加方法。
因为在运行时可以向类中添加方法,所以编译器在编译的时候,还无法确定类中是否有某个方法的视线,对于类无法处理一个消息时,就会触发消息转发机制
1 动态方法解析,先征询接受者所属的类,能够动态添加方法,来处理这个消息,若可以则结束,如不能则继续向下走。
2 完整的消息转发机制
<1>请接收者看看有没其他对象能处理这条消息,若有,就把这个消息转发给那个对象然后结束
<2>运行时系统会把与消息有关细节全部封装到NSInvocation 对象中,再给对象最后一次机会,令其设法解决当前还未处理的这条消
int和NSInteger其实是差不多的,但更推荐使用NSInteger,因为使用NSInteger,这样就不用考虑设备是32位还是64位了 NSUInteger是无符号的,即没有负数,NSInteger是有符号的,所以NSUInteger类型不能给它赋负值
二 .讲一下MVC和MVVM,MVP?
做客户端开发,前端开发对MVC,MVP,MVVM,这些名词不了解也应该大致听过,都是为了解决图形界面应用程序复杂性管理问题而产生的应用架构模式。
在开发应用程序的时候,以求更好的管理应用程序的复杂性,基于职责分离的思想都会对应用程序进行分层。在开发图形界面应用程序的时候,会把管理用户界面的层次称为View,应用程序的数据为Model(注意这里的model指的是Domain Model 这个应用程序对需要解决的问题的数据抽象,不包含应用的状态,可以简单的理解为对象。)Model层对应用程序的业务逻辑无知,只保存数据结构和提供数据操作的接口。
有了View和Model的分层,那么就有了两个问题
<1>响应用户操作的业务逻辑(例如:排序)的管理
<2>View如何同步Model的变更
带着这两个问题开始探索MV*模式,会发现这些模式之间的差异可以归纳为对这两个问题处理的方式的不同
MVC的依赖关系
Controller和View都依赖Model层,Controller和View可以相互依赖。在一些网上的资料中Controller和View之间的依赖关系可能不一样,有些是单向依赖,有些是双向依赖。这个关系不大,后面会看到她们的依赖关系都是为了把处理用户行为触发的业务逻辑的处理权交给Controller。
MVC的调用关系
用户对View进行操作后,View捕捉到这个操作,会把处理权移交给Controller(Pass calls),Controller接着回执行相关的业务逻辑,这些业务逻辑可能需要对Model进行操作;当Model变更了以后,会通过观察者模式,通知View;View通过观察者模式收到Model的变更消息以后,会向Model请求最新的数据,重新更新视图
看似没什么特别的地方,但是有几个需要特别关注的点
1.View 把控制权交给Controller 自己不执行业务逻辑
2.Controller 执行业务逻辑并且操作Model,但是不会直接操作View,可以说它是对View无知的。
3.View和Model的同步消息是通过观察者模式实现的,而同步操作是由View自己请求Model的数据然后对视图进行更新。
需要特别注意的是MVC模式的精髓在于第三点;Model的更新是通过观察者模式告诉View的,具体的表现形式可以是Pub(发布者)/Sub(订阅者)或者是触发Events。通过观察者模式的好处就是;不同的MVC三角关系可能会有共同的Model,一个MVC三角中的Controller操作了Model以后,两个MVC中的View都会收到通知,做出改变。保持了依赖同一块Model的不同View显示数据的实时性和准确性。
MVC的优缺点
优点
1、把业务逻辑全部分离到Controller中,模块化程度高。当业务逻辑变更的时候,不需要变更View和Model,只需要Controller换成另外一个Controller就行了
2、观察者模式可以做到多视图同时更新。
缺点
1.Controller测试困难,因为视图同步操作是由View自己执行,而View只能在有UI的环境下运行。在没有UI环境下对Controller进行单元测试的时候,Controller业务逻辑的正确性是无法验证的:Controller更新Model的时候,无法对View的更新操作进行断言。
2.View无法组件化。View是强依赖特定的Model的,如果需要把这个View抽出来作为一个另外一个应用程序可复用的组件就困难了。因为不同程序的的Domain Model是不一样的
MVP模式
MVP模式是MVC模式的改良,下图是MVP的依赖关系
MVP模式把MVC中的Controller变成了Presenter,可以看出,打破了View原来对于Model的依赖,其余的依赖关系和MVC模式一致。
MVP的调用关系
和MVC模式一样,用户对View的操作都会从View移交给Presenter,Persenter同样会执行相应的业务逻辑,并且对Model进行相应的操作,但是Model通过观察者模式传递给Presenter而不是View。Presenter获取到Model变更的消息以后,通过View提供的接口更新界面。
关键点
1.View不在负责同步逻辑,而是由Presenter负责,Presenter中既有业务逻辑也有同步逻辑。
2.View需要提供借口给Presenter使用。(关键)
1、便于测试。Presenter对View是通过接口进行,在对Presenter进行不依赖UI环境的单元测试的时候。可以通过Mock一个View对象,这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中,单元测试的时候就可以完整的测试Presenter业务逻辑的正确性。
2、View可以进行组件化。在MVP当中,View不依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务逻辑完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做高度可复用的View组件。
缺点
1、Presenter中除了业务逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较笨重,维护起来会比较困难。
MVVM模式
MVVM可以看作是一种特殊的MVP模式,或者说是对MVP模式的改良
ViewModel
MVVM代表的是Model-View-ViewModel,这里解释一下什么是ViewModel,ViewModel的含义就是Model of View 视图的模型。她的含义包含了领域模型(Domain Model)和视图的状态(state)。在图形界面应用程序中,界面所提供的信息可能不仅仅包含应用程序的领域模型,还可能包含一些领域模型不包含的视图状态,例如电子表格程序需要显示当前的排序的状态是顺序的还是逆序的,而这是Domain Model所不包含的,但也是需要显示的信息。
可以简单的把View Model理解为页面上所显示内容的数据抽象,和Domain Model不一样,ViewModel更适合用来描述View。
MVVM的依赖
MVVM的依赖和MVP的依赖相似 只不过是把P换成了VM
MVVM的优缺点
优点
1、提高可维护性。解决了MVP大量的手动View和Model同步的问题,提供双向绑定机制。提高了代码的可维护性。
2、简化测试。因为同步逻辑是交由Binder做的,View跟着Model同时变更,所以只需要保证Model的正确性,View就正确。大大减少了对View同步更新的测试。
缺点
1、过于简单的图形界面不适用,或说牛刀杀鸡。
2、对于大型的图形应用程序,视图状态较多,ViewModel的构建和维护的成本都会比较高。
3、数据绑定的声明是指令式地写在View的模版当中的,这些内容是没办法去打断点debug的。
三 为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?
weak 其实是一个hash(哈希)表 key是所指对象的地址,Value是weak指针的地址(当weak指针所指的对象销毁的时候,这个表根据这个对象地址(key),找到所有的只想他的Value(weak指针),并把这些指针置nil),我们都知道weak的作用:所引用的对象的计数器不会加一,并在引用对象被释放的时候自动置为nil。通常用于解决循环引用.
weak 实现原理概括
RunTime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。
weak的实现原理可以概括为三步
(1)初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
(2)添加引用时 objc_initWeak函数会调用objc_storeWeak()函数,objc_storeWeak()的作用时更新指针的指向。创建对应的弱引用表
(3)释放时,调用clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
代理使用weak指针,其实就是为了解决循环引用的问题,假如一个A对象引用了另一个B对象并遵循了它的代理 如果不使用weak 就会造成 A引用了B B持有delegate delegate持有A 造城循环引用。而如果食用了weak delegate就不会持有A 打破循环引用,释放内存.
在代理中经常会看到DataSource和Delegate
DataSource 偏重于数据的回调,View里有什么东西 属性都是什么。例如UITableviewDatasource; delegate偏重于与用户交互的回调,有那些方法可以供我使用,例如UITableviewDelegate
Block和代理的区别
block和代理是iOS开发中实现回调的两种方式,大多数情况下是用哪个都可以 具体可以参考这里 https://www.jianshu.com/p/6bba9b4a25d5
四 属性的实质是什么?包括哪几个部分?属性默认的关键字有哪些?@dynamic关键字和@synthesize关键字是用来做什么的?
属性的本质也就是说@property的本质是什么
其实就是 @property = ivar + getter + setter (实例变量 + get方法 + set方法) https://www.jianshu.com/p/7ddefcfba3cb
atomic
原子操作(原子性是指事务的一个完整操作,操作成功就提交,反之就回滚。原子操作就是指具有原子性的操作),在OC属性设置中,默认的就是atomic,意思就是Setter/getter函数是一个原子操作,如果多线程同时调用setter时,不会出现某一个线程完成setter所有的语句之前,另一个线程就开始执行setter。相当于函数加了锁。这样的话,并发访问性能会比较低.
nonatomic
非原子操作属性 一般不需要多线程支持的时候就用它,这样在并发访问的时候效率会比较高,在OC中通常对象类型都应该声明为非原子性的。iOS中程序启动时系统会自动生成一个单一的主线程。程序在执行的时候一般情况下都是在同一个线程里面进行操作。如果在程序中 我们确定某一个属性会在多线程中被使用,并且需要做数据同步,就必须设置成原子性的,但也可以设置成非原子性的,然后自己在程序中用加锁之类的来做数据同步
@synthesize 和 @dynamic
如果没有实现setter和getter方法,编译将会自动生成setter和getter 如果@synthesize和@dynamic都没写 默认时@synthesize
@dynamic 属性的setter和getter方法由用户自己实现,不能自动生成 (当然对于 readonly 的属性只需提供 getter 即可)
readwrite
如果没有声明成 readonly ,那就 默认是 readwrite 。可以用来赋值,也可以被赋值
readonly
不可以被赋值 只可以调用
assgin 不会使引用计数加1 直接饮用对象 对象释放 会指向一个空地址 不安全
weak 见上面
retain strong
在ARC打开的情况下 才使用strong 引用计数加1
copy 建立一个索引计数为1的对象,在赋值时会使用传入值的一份拷贝.
综上所述,如果在声明属性的时候 没写关键字 如果是基本对象会默认 assign readwrite atomic OC对象默认 strong readwrite atomic
五 NSString为什么要用copy关键字,如果用strong会有什么问题?(注意:这里没有说用strong就一定不行。使用copy和strong是看情况而定的)
@interface ViewController () @property (nonatomic,strong) NSString *strongStr;
@property (nonatomic,copy) NSString * copyedStr; @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; NSString *string = [NSString stringWithFormat:@"abc"];
self.strongStr = string;
self.copyedStr = string;
NSLog(@"origin string: %p, %p", string, &string);
NSLog(@"strong string: %p, %p", _strongStr, &_strongStr);
NSLog(@"copy string: %p, %p", _copyedStr, &_copyedStr); } -- ::55.804861+ StrCopyAndStrong[:] origin string: 0xa000000006362613, 0x7fff558b0c68
-- ::55.805013+ StrCopyAndStrong[:] strong string: 0xa000000006362613, 0x7f8ecce058a0
-- ::55.805123+ StrCopyAndStrong[:] copy string: 0xa000000006362613, 0x7f8ecce058a8
我们可以看到 这种情况下 不管是Strong还是copy属性的对象,其指向的地址都是同一个,即为string指向的地址。如果我们换作MRC环境,打印string的引用计数的话,会看到其引用计数值是3,即strong操作和copy操作都使原字符串对象的引用计数值加了1。
接下来 我们把string由不可变改为可变对象
NSString *string = [NSString stringWithFormat:@"abc"];
变为
NSMutableString *string = [NSMutableString stringWithString:@"abc"];
打印结果变为
-- ::04.404233+ StrCopyAndStrong[:] origin string: 0x60400005bff0, 0x7fff557d2c68
-- ::04.404412+ StrCopyAndStrong[:] strong string: 0x60400005bff0, 0x7fc8d3c08ac0
-- ::04.404522+ StrCopyAndStrong[:] copy string: 0xa000000006362613, 0x7fc8d3c08ac8
此时 可以看到copy属性的字符串已经不在指向string字符串对象,而是深拷贝了string字符串,并让_copyedStr指向这个字符串。
我们在声明一个NSString属性时,对于其内存相关特性,通常会有两种选择(基于ARC环境):strong和copy。在MRC环境下,打印两者的引用计数,可以看到string对象的引用计数是2,而_copyedString对象的引用计数是1。
此时,我们如果去修改string字符串的话,可以看到:因为_strongStr与string是指向同一对象,所以_strongStr的值也会跟随着改变(需要注意的是,此时_strongStr的类型实际上是NSMutableString,而不是NSString);而_copyedStr是指向另一个对象的,所以并不会改变。
由于NSMutableString是NSString的子类,所以一个NSString指针可以指向NSMutableString对象,让我们的strongString指针指向一个可变字符串是OK的。
而上面的例子可以看出,当源字符串是NSString时,由于字符串是不可变的,所以,不管是strong还是copy属性的对象,都是指向源对象,copy操作只是做了次浅拷贝。
当源字符串是NSMutableString时,strong属性只是增加了源字符串的引用计数,而copy属性则是对源字符串做了次深拷贝,产生一个新的对象,且copy属性对象指向这个新的对象。另外需要注意的是,这个copy属性对象的类型始终是NSString,而不是NSMutableString,因此其是不可变的。
这里还有一个性能问题,即在源字符串是NSMutableString,strong是单纯的增加对象的引用计数,而copy操作是执行了一次深拷贝,所以性能上会有所差异。而如果源字符串是NSString时,则没有这个问题。
所以,在声明NSString属性时,到底是选择strong还是copy,可以根据实际情况来定。不过,一般我们将对象声明为NSString时,都不希望它改变,所以大多数情况下,我们建议用copy,以免因可变字符串的修改导致的一些非预期问题。http://www.cocoachina.com/ios/20150512/11805.html
六 如何令自己所写的对象具有拷贝功能
若想令自己所写的对象具有拷贝功能,则需要实现NSCopying协议。如果自定义的对象氛围可变版本和不可变版本,那么需要同时实现NSCopying/NSMutableCopying协议。
具体步骤:
需要声明该类遵守NSCopying协议
实现NSCopying协议,该协议只有一个方法
- (id)copyWithZone:(NSZone *)zone;
注意:一提到让自己的类用 copy 修饰符,我们总是想覆写copy方法,其实真正需要实现的却是 “copyWithZone” 方法。
至于如何重写带copy关键字的setter方法这个问题
- (void)setName:(NSString *)name {
//[_name release];
_name = [name copy];
}
另外对于实现了NSCopying和NSMutableCopying协议的类来说,对他们的对象发送 copy 和 mutableCopy 也会有不同的效果.
copy 对于不可变对象 是浅拷贝(拷贝对象的指针,指向的是同一块内存地址<内存地址中的内容没被拷贝>) 对可变对象进行copy操作 是深拷贝(拷贝出来一个新的对象指向一个拷贝出来的新的内存地址<原内存地址中的内容也被拷贝>) 但是拷贝出来的都是不可变对象
需要注意的是集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制
mutableCopy 拷贝出来的都是可变对象 而且进行的是深拷贝 这篇文章讲的很具体 https://www.jianshu.com/p/e98e6e30ebda
七 为什么IBOutlet修饰的UIView也适用weak关键字
因为当我们将控件拖到Storyboard上,相当于新创建了一个对象,而这个对象是加到视图控制器的view上,view有一个subViews属性,这个属性是一个数组,里面是这个view的所有子view,而我们加的控件就位于这个数组中,那么说明,实际上我们的控件对象是属于view的,也就是说view对加到它上面的控件是强引用。当我们使用Outlet属性的时候,我们是在viewController里面使用,而这个Outlet属性是有view来进行强引用的,我们在viewController里面仅仅是对其使用,并没有必要拥有它,所以是weak的。
如果将weak改为strong,也是没有问题的,并不会造成强引用循环。当viewController的指针指向其他对象或者为nil,这个viewController销毁,那么对控件就少了一个强引用指针。然后它的view也随之销毁,那么subViews也不存在了,那么控件就又少了一个强引用指针,如果没有其他强引用,那么这个控件也会随之销毁。
不过,既然没有必将Outlet属性设置为strong,那么用weak就好了
八 nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?
在默认情况下,由编译器所合成的方法会通过锁定机制确保其原子性(atomicity)。如果属性具备nonatomic特质,则不需要同步锁。
具备atomic特质的获取方法会通过锁定机制来确保其操作的原子性。也就是说,如果两个线程同时读取一个属性,那么不论何时,总能看到有效的属性值。
如果不加锁的话(或者说使用nonatomic语义),那么当其中一个线程正在改写某属性值的时候,另外一个线程也许会突然闯入,把尚未修改好的属性值读取出来。发证这种情况时,线程读取道德属性值肯能不对。
一般iOS程序中,所有属性都声明为nonatomic。这样做的原因是:
在iOS中使用同步锁的开销比较大, 这会带来性能问题。一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全”(thread safety),若要实现“线程安全”的操作,还需采用更为深层的锁定机制才行。
例如:一个线程在连续多次读取某个属性值的过程中有别的线程在同时改写该值,那么即便将属性声明为atomic,也还是会读取到不同的属性值。
因此,iOS程序一般都会使用nonatomic属性。但是在Mac OS X程序时, 使用atomic属性通常都不会有性能瓶颈;
然而atomic一定是线程安全的么,回答是NO :
nonatomic的内存管理语义是非原子性的,非原子性的操作本来就是线程不安全,而atomic的操作是原子性的,但并不意味着他就是线程安全的,它会增加正确的几率,能够更好的避免线程错误,但仍旧是不安全的。
为了说atomic与nonatomic的本质区别其实也就是在setter方法上的操作不同:
nonatomic的实现:
- (void)setCurrentImage:(UIImage *)currentImage
{
if (_currentImage != currentImage) {
[_currentImage release];
_currentImage = [currentImage retain]; // do something
}
} - (UIImage *)currentImage
{
return _currentImage;
}
atomic的实现:
- (void)setCurrentImage:(UIImage *)currentImage
{
@synchronized(self) {
if (_currentImage != currentImage) {
[_currentImage release];
_currentImage = [currentImage retain]; // do something
}
}
} - (UIImage *)currentImage
{
@synchronized(self) {
return _currentImage;
}
}
当使用atomic时,虽然对属性的读和写是原子性的,但是仍然可能出现线程错误:当线程A进行写操作,这时其他线程的读或者写操作会因为等该操作而等待。当A线程的写操作结束后,B线程进行写操作,所有这些不同线程上的操作都将依次顺序执行——也就是说,如果一个线程正在执行 getter/setter,其他线程就得等待。如果有线程C在A线程读操作之前release了该属性,那么还会导致程序崩溃。所以仅仅使用atomic并不会使得线程安全,我们还要为线程添加lock来确保线程的安全。
更准确的说应该是读写安全,但并不是线程安全的,因为别的线程还能进行读写之外的其他操作。线程安全需要开发者自己来保证。
其实无论是否是原子性的只是针对于getter和setter而言,比如用atomic去操作一个NSMutableArray ,如果一个线程循环读数据,一个线程循环写数据,肯定会产生内存问题,这个就跟getter和setter就木有关系了。
至于保证线程安全 我觉得可以看看这篇文章:http://www.cocoachina.com/ios/20160707/16957.html
九 UICollectionView自定义layout如何实现?
第一步 我们要自定义一个继承与UICollectionViewLayout的布局
第二步 根据需要重写UICollectionViewLayout中的一些方法 常用的方法如下
//预布局方法 所有的布局应该写在这里(在里面计算好必要的布局信息并存储起来)
open func prepare()
//返回collectionView 的尺寸
open var collectionViewContentSize: CGSize { get }
//返回rect中的所有的元素布局属性
//返回的是包含UICollectionViewLayoutAttributes的数组
open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
//根据Indexpath返回布局属性
open func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
//返回头尾视图的布局属性
open func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
//返回对应于indexPath的位置的装饰视图的布局属性,如果没有装饰视图可不重载
open func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
//当边界发生变化时 是否重新布局 如果YES则在边界变化(一般是scroll到其他地方)时,将重新计算需要的布局信息
open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool
UICollectionViewLayoutAttributes的定义是这样的
@property (nonatomic) CGRect frame
@property (nonatomic) CGPoint center
@property (nonatomic) CGSize size
@property (nonatomic) CATransform3D transform3D
@property (nonatomic) CGFloat alpha
@property (nonatomic) NSInteger zIndex
@property (nonatomic, getter=isHidden) BOOL hidden
1.一个cell对应一个UICollectionViewLayoutAttributes对象
2.UICollectionViewLayoutAttributes对象决定了cell的摆设位置(frame)
:-(void)prepareLayout 设置layout的结构和初始需要的参数等
:-(CGSize) collectionViewContentSize 确定collectionView的所有内容的尺寸。
:-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect初始的layout的外观将由该方法返回的UICollectionViewLayoutAttributes来决定。
:在需要更新layout时,需要给当前layout发送
)-invalidateLayout, 该消息会立即返回,并且预约在下一个loop的时候刷新当前layout
)-prepareLayout,
)依次再调用-collectionViewContentSize和-layoutAttributesForElementsInRect来生成更新后的布局
所以自定义UICOllectionViewLayout的步骤可以分为三步
1.覆写prepareLayout方法,并在里面事先就计算好必要的布局信息并存储起来。
2.基于prepareLayout方法中的布局信息,使用collectionViewContentSize方法返回UICollectionView的内容尺寸。
3.使用layoutAttributesForElementsInRect:方法返回指定区域cell、Supplementary View和Decoration View的布局属性。
10 用StoryBoard开发界面有什么弊端? 如何避免?
难以维护
Storyboard在某些角度上,是难以维护的。我所遇到过的实际情况是,公司一个项目的2.0版本,设计师希望替换原有字体。然而原来项目的每一个Label都是采用Storyboard来定义字体的,因此替换新字体需要在Storyboard中更改每一个Label。
幸亏我们知道Storyboard的源文件是XML,最终写了一个读取-解析-替换脚本来搞定这件事
性能瓶颈
当项目达到一定的规模,即使是高性能的MacBook Pro,在打开Storyboard是也会有3-5秒的读取时间。无论是只有几个Scene的小东西,还是几十个Scene的庞然大物,都无法避免。Scene越多的文件,打开速度越慢(从另一个方面说明了分割大故事板的重要性)。
让人沮丧的是,这个造成卡顿的项目规模并不是太难达到。
我猜想是由于每一次打开都需要进行I/O操作造成的,Apple对这一块的缓存优化没有做到位。可能是由于Storyboard占用了太多内存,难以在内存中进行缓存。Whatever,这个问题总是让人困扰的。
然而需要指出的是,采用Storyboard开发或采用纯代码开发的App,在真机的运行效率上,并没有太大的区别
错误定位难
Storyboard的初学者应该对此深有体会。排除BAD_EXCUSE错误不说,单单是有提示的错误,就足以让人在代码和Storyboard之间来回摸索,却无法找到解决方案。
一个典型的例子是,在代码中删除了IBOUTLET属性或者IBAction方法,但是却忘了在Storyboard中删除对应的连接,运行后crash。然而控制台只会输出一些模糊其词的错误描述。
*** Terminating app due to uncaught exception 'NSUnknownKeyException',
reason: '[ setValue:forUndefinedKey:]:
this class is not key value coding-compliant for the key drawButton.'
最后一方面是其提供的便利,另一方面是Apple对Storyboard的大力支持。这一点宏观上看,可以在以往对Storyboard的改进和增强上看出,微观上看,几乎所有iOS 8之后的simple code都或多或少采用了Storyboard作为界面开发工具;
那改如何避免这些弊端呢, 参考以下文章:
iOS项目开发实战——storyboard设置界面技巧与注意事项
11 进程和线程的区别? 同步异步的区别?串行和并发的区别?
进程和线程
进程中所包含的一个或多个执行单元称为线程。比如一个应用程序就是一个进程,而它又包含至少一个线程。
主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程;
同步 和 异步
所谓同步 就是 在线程中一个任务没有执行完毕后 下一个任务就不会执行(任务是一个接一个的执行的) 比如主线程中的任务 或者GCD中异步函数+串行队列。同步函数+并发队列
异步 和同步相对 调用异步函数执行任务 在多线程下 多个任务是可以同时执行的(并不是严格上的同时) 只是在一个任务还没结束的时候,可以开启下一个任务.
由此 可以看出 在iOS中 同步 和 异步 影响的是任务执行的方式
串行 和 并发
并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。就好像两个人各拿一把铁锨在挖坑,一小时后,每人一个大坑。所以无论从微观还是从宏观来看,二者都是一起执行的
强调同时处理多个任务的能力
并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。这就好像两个人用同一把铁锨,轮流挖坑,一小时后,两个人各挖一个小一点的坑,要想挖两个大一点得坑,一定会用两个小时。
强调交替做不同事的能力
12 线程间的通信
线程间通信的体现
1>一个线程传递数据给另一个线程
2>在一个线程中执行完特定任务后 转到另一个线程继续执行任务
线程间通信常用的方法
1.NSthread 可以先将自己的当前线程对象注册到某个全局的对象中去,这样相互之间就可以获取对方的线程对象,然后就可以使用下面的方法进行线程间的通信了,由于主线程比较特殊,所以框架直接提供了在主线程执行的方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait; - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
GCD 是可以嵌套使用的 他们子线程切换到主线程的方法很简单
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{ NSLog(@"donwload---%@", [NSThread currentThread]); // 1.子线程下载图片
NSURL *url = [NSURL URLWithString:@"http://d.jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; // 2.回到主线程设置图片
dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"setting---%@ %@", [NSThread currentThread], image); [self.button setImage:image forState:UIControlStateNormal];
});
});
}
NSOperation 转换线程的方法
- (void)downLoadImage {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"&&&&&"]];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
//主线程更新UI
[NSOperationQueue.mainQueue addOperationWithBlock:^{
self.imageView.image = image;
}]; }];
[queue addOperation:op1];
}
也可以移到其他线程 GCD 和 NSOperation可以传递一个自己建立的队列 不用拘泥于传递主队列(当然是在有这些需求的时候)
13 objc使用什么机制管理对象内存(内存管理方式有哪些)
内存管理的一些面试题
14 属性和成员变量的区别
成员变量的作用范围
@public: 在任何地方都能直接访问对象的成员变量
@private: 只能在当前类的对象方法中直接访问,如果子类需要访问需要通过getter/setter方法来访问。
@protected: 可以在当前类及其子类对象方法中直接访问(系统默认下就是用它来修饰的)
@package:在同一个文件包下就可以直接访问,比如同一个框架。
值得注意的是无论父类是在@interface还是@implementation声明的成员变量子类都能拥有;但是子类能不能直接通过变量名来访问父类中定义的成员变量是需要看父类中定义的成员变量是由什么修饰符来修饰的
//马甲包
https://www.jianshu.com/p/04b63de8ae23
iOS 面试题总结的更多相关文章
- 最全的iOS面试题及答案-转载
1. Object-c的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么? 答: Object-c的类不可以多重继承:可以实现多个接口,通过实现 ...
- IOS面试题总结
iOS面试题: 一:网络理论知识的理解 1:Internet物理地址和IP地址转换采用什么协议 ARP(Address Resolution Protocol)地址解析协议 2:Internet采用哪 ...
- iOS面试题及答案2015.6.7
iOS面试题及答案 1. Object-c的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么? 答: Object-c的类不可以多重继承 ...
- iOS 面试题 总结
#include <iostream> using namespace std; int main () { char p[]={'a','b','c'}, q[]="abc&q ...
- [转载]iOS面试题总
转载自:http://blog.sina.com.cn/s/blog_67eb608b0101r6xb.html (2014-06-13 20:23:33) 转载▼ 标签: 转载 crash 原文 ...
- 试答卓同学的 iOS 面试题
卓同学昨天写了一篇文章<4道过滤菜鸟的iOS面试题>.我手痒决定默写一个参考答案.后来发现不认真回答被大家喷成狗,所以决定积极改造,重新做人.下面就是修编之后的答案. 1. struct和 ...
- iOS面试题大全-点亮你iOS技能树
所有的内容大部分来自于网络的搜集,所以我不是一个创造者,而是一个搬运工.我尽量把题目,尤其是参考答案的出处列明.若有任何疑问,建议,意见,请联系我. 第一部分面试题来源于iOS-Developer-I ...
- 原 iOS面试题收集
原 iOS面试题收集 发表于2年前(2013-07-22 13:47) 阅读(369) | 评论(0) 4人收藏此文章, 我要收藏 赞0 听云性能监测产品App.Server.CDN免费试用,绑定 ...
- ios 面试题 经典(比较全) 根据重点总结
史上最全的iOS面试题及答案 1.写一个NSString类的实现 + (id)initWithCString:(c*****t char *)nullTerminatedCString encodin ...
- YouKu iOS笔试题一
序言 最近收到某某同学将去youku的iOS笔试题的邮件,希望笔者能整理一下,并提供参考答案.笔者决定整理出来,并分享给大家.当然,与此同时,也想看看youku的笔试题到底有多难,也考考自己有多少料吧 ...
随机推荐
- EXTJS4自学手册——图形行为(rotate,scale)
一.旋转图像: { xtype: 'button', text: '旋转的字', handler: function (btn) { Ext.create('Ext.window.Window', { ...
- 【Excle】Countif函数
Countif在Excle中是相当的使用,那么我们看下Countif的如下几个功能: ①一对一对比两列数据 ②输入时必须指定包含指定字符 ③帮助Vlookup实现一对多查找 ④统计不重复的个数 一对一 ...
- Java IO操作:合并流
合并流:把两个文件合并在一起. 主要操作的是内容. 定义: public class SequenceInputStreamextends InputStream 方法摘要: 操作步骤: 1,分别建立 ...
- GoldenGate Lag For Huge Insert
前些天客户的ogg延迟到达8小时左右.于是我当时用logdump追踪了一下: 看进程状态: send extsa staus EXTRACT ZBDBA (PID 2269368) Current s ...
- php 按汉字首字母查询[转载]
<?php function getfirstchar($s0){ //获取单个汉字拼音首字母.注意:此处不要纠结.汉字拼音是没有以U和V开头的 $fchar = ord($s0{0}); if ...
- C#中ArrayList类的使用
ArrayList类 使用大小可按须要动态添加的数组实现IList接口 命名空间:System.Collections 程序集:mscorlib 语法: public class ArrayList: ...
- 【转载】表单验证<AngularJs>
原文地址:http://www.cnblogs.com/rohelm/archive/2014/10/19/4033513.html 常用的表单验证指令 1. 必填项验证 某个表单输入是否已填写,只要 ...
- android proguard 保留内部类
今天在使用Proguard keep一个 静态内部类的时候,混淆完之后一直找不到那个静态内部类,内心抓狂啊. 最后在stackoverflow上找到了答案: -keepattributes Excep ...
- 【问题记录】eclipse启动web项目时,spring会初始化两次
背景:一个tomcat,一个eclipse,一个SSM框架的web项目.在eclipse中新建tomcat服务器,默认配置,然后在服务器配置中将Server Locations改成Use Tomcat ...
- oracle 数据查询
1,读取从今天到1个月前之间的数据select * from tablewhere column between add_months(sysdate, -1) and sysdate;