iOS 常见面试题整理一
1、使用NSTimer,需要注意什么?
这里按我的理解就是,主要是涉及runloop了。 一是拖动scrollView的时候,runloop处于EventTracking模式,创建的计时器默认是添加在defalut下,而runloop 有一个特点只会处理当前mode下的输入源事件和计时器事件, 所以当scrollView滚动的时候,计时器的事件会导致无法触发,这种情况下,只需需将计时器添加到当前runloop下的common mode下即可;二是,子线程的计时器事件,需要主动添加这句[[NSRunLoop currentRunLoop] run]才能触发, 子线程的运行循环默认是没有开启的
2、self.name = nil 和[_name release]的区别
前者使用property的点操作符,也就相当于调用了name对应的set method,和这句是一样的:[self setName:nil];
而后者没有通过property,直接访问了成员变量,调用了它的release方法。
对于set method来说,用synthesize来让系统帮我们生成的set方法和如下的类似:
- (void)setAbc:(id)newAbc
{
if(_abc != newAbc){
[_abc release];
_abc = [newAbc retain]; //是retain还是copy取决于你property声明时的attributes
}
}
如果新值和成员相等,就不需要进行重复的赋值了,不等的话,需要把新值赋给成员,同时,成员_abc原来的内容就不需要了,这里要先调用release进行释放。(这个具体的原因在那本讲Objective-C的书中写的很清楚,请查看)。
因此在这里,调用self.abc = nil;
就等于掉了[_abc release]; 和_abc = nil;
self.abc = nil;和[_abc release]; 都不一定释放对象,因为该对象还可能被别的引用,这里的操作的意图就是:别的地方用没用_abc我不知道,在这里的_abc我不用了。
在用retain或者copy的property attributes的时候,self.abc = nil;等于如下语句:
if(_abc!= nil)
{
[_abc release];
_abc = nil; //message sent to nil returns nil.
}
所以用起来更简单一点。
3、@property中一些属性关键字的作用, 默认下分别是哪些关键字?
strong 释放旧对象将旧对象的值赋予输入对象,再提高输入对象的索引计数为1,此关键字经常使用。
weak 不增加引用计数,不持有对象,因此也不能决定对象释放 对比assign 的一个好处是,当对象消失时指针自动归为nil
assign 适用于基础数据类型(NSInteger CGFloat...)不增加引用计数
copy 建立一个索引计数为1 的对象然后释放旧对象 此属性只对那些实行了NSCopying协议的对象类型有效(NSString , Block)
atomic 和 nonatomic用来决定编译器生成的getter和setter是否为原子操作,atomic 设置成员变量的@property属性时 默认为是atomic 提供线程安全。
nonatomic 非原子性访问对于属性赋值的时候不加锁,多线程并发访问会提高性能,如果不加此属性则默认是两个访问方法都为原子型事务访问。
readonly 此标记说明属性是只读的
readwrite 此标记说明属性会被当成读写的 这也是默认的属性
unsafe_unretained 跟weak类似,声明一个弱引用,但是当引用计数为0时,变量不会自动设置为nil(ios5加入的 我基本没用过)
3.1、属性关键字对比
copy : strong
1.copy建立一个同样的对象 比如一个NSString 地址为0x1212 内容 @“121” copy到另一个NSString上 地址改变 而内容不变 新对象retain +1 就对象不变 也就是说copy时内容拷贝 而 strong不是他只是单纯的生命一个变量 retain为1
2.strong的set方法时浅拷贝 copy的set为深拷贝
assign : weak
在MRC环境下使用使用assign实现基本类型. 在ARC环境下,weak相当于assign 个人感觉weak比assign要强大的地方在 weak可以避免循环引用 , 同时当对象不存在时候可以将其置为nil
4、sql语句常用的增删改查
5、KVO的内部实现
KVO是基于runtime机制实现的
当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制
如果原类为Person,那么生成的派生类名为NSKVONotifying_Person
每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
补充:KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类
KVC是OC特有的,本质是在运行时动态的给对象发送setValue:forKey 消息,设置数值 -调用super.init 保证对象已经被创建完成 .当给对象发送setValue:forKey 消息时要判断对象是否存在key所对应的属性,直接赋值 如果没有就调用undefinedKey(默认崩溃,需要重写) setValue:forKey的调用顺序首先会寻找set方法,如果没有就去找__isis顺序寻找,如何还没找到就调用undefinedKey(默认崩溃,需要重写). ValueForKey的调用顺序 按照get,is顺序寻找,如果没有找到按照__isis顺序寻找,如何还没找到就调用undefinedKey(默认崩溃,需要重写).
6、category 和 extension 的区别
分类有名字,类扩展没有分类名字,是一种特殊的分类
分类只能扩展方法(属性仅仅是声明,并没真正实现),类扩展可以扩展属性、成员变量和方法
7、define 和 const常量有什么区别?
define在预处理阶段进行替换,const常量在编译阶段使用
宏不做类型检查,仅仅进行替换,const常量有数据类型,会执行类型检查
define不能调试,const常量可以调试
define定义的常量在替换后运行过程中会不断地占用内存,而const定义的常量存储在数据段只有一份copy,效率更高
define可以定义一些简单的函数,const不可以
8、block和weak修饰符的区别?
__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,也可以修饰基本数据类型
__weak只能在ARC模式下使用,只能修饰对象(NSString),不能修饰基本数据类型
block修饰的对象可以在block中被重新赋值,weak修饰的对象不可以
9、static关键字的作用
函数(方法)体内 static 变量的作用范围为该函数体,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明 它的模块内;
在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的static 成员变量
10、堆和栈的区别
从管理方式来讲
对于栈来讲,是由编译器自动管理,无需我们手工控制;
对于堆来说,释放工作由程序员控制,容易产生内存泄露(memory leak)
从申请大小大小方面讲
栈空间比较小
堆控件比较大
从数据存储方面来讲
栈空间中一般存储基本类型,对象的地址
堆空间一般存放对象本身,block的copy等
11、进程和线程的区别和联系
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
12、谈谈instancetype和id的异同
1、相同点
都可以作为方法的返回类型
2、不同点
①instancetype可以返回和方法所在类相同类型的对象,id只能返回未知类型的对象;②instancetype只能作为返回值,不能像id那样作为参数
13、isKindOfClass和isMemberOfClass的区别
isKindOfClass来确定一个对象是否是一个类的成员,或者是派生自该类的成员
14、Core Data是数据库么?有哪些重要的类?
Core Data确实不是一个数据库,只是把表和OC对象进行的映射,当时并不是进进映射那么简单,底层还是用的Sqlite3进行存储的,所以Core Data不是数据库。
有以下6个重要的类:
(1)NSManagedObjectContext(被管理的数据上下文)
操作实际内容(操作持久层)
作用:插入数据,查询数据,删除数据
(2)NSManagedObjectModel(被管理的数据模型)
数据库所有表格或数据结构,包含各实体的定义信息
作用:添加实体的属性,建立属性之间的关系
操作方法:视图编辑器,或代码
(3)NSPersistentStoreCoordinator(持久化存储助理)
相当于数据库的连接器
作用:设置数据存储的名字,位置,存储方式,和存储时机
(4)NSManagedObject(被管理的数据记录)
相当于数据库中的表格记录
(5)NSFetchRequest(获取数据的请求)
相当于查询语句
(6)NSEntityDescription(实体结构)
相当于表格结构
15、算法题
二分查找 θ(logn)
递归方法
int binarySearch1(int a[] , int low , int high , int findNum)
{
int mid = ( low + high ) / 2;
if (low > high)
return -1;
else
{
if (a[mid] > findNum)
return binarySearch1(a, low, mid - 1, findNum);
else if (a[mid] < findNum)
return binarySearch1(a, mid + 1, high, findNum);
else
return mid;
}
}
非递归方法
int binarySearch2(int a[] , int low , int high , int findNum)
{
while (low <= high)
{
int mid = ( low + high) / 2; //此处一定要放在while里面
if (a[mid] < findNum)
low = mid + 1;
else if (a[mid] > findNum)
high = mid - 1;
else
return mid;
}
return -1;
}
冒泡排序 θ(n^2)
void bubble_sort(int a[], int n)
{
int i, j, temp;
for (j = 0; j < n - 1; j++)
for (i = 0; i < n - 1 - j; i++) //外层循环每循环一次就能确定出一个泡泡(最大或者最小),所以内层循环不用再计算已经排好的部分
{
if(a[i] > a[i + 1])
{
temp = a[i];
a[i] = a[i + 1];
a[i + 1] = temp;
}
}
}
快速排序 调用方法 quickSort(a,0,n); θ(nlogn)
void quickSort (int a[] , int low , int high)
{
if (high < low + 2)
return;
int start = low;
int end = high;
int temp;
while (start < end)
{
while ( ++start < high && a[start] <= a[low]);//找到第一个比a[low]数值大的位子start
while ( --end > low && a[end] >= a[low]);//找到第一个比a[low]数值小的位子end
//进行到此,a[end] < a[low] < a[start],但是物理位置上还是low < start < end,因此接下来交换a[start]和a[end],于是[low,start]这个区间里面全部比a[low]小的,[end,hight]这个区间里面全部都是比a[low]大的
if (start < end)
{
temp = a[start];
a[start]=a[end];
a[end]=temp;
}
//在GCC编译器下,该写法无法达到交换的目的,a[start] ^= a[end] ^= a[start] ^= a[end];编译器的问题
}
//进行到此,[low,end]区间里面的数都比a[low]小的,[end,higt]区间里面都是比a[low]大的,把a[low]放到中间即可
//在GCC编译器下,该写法无法达到交换的目的,a[low] ^= a[end] ^= a[low] ^= a[end];编译器的问题
temp = a[low];
a[low]=a[end];
a[end]=temp;
//现在就分成了3段了,由最初的a[low]枢纽分开的
quickSort(a, low, end);
quickSort(a, start, high);
}