http://www.cnblogs.com/ldnh/tag/iOS面试/
1、#import 跟#include、@class有什么区别?#import<> 跟 #import”"又什么区别?
include和#import都能完整的包含某个文件的内容,#import可以防止一个文件被导入多次。@class只是声明一个类名,并不会包含包含类的完整声明,@class可以解决循环包含的问题。
include通常是用来包含系统自带的文件,而import则是用来包含自己自定义的文件。
import会包含这个类的所有信息,包括实体变量和方法,而@class只是告诉编译器,其后面声明的名称是类的名称,至于这些类是如何定义的,暂时不用考虑,后面会再告诉你。
在头文件中, 一般只需要知道被引用的类的名称就可以了。 不需要知道其内部的实体变量和方法,所以在头文件中一般使用@class来声明这个名称是类的名称。 而在实现类里面,因为会用到这个引用类的内部的实体变量和方法,所以需要使用#import来包含这个被引用类的头文件。
在编译效率方面考虑,如果你有100个头文件都#import了同一个头文件,或者这些文件是依次引用的,如A–>B, B–>C, C–>D这样的引用关系。当最开始的那个头文件有变化的话,后面所有引用它的类都需要重新编译,如果你的类有很多的话,这将耗费大量的时间。而是用@class则不会。
如果有循环依赖关系,如:A–>B, B–>A这样的相互依赖关系,如果使用#import来相互包含,那么就会出现编译错误,如果使用@class在两个类的头文件中相互声明,则不会有编译错误出现。
所以,一般来说,@class是放在interface中的,只是为了在interface中引用这个类,把这个类作为一个类型来用的。 在实现这个接口的实现类中,如果需要引用这个类的实体变量或者方法之类的,还是需要import在@class中声明的类进来.
2、属性readwrite,readonly,assign,retain,copy,nonatomic 各是什么作用,在那种情况下用?
readwrite--同时生成get方法及set方法的声明和实现。
readonly--只生成get方法的声明和实现。
assign--set方法的实现是直接赋值,用于基本数据类型。
retain--set方法的实现是release旧值,retain新值,用于OC对象。
copy--set方法的实现是release旧值,copy新值,用于NSString,Block类型
nonatomic--非原子性,set方法的实现不加锁(atomic主要加的是自旋锁)
unsafe_unretained 用unsafe_unretained声明的指针,指针指向的对象一旦被释放,这些指针将成为野指针
3、写一个setter方法用于完成@property (nonatomic,retain)NSString name,写一个setter方法用于完成@property(nonatomic,copy)NSString name.
- (void)setName:(NSString *)name
{
if (_name != name) {
//release旧值
[_name release];
_name = [name copy];
}
}
- (void)setName:(NSString *)name
{
if (_name != name) {
[_name release];
_name = [name retain];
}
}
4、对于语句NSString*obj = [[NSData alloc] init]; ,编译时和运行时obj分别是什么类型?(待补充)
编译时是NSString类型,执行时是NSData类型。
runtime是一个C语言框架,苹果底层就是这个,
5、常见的object-c的数据类型有那些, 和C的基本数据类型有什么区别?
object-c的数据类型有NSString,NSNumber,NSArray,NSMutableArray,NSData等等,这些都是class,创建后便是对象,而C语言的基本数据类型int,只是一定字节的内存空间,用于存放数值;NSInteger是基本数据类型,并不是NSNumber的子类,当然也不是NSObject的子类。NSInteger是基本数据类型Int或者Long的别名(NSInteger的定义typedef long NSInteger),它的区别在于,NSInteger会根据系统是32位还是64位来决定是本身是int还是Long。
6、id声明的变量有什么特性
id声明的对象能指向任何OC对象
用于修饰代理,
id相当于NSObject *
7、Objective-C如何对内存管理的,说说你的看法和解决方法?
Objective-C内存管理主要有三种方式,MRC(Manual Reference Counting )和ARC(Automatic Reference Counting)、自动释放池
每个对象都有一个引用计数,当他被alloc init出来时,引用计数为1,然后retain一次加1,release减1,当引用计数为0时,系统就会自动调用delloc方法,在delloc方法中[super delloc]必须写,这个对象就会被回收。
Autorelease-自动释放池,在iOS运行过程中,会创建无数个池子,这些池子都是以栈型结构存储的,当一个对象调用autorelease 系统会把该对象放到栈顶的自动释放池中,当自动释放池销毁的时候,系统会对池子中的所有对象进行一次release操作,系统自带的方法中,如果不包含alloc copy new,那么这些方法的返回对象都是autorelease的,如[NSData data].
自动释放池的创建方式
iOS 5之前
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
iOS 5之后
@autoreleasepool
{//开始代表创建自动释放池
·······
}//结束代表销毁自动释放池
ARC--只要没有强指针指向的对象,对象就会被释放,强指针__strong ,属性中的strong相当于MRC中的retain,弱指针__weak,属性中的weak相当于assign,成员变量是弱指针,ARC不允许调用retain release retainCount方法,可以重写delloc方法,但是不允许调用[super delloc]方法。
8、看下面的程序,三次NSLog会输出什么?为什么?
NSMutableArray* ary = [[NSMutableArray array] retain];
NSString *str = [NSString stringWithFormat:@"test"]; // 1
[str retain]; // 2
[ary addObject:str]; // 3
NSLog(@"%d", [str retainCount]);
[str retain]; // 4
[str release]; // 3
[str release]; // 2
NSLog(@"%d", [str retainCount]);
[ary removeAllObjects]; // 1
NSLog(@"%d", [str retainCount]);
str的retainCount创建+1,retain+1,加入数组自动+1
3
retain+1,release-1,release-1
2
数组删除所有对象,所有数组内的对象自动-1
1
9、内存管理的几条原则时什么?按照默认法则.那些关键字生成的对象
需要手动释放?在和property结合的时候怎样有效的避免内存泄露?
谁申请,谁释放
遵循Cocoa Touch的使用原则;
内存管理主要要避免“过早释放”和“内存泄漏”,对于“过早释放”需要注意@property设置特性时,一定要用对特性关键字,对于“内存泄漏”,一定要申请了要负责释放,要细心。
关键字alloc 或new 生成的对象需要手动释放;
设置正确的property属性,对于retain需要在合适的地方释放,
10、Object C中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么?
线程创建有三种方法:使用NSThread创建、使用GCD的dispatch、使用子类化的NSOperation,然后将其加入NSOperationQueue;
在主线程执行代码,方法是performSelectorOnMainThread,如果想延时执行代码可以用performSelector:onThread:withObject:waitUntilDone:
11、ViewController的didReceiveMemoryWarning怎么被调用?
[supper didReceiveMemoryWarning];
12、什么时候用delegate,什么时候用Notification?
Delegate是一种点对点的消息传送机制。传递给自己或者其他对象。有时候他还会返回一个影响事件如何被处理的值。
在内存管理环境中,delegate是弱引用。在垃圾回收环境中,delegate是强引用。
Notification是一种一对多的消息传递方式。他的实质是广播信息给所有observer。消息发送者不需要知道谁是消息的接收者。
他减少了对象之间的依赖。
13、深拷贝和浅拷贝
浅拷贝
//创建一个可变的数组
NSMutableArray *array = [NSMutableArray array];
//创建两个person对象,然后把他们加入到数组中
Person *p1 = [[Person alloc]init];
p1.name = @"小玉";
Person *p2 = [[Person alloc]init];
p2.name = @"小小玉";
[array addObject:p1];
[array addObject:p2];
//浅拷贝
NSArray *newArray = [array copy];
Person *p = newArray[0];
p.name = @"小王八";
//输出array[0]和newArray[0],结果发现他两输出都为小王八,
NSLog(@"array = %@ newArray = %@",((Person *)array[0]).name,((Person *)newArray[0]).name);
深拷贝
//创建一个可变的数组
NSMutableArray *array = [NSMutableArray array];
//创建两个person对象,然后把他们加入到数组中
Person *p1 = [[Person alloc]init];
p1.name = @"小玉";
Person *p2 = [[Person alloc]init];
p2.name = @"小小玉";
[array addObject:p1];
[array addObject:p2];
NSMutableArray *newArray = [NSMutableArray array];
for (Person *p in array) {
Person *p2 = [[Person alloc]init];
p2.name = p.name;
[newArray addObject:p2];
}
Person *person = newArray[0];
person.name = @"小王八";
//输出为小王八
NSLog(@"%@",((Person *)newArray[0]).name);
//输出为小玉
NSLog(@"%@",((Person *)array[0]).name);
14 OC的理解和特性
OC作为一个面向对象的语言,他也就具有面向对象的特点-封装,继承,多态。
OC是一门动态性的语言,他具有动态绑定,动态加载,动态类型。动态即就是在运行时才会做的一些事情。
动态类型-即运行时才决定对象的类型,简单来说,就是id类型,他可以存储任意类型的变量,他自己本身就相当于指针,类似于void*。
静态类型和动态类型--
编译期间检查和运行时检查
静态类型在编译期间就能检查出错误,静态类型声明代码可读性良好,动态类型只有在运行时才能发现错误。
动态绑定-程序直到执行时才知道执行哪个方法,动态绑定需要做的,即就是在实例所属类确定后,将某些属性和方法绑定到实例上。
SEL是类方法方法的指针,他就相当于C语言中的中函数指针。SEL class_func = @selector(),OC类里面的方法都是被转换成SEL变量进行存储的,当类声明一个对象,对象调用方法时,系统会将这个方法转换成SEL,然后拿这个SEL在类方法中进行查找,我们可以手动将方法转换为SEL,然后用SEL去查找方法(performSelector)。
动态加载-根据需求加载相应的资源,在iOS开发中,主要是做屏幕的适配。
15 如何理解MVC设计模式
M指的是model层,它主要是进行一些数据的收集和存储。
V指的是view层,它主要是来展示一些UI方面的东西,比如说布局界面。
C指的是ViewController,他主要有一些业务逻辑的算法,还有一些功能的设计,Controller负责把model层的数据展示在view层,但是view层不能直接和Controller交互,这样,就需要用到代理或者Block了,model层和view层不能直接进行交互。
16 如何理解MVVM模式。
V层指的是viewController层,从viewModel层或得数据,然后展示。
VM指的是viewMOdel,他是model和V层的粘合剂,说实在的,就是把MVC中ViewController中的一些业务逻辑和功能模块给抽出来。
17 协议的基本概念和默认的类型
协议就是一些方法,但是仅仅只是声明这些方法,但是没有实现,他可以被任意类使用,但是他并不是类,协议分为非正式协议和正式协议,非正式协议是NSObject的类别。
协议的用法,当一个类遵循一个协议时,如果协议中的方法使用@optional修饰时,表示这个方法可以实现也可以不实现,但是要是使用@required修饰的话,说明这个类必须要实现这个方法。
默认类型是@required,必须要实现的。
18 什么是单例,单例的写法
单例模式在他的核心结构中只包含一个叫单例的特殊类,他只有一个实例对象并且易于外部访问,从而实现对实例个数的控制,并且减少系统资源,如果想要实现实例对象只有一个,那么单例模式是你最好的选择。
单例创建步骤
创建一个类方法,名字以shared,default current开头。
创建一个全局变量来保存对象的引用。
判断该对象是否存在,如果没有则创建。
单例写法。
非线程安全的单例
/**
* 非线程安全的单例
*
* @return
*/
+ (instancetype)defaultPerson
{
//创建一个全局变量,以保存对象的引用
static Person *person = nil;
if (person == nil) {
person = [[self alloc]init];
}
return person;
}
线程安全单例写法1 - 加了一个互斥锁
+ (instancetype)defaultPerson
{
static Person *person = nil;
@synchronized(self) {
if (person == nil) {
person = [[self alloc]init];
}
}
return person;
}
线程安全单例写法2
+ (void)initialize
{
static Person *person = nil;
if([self class] == [Person class])
{
person = [[Person alloc]init];
}
}
线程安全单例写法3 -- GCD,苹果推荐的
+ (instancetype)defaultPerson
{
static Person *person = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
person = [[self alloc]init];
});
return person;
}
19 load方法和initialize方法的区别
load方法:
当类被引用进程序的时候会执行这个函数
一个类的load方法不用写明[super load],父类就会收到调用,并且在子类之前。
Category的load也会收到调用,但顺序上在主类的load调用之后。
initialize方法:
initialize的自然调用是在第一次主动使用当前类的时候
和load不同,即使子类不实现initialize方法,会把父类的实现继承过来调用一遍。注意的是在此之前,父类的方法已经被执行过一次了,同样不需要super调用。
load和initialize有很多共同特点,下面简单列一下:
在不考虑开发者主动使用的情况下,系统最多会调用一次
如果父类和子类都被调用,父类的调用一定在子类之前
都是为了应用运行提前创建合适的运行环境
在使用时都不要过重地依赖于这两个方法,除非真正必要
20 简述类目Category优点和缺点
优点
不要要增加子类就可以给现有类增加方法。
通过类目可以对一个类的方法进行划分,有益于代码的维护,管理,提高代码的可阅读性。
如果类目中的方法名称和原有类的方法名字一样,类目中的方法优先级更高,系统会优先调用类目中的方法。
缺点
无法向类目添加实例变量,如果需要添加实例变量,只能通过定义子类的方式;
类目中的方法与原始类以及父类方法相比具有更高优先级,如果覆盖父类的方法,可能导致super消息的断裂。因此, 最好不要覆盖原始类中的方法。
21 循环引用的产生原因,以及解决方法
比如两个对象A和B,他们相互都持有对方作为自己的成员变量,只有自己销毁的时候,对象的引用计数才能减1,对象A依赖于对象B的销毁,对象B依赖于对象A的销毁,这样就造成了循环引用,
解决方法 - 把其中一个对象使用__weak修饰,使其中一个对象编程弱对象。
22 键值编码KVC
KVC全称key valued coding 键值编码
提到KVC,就不能不提反射机制,反射机制就是在运行状态中,对于任意一个类,都能够调用他的所有属性和方法,对于任意一个对象,都能够调用他的任意一个方法和属性,java和C#都有,ObjC也有,所以你根本不必进行任何操作就可以进行属性的动态读写,这就是KVC.KVC的操作方法基本上都由NSKeyValueCoding提供,而他是NSObject的类别,也就是说所以的对象都支持KVC操作。
使用KVC间接修改对象属性时,系统会自动判断对象属性的类型,并完成转换。
KVC可以访问成员变量,无论是否提供get,set方法,无论可见性咋样,是否有readonly修饰。
KVC的主要作用dict和model之间的互转
字典转模型
[self setValuesForKeysWithDictionary:dict];
模型转字典
[p dictionaryWithValuesForKeys:array];
使用KVC计算属性
举例:[p valueForKeyPath:@"[email protected]"];
@avg:平均值
@count:总数
@max:最大
@min:最小
@sum:总数
23 键值观察KVO
键值观察机制是一种能够使对象获取到其它对象属性变化的通知,极大的简化了代码。
实现KVO键值观察模式,被观察的对象必须使用KVC键值编码来修改他的实例变量,这样才可以被观察者观察到,所以说KVC是KVO的基础。
KVC,KVO原理剖析
24 协议
概念:定义了一个接口,其他类负责实现这个接口,如果你的类实现了一个协议的方法时,则说明该类遵循此协议
非正式协议:NSObject的类别
协议的修饰符:@required 表示这个方法是必须实现的,@optional,这个表示这个方法是可以实现也可以不实现的,
conformsToProtocol方法--监测一个方法是否遵循某个协议
协议是无类型的,一个协议可以被多个类使用,一个类可以遵循多个协议
25 委托
代理又叫委托,是一种设计模式,代理是对象与对象之间的通讯,代理解除了对象之间的耦合性。
代理可以理解为JAVA重的回调监听机制。
委托步骤
定义协议
创建委托对象,委托对象必须实现委托协议
实现协议方法
26 NSNotification、Block、代理、和KVO的区别。
代理是一种回调机制,且是一对一的关系,通知是一对多。
Delegate比NSNotification效率高
代理和Block一般是一对一的通信
代理需要定义协议方法,代理对象需要实现协议方法,并且需要建立代理关系
Block比较简洁,不需要定义繁琐的协议方法,如果通信事件比较多的话,建议使用代理
27 当我们调用一个静态方法时,需要对对象进行release吗??
不需要,静态方法(类方法)创建一个对象时,对象已经被放入自动释放池中
28 static self super关键字的作用
函数体内static变量的作用范围是函数体,该变量只分配一次内存,只初始化一次。
在类中satic成员变量属于整个类所拥有,对类的所有对象只有一份拷贝
self 当前消息的接受者
super 向父类发送消息
29 iOS数据持久化有几种??
属性列表:plist文件
归档:归档通过序列化的形式,键值关系存储到本地,转为二进制流,通过runtime实现自动化归档和解档
遵循NSCodeing协议,需要实现encodeWithCoder和initWithCoder方法,如果是子类继承父类的话,需要实现父类的归档和解档方法
如果需要归档的属性过多,可以使用runtime来进行归档
SQlite数据库-一种关系型数据库
CoreData:通过管理对象进行、增、删、改、查的,他的底层其实还是SQL语句,但是他却是一个对象型数据库
30 CoreData
CoreData介绍
CoreData是面向对象的API,CoreData是iOS中非常重要的一项技术,几乎在所有编写的程序中,CoraData也作为数据存储的基础。
他是苹果官方提供的一套框架,用来解决与对象声明周期管理,对象关系管理和持久化等问题
他提供的是对象-关系映射功能,可以将OC对象转换成数据,保存到SQL中,然后将保存后的数据还原成OC对象。
CoreData的特征
通过CoreData管理应用程序的数据模型,可以极大程度减少需要编写的代码数量。
将对象数据存储在SQLite数据库已获得性能优化。
提供NSFetchResultsController类用于管理表视图的数据,即将Core Data的持久化存储在表视图中,并对这些数据进行管理:增删查改。
管理undo/redo操纵;
检查托管对象的属性值是否正确。
CoreData的6成员对象
NSManageObject:被管理的数据记录Managed Object Model是描述应用程序的数据模型,这个模型包含实体(Entity)、特性(Property)、读取请求(Fetch Request)等。
NSManageObjectContext:管理对象上下文,持久性存储模型对象,参与数据对象进行各种操作的全过程,并监测数据对象的变化,以提供对undo/redo的支持及更新绑定到数据的UI。
NSPersistentStoreCoordinator:连接数据库的Persistent Store Coordinator相当于数据文件管理器,处理底层的对数据文件的读取和写入,一般我们与这个没有交集。
NSManagedObjectModel:被管理的数据模型、数据结构。
NSFetchRequest:数据请求;
NSEntityDescription:表格实体结构,还需知道.xcdatamodel文件编译后为.momd或者.mom文件。
CoreData的功能
对于KVC和KVO完整且自动化的支持,除了为属性整合KVO和KVC访问方法外,还整合了适当的集合访问方法来处理多值关系;
自动验证属性(property)值;
支持跟踪修改和撤销操作;
关系维护,Core Data管理数据的关系传播,包括维护对象间的一致性;
在内存上和界面上分组、过滤、组织数据;
自动支持对象存储在外部数据仓库的功能;
创建复杂请求:无需动手写SQL语句,在获取请求(fetch request)中关联NSPredicate。NSPreadicate支持基本功能、相关子查询和其他高级的SQL特性。它支持正确的Unicode编码、区域感知查询、排序和正则表达式;
延迟操作:Core Data使用懒加载(lazy loading)方式减少内存负载,还支持部分实体化延迟加载和复制对象的数据共享机制;
合并策略:Core Data内置版本跟踪和乐观锁(optimistic locking)来支持多用户写入冲突的解决,其中,乐观锁就是对数据冲突进行检测,若冲突就返回冲突的信息;
数据迁移:Core Data的Schema Migration工具可以简化应对数据库结构变化的任务,在某些情况允许你执行高效率的数据库原地迁移工作;
可选择针对程序Controller层的集成,来支持UI的显示同步Core Data在IPhone OS之上,提供NSFetchedResultsController对象来做相关工作,在Mac OS X上我们用Cocoa提供的绑定(Binding)机制来完成的。
31 Runloop
想深入了解Runloop,点这里
32 解释self = [super init]方法
容错处理,当父类初始化失败,会返回一个nil,表示初始化失败,由于继承的关系,子类是要得到父类的实例和行为,因此我们必须先初始化父类,然后初始化子类
33 栈和堆的区别
栈区(stack)由编译器自动分配释放 ,存放方法(函数)的参数值, 局部变量的值等,栈是向低地址扩展的数据结构,是一块连续的内存的区域。即栈顶的地址和栈的最大容量是系统预先规定好的。
堆区(heap)一般由程序员分配释放, 若程序员不释放,程序结束时由OS回收,向高地址扩展的数据结构,是不连续的内存区域,从而堆获得的空间比较灵活。
碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出.
分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的。
全局区(静态区)(static),全局变量和静态变量的存储是放在一块 的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后有系统释放。
文字常量区—常量字符串就是放在这里的。程序结束后由系统释放。
程序代码区—存放函数体的二进制代码
34 cell重用问题
UITableView通过重用单元格来达到节省内存的目的,通过为每个单元格指定一个重用标示(reuseidentifier),即指定了单元格的种类,以及当单元格滚出屏幕时,允许恢复单元格以便复用。对于不同种类的单元格使用不同的ID,对于简单的表格,一个标示符就够了。
如一个TableView中有10个单元格,但屏幕最多显示4个,实际上iPhone只为其分配4个单元格的内存,没有分配10个,当滚动单元格时,屏幕内显示的单元格重复使用这4个内存。实际上分配的cell的个数为屏幕最大显示数,当有新的cell进入屏幕时,会随机调用已经滚出屏幕的Cell所占的内存,这就是Cell的重用。
35 使用Block有什么好处?使用NSTimer写一个使用Block显示
Block传值和回调相比代理方便,而且操作也简单,省了很多代码
NSTimer封装的Block,具体实现