在 iOS 开发中,语言的选择是最初的一步。面试
Objective-C 是苹果为 iOS 和 Mac 开发量身定制的语言。它随着 iPhone 的出现而大火,直到今天国内外大多数的 App 依然是用 Objective-C 在写。它一度在 TIOBE 排行榜上位列第三名,仅次于 Java 和 C。其市场占有份额也远超其余语言。看名字咱们能够知道,它与 C 语言有千丝万缕的联系,事实上也确实如此:Objective-C 是 C 语言的超集,它在 C 语言主体上加上了面向对象的特性。这是为了 App 开发的方便,同时也兼顾了语言的总体性能。编程
2014年来,Swift 横空出世,功能不断完善,逐渐成为 Apple 全力主推的官方编程语言。自发布以来,Swift 已经历经4个版本的迭代。在 TIOBE 编程语言排行榜上的目前位列12位,超过 Ruby 并远远甩开其上代语言 Objective-C。从性能上来讲,它的速度是 Objective-C 的2.6倍,Python 的8.4倍。更重要的是,Swift 是一门开源的语言,它的质量和进步接受着整个业界的建议、监督、关注。不管从哪一个角度讲,Swift 都将取代 Objective-C,成为 iOS 开发的主流语言。安全
如今的面试中,传统大厂如BAT对 Objective-C 的语言进行较多考察,平常开发也是以 Objective-C为主。而由于 Swift 的高歌猛进,咱们往后会看到关于 Swift 的问题愈来愈多。本文收录总结了常见的 Swift 和 Objective-C 的面试题,但愿对你们有所帮助。多线程
strong表示指向并拥有该对象。其修饰的对象引用计数会增长1。该对象只要引用计数不为0则不会被销毁。固然强行将其设为nil能够销毁它。闭包
weak表示指向但不拥有该对象。其修饰的对象引用计数不会增长。无需手动设置,该对象会自行在内存中销毁。async
assign主要用于修饰基本数据类型,如NSInteger和CGFloat,这些数值主要存在于栈上。编程语言
weak 通常用来修饰对象,assign通常用来修饰基本数据类型。缘由是assign修饰的对象被释放后,指针的地址依然存在,形成野指针,在堆上容易形成崩溃。而栈上的内存系统会自动处理,不会形成野指针。函数
copy与strong相似。不一样之处是strong的复制是多个指针指向同一个地址,而copy的复制每次会在内存中拷贝一份对象,指针指向不一样地址。copy通常用在修饰有可变对应类型的不可变对象上,如NSString, NSArray, NSDictionary。oop
Objective-C 中,基本数据类型的默认关键字是atomic, readwrite, assign;普通属性的默认关键字是atomic, readwrite, strong。性能
__weak
,__block
__weak
与weak基本相同。前者用于修饰变量(variable),后者用于修饰属性(property)。__weak
主要用于防止block中的循环引用。
__block
也用于修饰变量。它是引用修饰,因此其修饰的值是动态变化的,便可以被从新赋值的。__block
用于修饰某些block内部将要修改的外部变量。
__weak
和__block
的使用场景几乎与block息息相关。而所谓block,就是Objective-C对于闭包的实现。闭包就是没有名字的函数,或者理解为指向函数的指针。
atomic修饰的对象会保证setter和getter的完整性,任何线程对其访问均可以获得一个完整的初始化后的对象。由于要保证操做完成,因此速度慢。它比nonatomic安全,但也并非绝对的线程安全,例如多个线程同时调用set和get就会致使得到的对象值不同。绝对的线程安全就要用关键词synchronized。
nonatomic修饰的对象不保证setter和getter的完整性,因此多个线程对它进行访问,它可能会返回未初始化的对象。正由于如此,它比atomic快,但也是线程不安全的。
ARC全称是 Automatic Reference Counting,是Objective-C的内存管理机制。简单地来讲,就是代码中自动加入了retain/release,原先须要手动添加的用来处理内存管理的引用计数的代码能够自动地由编译器完成了。
ARC的使用是为了解决对象retain和release匹配的问题。之前手动管理形成内存泄漏或者重复释放的问题将不复存在。
之前须要手动的经过retain去为对象获取内存,并用release释放内存。因此之前的操做称为MRC (Manual Reference Counting)。
循环引用是指2个或以上对象互相强引用,致使全部对象没法释放的现象。这是内存泄漏的一种状况。举个例子:
class Father
@interface Father: NSObject
@property (strong, nonatomic) Son *son;
class Son
@interface Son: NSObject
@property (strong, nonatomic) Father *father;
@end
上述代码有两个类,分别为爸爸和儿子。爸爸对儿子强引用,儿子对爸爸强引用。这样释放儿子必须先释放爸爸,要释放爸爸必须先释放儿子。如此一来,两个对象都没法释放。
解决方法是将Father中的Son对象属性从strong改成weak。
内存泄漏能够用Xcode中的Debug Memory Graph去检查,同时Xcode也会在runtime中自动汇报内存泄漏的问题。
- (void)viewDidLoad {
UILabel *alertLabel = [[UILabel alloc] initWithFrame:CGRectMake(100,100,100,100)];
alertLabel.text = @"Wait 4 seconds...";
[self.view addSubview:alertLabel];
NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];
[backgroundQueue addOperationWithBlock:^{
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:4]];
alertLabel.text = @"Ready to go!”
}];
}
Bug在于,在等了4秒以后,alertLabel并不会更新为Ready to Go。
缘由是,全部UI的相关操做应该在主线程进行。当咱们能够在一个后台线程中等待4秒,可是必定要在主线程中更新alertLabel。
最简单的修正以下:
- (void)viewDidLoad {
UILabel *alertLabel = [[UILabel alloc] initWithFrame:CGRectMake(100,100,100,100)];
alertLabel.text = @"Wait 4 seconds...";
[self.view addSubview:alertLabel];
NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];
[backgroundQueue addOperationWithBlock:^{
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:4]];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
alertLabel.text = @"Ready to go!”
}];
}];
}
缘由在于滑动时当前线程的runloop切换了mode用于列表滑动,致使timer暂停。
runloop中的mode主要用来指定事件在runloop中的优先级,有如下几种:
Default(NSDefaultRunLoopMode):默认,通常状况下使用;
Connection(NSConnectionReplyMode):通常系统用来处理NSConnection相关事件,开发者通常用不到;
Modal(NSModalPanelRunLoopMode):处理modal panels事件;
Event Tracking(NSEventTrackingRunLoopMode):用于处理拖拽和用户交互的模式。
Common(NSRunloopCommonModes):模式合集。默认包括Default,Modal,Event Tracking三大模式,能够处理几乎全部事件。
回到题中的情境。滑动列表时,runloop的mode由原来的Default模式切换到了Event Tracking模式,timer原来好好的运行在Default模式中,被关闭后天然就中止工做了。
解决方法其一是将timer加入到NSRunloopCommonModes中。其二是将timer放到另外一个线程中,而后开启另外一个线程的runloop,这样能够保证与主线程互不干扰,而如今主线程正在处理页面滑动。示例代码以下:
// 方法1
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// 方法2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(repeat:) userInfo:nil repeats:true];
[[NSRunLoop currentRunLoop] run];
});
Swift 中,类是引用类型,结构体是值类型。值类型在传递和赋值时将进行复制,而引用类型则只会使用引用对象的一个"指向"。因此他们二者之间的区别就是两个类型的区别。
举个简单的例子,代码以下
class Temperature {
var value: Float = 37.0
}
class Person {
var temp: Temperature?
func sick() {
temp?.value = 41.0
}
}
let A = Person()
let B = Person()
let temp = Temperature()
A.temp = temp
B.temp = temp
A.sick()
上面这段代码,因为 Temperature 是 class ,为引用类型,故 A 的 temp 和 B 的 temp指向同一个对象。A 的 temp修改了,B 的 temp 也随之修改。这样 A 和 B 的 temp 的值都被改为了41.0。若是将 Temperature 改成 struct,为值类型,则 A 的 temp 修改不影响 B 的 temp。
内存中,引用类型诸如类是在堆(heap)上,而值类型诸如结构体实在栈(stack)上进行存储和操做。相比于栈上的操做,堆上的操做更加复杂耗时,因此苹果官方推荐使用结构体,这样能够提升 App 运行的效率。
class有这几个功能struct没有的:
class能够继承,这样子类可使用父类的特性和方法
类型转换能够在runtime的时候检查和解释一个实例的类型
能够用deinit来释放资源
一个类能够被屡次引用
struct也有这样几个优点:
结构较小,适用于复制操做,相比于一个class的实例被屡次引用更加安全。
无须担忧内存memory leak或者多线程冲突问题