iOS面试题汇总一
面试题汇总
@Property
负责生成setter/getter方法的声明
@Synthesize
在 .m 生成 @synthesize 属性名 = _属性名,相当于起一个别名
@dynamic
@dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成,
如果没有实现setter/getter 方法,在self.xxxx = xxxx 进行赋值时,会crash。
@property (assign/retain/strong/weak/unsafe_unretained/copy) Number* num
assign表明 setter 仅仅是一个简单的赋值操作,通常用于基本的数值类型,例如CGFloat和NSInteger。
strong 表明属性定义一个拥有者关系。当给属性设定一个新值的时候,首先这个值进行 retain ,旧值进行 release ,然后进行赋值操作。
weak 表明属性定义了一个非拥有者关系。当给属性设定一个新值的时候,这个值不会进行 retain,旧值也不会进行 release, 而是进行类似 assign 的操作。不过当属性指向的对象被销毁时,该属性会被置为nil。
unsafe_unretained 的语义和 assign 类似,不过是用于对象类型的,表示一个非拥有(unretained)的,同时也不会在对象被销毁时置为nil的(unsafe)关系。
copy 类似于 strong,不过在赋值时进行 copy 操作而不是 retain 操作。通常在需要保留某个不可变对象(NSString最常见),并且防止它被意外改变时使用。
#define, const, static, extern
#define与const都可用来修饰常量。
编译器处理方式不同
* define宏是在预处理阶段展开。
* const常量是编译运行阶段使用。
类型和安全检查不同
* define宏没有类型,不做任何类型检查,仅仅是展开。
* const常量有具体的类型,在编译阶段会执行类型检查。
存储方式不同
* define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。(宏定义不分配内存,变量定义分配内存。)
* const常量会在内存中分配(可以是堆中也可以是栈中)。
* const可以节省空间,避免不必要的内存分配。
提高了效率;编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
宏替换只作替换,不做计算,不做表达式求解;
a,b 两个子线程,两个总结束时间小于3秒 执行a1 方法,反之 a 2
//创建调度组
dispatch_group_t group = dispatch_group_create();
//获取全局队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
__block int count = 0;
//调度组的异步请求
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:1];
count ++;
NSLog(@"下载第一张图");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:1];
count ++;
NSLog(@"下载第二张图");
});
dispatch_time_t time = dispatch_time(0, 3*NSEC_PER_SEC);
dispatch_group_wait(group, time);
if (count < 2)
{
NSLog(@"---没有执行完--%d", count);
} else
{
dispatch_group_notify(group, queue, ^{
NSLog(@"---小于3秒执行完--");
});
}
https连接过程
* 客户端发起HTTPS请求
这个没什么好说的,就是用户在浏览器里输入一个https网址,然后连接到server的443端口。
* 服务端的配置
采用HTTPS协议的服务器必须要有一套数字证书,可以自己制作,也可以向组织申请。
区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,
而使用受信任的公司申请的证书则不会弹出提示页面(startssl就是个不错的选择,
有1年的免费服务)。这套证书其实就是一对公钥和私钥。如果对公钥和私钥不太理解,可以想象成一
把钥匙和一个锁头,只是全世界只有你一个人有这把钥匙,你可以把锁头给别人,别人可以用这个锁
把重要的东西锁起来,然后发给你,因为只有你一个人有这把钥匙,所以只有你才能看到被这把锁锁
起来的东西。
* 传送证书
这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间等等。
* 客户端解析证书
这部分工作是有客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,
如果发现异常,则会弹出一个警告框,提示证书存在问题。如果证书没有问题,那么就生成一个随即
值。然后用证书对该随机值进行加密。就好像上面说的,把随机值用锁头锁起来,这样除非有钥匙,
不然看不到被锁住的内容。
* 传送加密信息
这部分传送的是用证书加密后的随机值,目的就是让服务端得到这个随机值,以后客户端和服务端的
通信就可以通过这个随机值来进行加密解密了。
* 服务段解密信息
服务端用私钥解密后,得到了客户端传过来的随机值(私钥),然后把内容通过该值进行对称加密。
所谓对称加密就是,将信息和私钥通过某种算法混合在一起,这样除非知道私钥,不然无法获取内
容,而正好客户端和服务端都知道这个私钥,所以只要加密算法够彪悍,私钥够复杂,数据就够安
全。
* 传输加密后的信息
这部分信息是服务段用私钥加密后的信息,可以在客户端被还原
客户端解密信息
客户端用之前生成的私钥解密服务段传过来的信息,于是获取了解密后的内容。整个过程第三方即使
监听到了数据,也束手无策。
xib拖控件为什么用weak,
ViewController 强引View,view 强引Subviews,新增控件时,把控件以强引加入subviews,
在removeFromSuperView时,把控件移除,weak时置nil,若是strong,从父类移除,但依然被view强引。
NSString *b = @"12345"; 存哪儿
image.png- 栈区 0x7
创建临时变量时由编译器自动分配,在不需要的时候自动清除的变量的存储区。
里面的变量通常是局部变量、函数参数等。在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调用。和堆一样,用户栈在程序执行期间可以动态地扩展和收缩。
栈是向低地址扩展的数据结构,是一块连续的内存区域
- 堆区 0x6
那些由 new alloc 创建的对象所分配的内存块,它们的释放系统不会主动去管,由我们的开发者去告诉系统什么时候释放这块内存(一个对象引用计数为0是系统就会回销毁该内存区域对象)。一般一个 new 就要对应一个 release。在ARC下编译器会自动在合适位置为OC对象添加release操作。会在当前线程Runloop退出或休眠时销毁这些对象,MRC则需程序员手动释放。
堆可以动态地扩展和收缩,是向高地址扩展的数据结构,是不连续的内存区域
- 静态区(未初始化数据).bss
程序运行过程内存的数据一直存在,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域
程序结束后由系统释放
- 常量区(已初始化数据).data
专门用于存放常量,程序结束后由系统释放
- 代码区
用于存放函数体的二进制代码,代码会被编译成二进制存进内存的程序代码区
什么时候用栈,堆?解释栈溢出
栈容易溢出是因为栈内存有限,一般就几兆。
栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致与其相邻的栈中的变量的值被改变。
这种问题是一种特定的缓冲区溢出漏,类似的还有堆溢出,bss 段溢出等溢出方式。
发生栈溢出的基本前提是:
程序必须向栈上写入数据。
写入的数据大小没有被良好地控制。
常见的溢出:递归。
哈希存储、哈希表原理
哈希存储的基本思想是以关键字Key为自变量,通过一定的函数关系(散列函数或哈希函数),
计算出对应的函数值(哈希地址),以这个值作为数据元素的地址,并将数据元素存入到相应地址的存储单元中。
查找时再根据要查找的关键字采用同样的函数计算出哈希地址,然后直接到相应的存储单元中去取要找的数据元素即可。
哈希表(hash table)是实现字典操作的一种有效的数据结构。
- 建立哈希表操作步骤
-
step1 取数据元素的关键字key,计算其哈希函数值(地址)。若该地址对应的存储空间还没有被占用,则将该元素存入;否则执行step2解决冲突。
-
step2 根据选择的冲突处理方法,计算关键字key的下一个存储地址。若下一个存储地址仍被占用,则继续执行step2,直到找到能用的存储地址为止。
- 哈希冲突
我们知道影响哈希查找效率的一个重要因素是哈希函数本身。当两个不同的数据元素的哈希值相同时,就会发生冲突。
为减少发生冲突的可能性,哈希函数应该将数据尽可能分散地映射到哈希表的每一个表项中。
解决冲突的方法有以下两种:
(1) 开放地址法
如果两个数据元素的哈希值相同,则在哈希表中为后插入的数据元素另外选择一个表项。
①.线性探测法
②.二次探测法
③.双哈希函数探测法
(2) 链地址法
将哈希值相同的数据元素存放在一个链表中,在查找哈希表的过程中,当查找到这个链表时,必须采用线性查找方法。
MD5算法底层原理:
简单概括起来,MD5算法的过程分为四步:处理原文,设置初始值,循环加工,拼接结果。
NSCache 与 NSMutableDictionary
NSCache 基本上就是一个会自动移除对象来释放内存的 NSMutableDictionary。
无需响应内存警告或者使用计时器来清除缓存。唯一的不同之处是键对象不会像 NSMutableDictionary 中那样被复制,
这实际上是它的一个优点(键不需要实现 NSCopying 协议)。
cost 被用来计算缓存中所有对象的代价。当内存受限或者所有缓存对象的总代价超过了最大允许的值时,缓存会移除其中的一些对象。
KVC
KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态地访问和修改对象的属性。而不是在编译时确定,这也是iOS开发中的黑魔法之一。很多高级的iOS开发技巧都是基于KVC实现的。
KVC要设值,那么就要对象中对应的key,KVC在内部是按什么样的顺序来寻找key的。当调用setValue:属性值 forKey:@”name“的代码时,底层的执行机制如下:
程序优先调用set:属性值方法,代码通过setter方法完成设置。注意,这里的是指成员变量名,首字母大小写要符合KVC的命名规则,下同
如果没有找到setName:方法,KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly方法有没有返回YES,默认该方法会返回YES,
如果你重写了该方法让其返回NO的话,那么在这一步KVC会执行setValue:forUndefinedKey:方法,不过一般开发者不会这么做。
所以KVC机制会搜索该类里面有没有名为_的成员变量,无论该变量是在类接口处定义,还是在类实现处定义,也无论用了什么样的访问修饰符,
只在存在以_命名的变量,KVC都可以对该成员变量赋值。
如果该类即没有set:方法,也没有_成员变量,KVC机制会搜索_is的成员变量。
和上面一样,如果该类即没有set:方法,也没有_和_is成员变量,KVC机制再会继续搜索和is的成员变量。再给它们赋值。
如果上面列出的方法或者成员变量都不存在,系统将会执行该对象的setValue:forUndefinedKey:方法,默认是抛出异常。
如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员并进行赋值操作。
KVC使用:
(1)动态地取值和设值
(2)用KVC来访问和修改私有变量
(3)Model和字典转换
(4)修改一些控件的内部属性
(5)操作集合
(7)用KVC实现高阶消息传递
改self.view怎么做,需要 loadView 调父类吗
实现loadView 方法,并给self.view赋值
- (void)loadView {
}
不调用父类。
判断两个单链表交叉
遍历两个链表的节点,判断节点相等的点,不相等取出下一个节点给p1/p2赋值。
ListNode* noLoop(ListNode* head1, ListNode* head2)
{
ListNode* p1 = head1;
ListNode* p2 = head2;
while (p1 != p2)
{
p1 = p1 != NULL ? p1->next : head1;
p2 = p2 != NULL ? p2->next : head2;
}
return p1;
}
catagroy 监控不到的崩溃
分析下面代码
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 10000; i++)
{
dispatch_async(queue, ^{
NSLog(@"%d", i);
});
}
异步执行,i 的输出是无序的,开启大量线程,CPU负载很高。
UIWebView 和 WKWebView 的 API 区别
load, initial 区别
load函数调用特点如下:
当类被引用进项目的时候就会执行load函数(在main函数开始执行之前),与这个类是否被用到无关,每个类的load函数只会自动调用一次.
由于load函数是系统自动加载的,因此不需要调用父类的load函数,否则父类的load函数会多次执行。
1.当父类和子类都实现load函数时,父类的load方法执行顺序要优先于子类
2.当子类未实现load方法时,不会调用父类load方法
3.类中的load方法执行顺序要优先于类别(Category)
4.当有多个类别(Category)都实现了load方法,这几个load方法都会执行,但执行顺序不确定(其执行顺序与类别在Compile Sources中出现的顺序一致)
5.当然当有多个不同的类的时候,每个类load 执行顺序与其在Compile Sources出现的顺序一致
注意: load调用时机比较早,当load调用时,其他类可能还没加载完成,运行环境不安全. load方法是线程安全的,它使用了锁,我们应该避免线程阻塞在load方法.
initialize函数调用特点如下:
initialize在类或者其子类的第一个方法被调用前调用。即使类文件被引用进项目,但是没有使用,initialize不会被调用。
由于是系统自动调用,也不需要再调用 [super initialize] ,否则父类的initialize会被多次执行。
假如这个类放到代码中,而这段代码并没有被执行,这个函数是不会被执行的。
1.父类的initialize方法会比子类先执行
2.当子类未实现initialize方法时,会调用父类initialize方法,子类实现initialize方法时,会覆盖父类initialize方法.
3.当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category 的initialize方法)
注意: 在initialize方法收到调用时,运行环境基本健全。 initialize内部也使用了锁,所以是线程安全的。但同时要避免阻塞线程,不要再使用锁
catagorty 原理,重写同名方法
分类的实现原理是将category中的方法,属性,协议数据放在category_t结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。
category是 Objective-C 2.0 之后添加的语言特性,主要作用是为已经存在的类添加方法。
除此之外,Apple 还推荐了category 的另外两个使用场景。
-
可以把类的实现分开在几个不同的文件里面。这样做有几个显而易见的好处:
(1)可以减少单个文件的体积
(2)可以把不同的功能组织到不同的 category 里
(3)可以由多个开发者共同完成一个类
(4)可以按需加载想要的 category 等等 -
声明私有方法
-
除了apple推荐的使用场景,还衍生出了 category 的其他几个使用场景:
(1)模拟多继承
(2)把framework的私有方法公开 -
category特点
(1)category 只能给某个已有的类扩充方法,不能扩充成员变量
(2)category 中也可以添加属性,只不过 @property 只会生成 setter 和 getter 的声明,不会生成 setter 和 getter 的实现以及成员变量
(3)如果 category 中的方法和类中原有方法同名,category 中的方法会覆盖掉类中原有的方法
(4)如果多个 category 中存在同名的方法,运行时到底调用哪个方法由编译器决定(Compile Sources中添加顺序有关),后面参与编译的方法会覆盖前面同名的方法,所以最后一个参与编译的方法会被调用
多线程
图像绘制
聊天数据结构,时效,有效性
三方库原理
__block NSString *temp = @"abcd";
= ^{
}
- 字符翻转
NSMutableArray *temp = @[@"a", @"b", @"c", @"d", @"e", @"a", @"d"].mutableCopy;
NSInteger first = 0;
NSInteger last = temp.count-1;
while (first <= last) {
id tmp = temp[first];
temp[first] = temp[last];
temp[last] = tmp;
first ++;
last --;
}
NSLog(@"%@", temp);
- 100000个数中,找五个大的
100000 个数字组成的无序数组, 取出最大的 5 个数,
分成100份,对每个数组排序,取每个中最大的 5 个, 组成一个数组,再进行排序,找出最大的 5 个,用冒泡执行5次即可。