目录
1.用什么方式判断gif/png图片的?
2.什么是链表,链表逆序怎么实现?
3.Swift与OC的区别?
4.Swift 中的Any 与 AnyObject的区别?
5.如何使用Swift 中的weak与unowned?
6.instancetype和id的异同
7.iOS内存泄露及检测方法
8.NSString使用copy或strong
9.AFN为什么添加一条常驻线程?
10. 控件主要响应3种事件
11. xib文件的构成分为哪3个图标?都具有什么功能。
12.UIView与CLayer有什么区别?
13. Quatrz 2D的绘图功能的三个核心概念是什么并简述其作用。
14.有哪几种手势通知方法、写清楚方法名?
15.CFSocket使用有哪几个步骤。
16. ios 平台怎么做数据的持久化?coredata 和sqlite有无必然联系?coredata是一个关系型数据库吗?
17.OC的理解与特性
18. Object C中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么?
19.Objective-C 中是否支持垃圾回收机制?
20.循环引用的产生原因,以及解决方法
21.switch 语句 if 语句区别与联系
22.如何查找某一导航栈中是否有某一个特定VC,isMemberOfClass 和 isKindOfClass 联系与区别
23.在多人合作开发环境下,可能会有重复添加KVO的时候,或者重复移除KVO的时候,重复添加或删除KVO会怎么样?采取什么方案去解决?
24.如果让你去设计SDWebImage的缓存机制,怎么去设计?
25.如果一个自定义的Cell可能被用在多处,每处后台返回的数据源模型(json原始数据)字段都是不同的,如果让你去设计一种Model去兼容所有的业务需求,即不管原始数据什么样都能一样进行解析赋给Cell,你怎么去设计?
26.__block 能修改外部变量根本原因是什么?
27.自动释放池中的变量什么时候释放
28.block根据内存区间分为几种类型?
29.block在什么时候会造成循环引用,如何避免循环引用以及weakSelf、strongSelf的区别。
30.通知中心添加事件监听需要注意什么?如果添加监听后忘记移除监听会有什么样的坑?通知中心是什么设计模式的实现,如何实现这种模式?postNotification方法是同步的还是异步的?
31. ios crash的原因与抓取crash日志的方法
32.OC的消息转发机制
1.用什么方式判断gif/png图片的?
取出图片数据的第一个字节, 就可以判断出图片的真实类型
//通过图片Data数据第一个字节 来获取图片扩展名
- (NSString *)contentTypeForImageData:(NSData *)data {
uint8_t c;
[data getBytes:&c length:1];
switch (c) {
case 0xFF:
return @"jpeg";
case 0x89:
return @"png";
case 0x47:
return @"gif";
case 0x49:
case 0x4D:
return @"tiff";
case 0x52:
if ([data length] < 12) {
return nil;
}
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return @"webp";
}
return nil;
}
return nil;
}
使用方法
//假设这是一个网络获取的URL
NSString *path = @"http://www.yxzoo.com/uploads/allimg/160803/1-160P3113351.jpg";
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:path]];
//调用获取图片扩展名
NSString *string = [self contentTypeForImageData:data];
//输出结果为 jpeg
NSLog(@"%@",string);
2.什么是链表,链表逆序怎么实现?
链表(Linkedlist)是一种常见的基础数据结构,是一种线性表,是一种物理存储单元上非连续、非顺序的存储结构。数据元素的逻辑顺序是通过链表中的指针链接次序实现的,但是并不会按线性的顺序存储数据,链表通常由一连串节点组成,每个节点包含任意的实例数据(datafields)和一或两个用来指向上一个/或下一个节点的位置的链接("links")。
链表的结构
链表最基本的结构是在每个节点保存数据和到下一个节点的地址,在最后一个节点保存一个特殊的结束标记。另外在一个固定的位置保存指向第一个节点的指针,有的时候也会同时储存指向最后一个节点的指针。但是也可以提前把一个节点的位置另外保存起来,然后直接访问。当然如果只是访问数据就没必要了,不如在链表上储存指向实际数据的指针。这样一般是为了访问链表中的下一个或者前一个节点。
优势:可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。而链表是一种自我指示数据类型,因为它包含指向另一个相同类型的数据的指针(链接),同时,链表允许插入和移除表上任意位置上的节点。
劣势:链表由于增加了结点的指针域,空间开销比较大;另外,链表失去了数组随机读取的优点,一般查找一个节点的时候需要从第一个节点开始每次访问下一个节点,一直访问到需要的位置。
单向链表
链表中最简单的一种是单向链表,
一个单向链表的节点被分成两个部分。它包含两个域,一个信息域和一个指针域。第一个部分保存或者显示关于节点的信息,第二个部分存储下一个节点的地址,而最后一个节点则指向一个空值。单向链表只可向一个方向遍历。
双向链表
双向链表其实是单链表的改进,当我们对单链表进行操作时,有时你要对某个结点的直接前驱进行操作时,又必须从表头开始查找。这是由单链表结点的结构所限制的。因为单链表每个结点只有一个存储直接后继结点地址的链域,那么能不能定义一个既有存储直接后继结点地址的链域,又有存储直接前驱结点地址的链域的这样一个双链域结点结构呢?这就是双向链表。
在双向链表中,结点除含有数据域外,还有两个链域,一个存储直接后继结点地址,一般称之为右链域(当此“连接”为最后一个“连接”时,指向空值或者空列表);一个存储直接前驱结点地址,一般称之为左链域(当此“连接”为第一个“连接”时,指向空值或者空列表)。
循环链表
循环链表是与单向链表一样,是一种链式的存储结构,所不同的是,循环链表的最后一个结点的指针是指向该循环链表的第一个结点或者表头结点,从而构成一个环形的链。
循环链表的运算与单链表的运算基本一致。所不同的有以下几点:
1、在建立一个循环链表时,必须使其最后一个结点的指针指向表头结点,而不是象单链表那样置为NULL。此种情况还使用于在最后一个结点后插入一个新的结点。
2、在判断是否到表尾时,是判断该结点链域的值是否是表头结点,当链域值等于表头指针时,说明已到表尾。而非象单链表那样判断链域值是否为NULL。
块状链表
块状链表本身是一个链表,但是链表储存的并不是一般的数据,而是由这些数据组成的顺序表。每一个块状链表的节点,也就是顺序表,可以被叫做一个块。
块状链表另一个特点是相对于普通链表来说节省内存,因为不用保存指向每一个数据节点的指针。
常规写法(迭代)
迭代算法效率较高,但是代码比递归算法略长。递归算法虽然代码量更少,但是难度也稍大,不细心容易写错。迭代算法的思想就是遍历链表,改变链表节点next指向,遍历完成,链表逆序也就完成了。代码如下:
struct node {
int data;
struct node* next;
};
typedef struct node* pNode;
pNode reverse(pNode head)
{
pNode current = head;
pNode next = NULL, result = NULL;
while (current != NULL) {
next = current->next;
current->next = result;
result = current;
current = next;
}
return result;
}
如果不返回值,可以传递参数改为指针的指针,直接修改链表的头结点值(如果在C++中直接传递引用更方便),可以写出下面的代码:
void reverse(pNode* headRef)
{
pNode current = *headRef;
pNode next = NULL, result = NULL;
while (current != NULL) {
next = current->next;
current->next = result;
result = current;
current = next;
}
*headRef = result;
}
进阶写法(递归)
递归写法的实现原理:假定原链表为1,2,3,4,则先逆序后面的2,3,4变为4,3,2,然后将节点1链接到已经逆序的4,3,2后面,形成4,3,2,1,完成整个链表的逆序。代码如下:
void reverseRecur(pNode* headRef)
{
if (*headRef == NULL) return;
pNode first, rest;
first = *headRef;
rest = first->next;
if (rest == NULL) return;
reverseRecur(&rest);
first->next->next = first; //注意这里不要错写成rest->next = first噢,请想想指针的指向。
first->next = NULL;
*headRef = rest; //更新头结点
}
如果使用C++的引用类型,代码会稍显简单点,代码如下:
void reverseRecur(pNode& p)
{
if (!p) return;
pNode rest = p->next;
if (!rest) return;
reverseRecur(rest);
p->next->next = p;
p->next = NULL;
p = rest;
}
3.Swift与OC的区别?
-
不用#import 头文件,取而代之的是在前面加共有或者私有的前缀。
-
少了隐式转换(据说是为了安全),比如float 跟int。
-
加入可选值类型,并且因此出现强制解析,当然也可以安全解析。
-
写法上更加的偏向extension (扩展)
-
常量和变量
- Swift常量使用let声明;变量使用var声明
- Swift对常量和变量有类型推断的机制
- Swift对变量新增了可选类型,可选即表示这个变量要么有值,要么为nil
-
函数
- Swift一行代码不用写分号
- Swift的返回值可以使用元组返回多个值
- Swift的函数参数可以设置缺省值
- Swift的函数参数有内外标签
- Swift的函数可以嵌套函数
- Swift子类覆盖父类的方法必须使用关键字override
-
关键字、保留字、数据类型
- nil:OC中nil只能修饰NSObject及其子类对象,表示OC对象指针为空;Swift中nil可以修饰所有类型,包括基础数据类型,表示值缺失
- switch:1. Swift中switch语句的值可以是字符串等值 2.Swift中switch不用break,如果想实现几个值贯穿可以使用关键字fall through
- 数组:Swift的数据可以存储基础类型数据;NSArray只能使用NSNumber存储基础数据类型数据
- 布尔类型:Swift的Bool类型true才为真;OC里BOOL类型非0即为真
- 取余:Swift可以对浮点型数据取余(Swift3.0后使用函数对浮点型数据取余;%与OC中保持一致)
- Swift中对变量取别名使用typealias;OC中使用typedef
-
Swift新增关键字、运算符
- 范围运算符:
a...b表示[a, b],及a<= value <=b
a..<b表示[a, b),及a<= value <b - 元组
- 元组(tuples)把多个值组合成一个复合值。元组内的值可以使任意类型,并不要求是相同类型。
- 范围运算符:
4.Swift 中的Any 与 AnyObject的区别?
AnyObject:可以代表任何class类型的实例;
Any:可以代表任何类型,甚至包括方法(func)类型。
Any和AnyObject都是协议而且,并且从Apple提供的注释中可以看出所有的type(类型)都隐式实现了Any协议,所有的class都隐式实现了AnyObject协议。
可以总结为:
- AnyObject是Any的子集
- 所有用class关键字定义的对象就是AnyObject
- 所有不是用class关键字定义的对象就不是AnyObject,而是Any
5.如何使用Swift 中的weak与unowned?
weak
含义:weak 即弱引用,当把一个实例声明为弱引用时,此实例不会持有这个对象,即不会使对象的引用计数加1。当对象被废弃,其所有的弱引用会被置为 nil。
适用场景:
- 类实例之间的循环强引用:实例之间形成引用循环,且无法确定对象之间生命周期的依赖关系,即无法确定弱引用在某一时刻是否为空时,将可能为空的实例声明为弱引用。由于弱引用可能为 nil,应当声明为可选值。如;
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person? //公寓的房客可能为空所以声明为weak
deinit { print("Apartment \(unit) is being deinitialized") }
}
由于 tenant 是弱引用,当 tenant 引用的对象被销毁(如赋值 nil),tenant 被置为空,并且释放对 apartment的强引用,此时 apartment 所指对象就可以正常释放了。
- 闭包引起的循环强引用:在被捕获的引用可能会变为nil时,在捕获列表中,将闭包内的捕获定义为弱引用。
如:
// someClosure 是类成员变量
lazy var someClosure: (Int, String) -> String = {
//self.delegate 可能在被捕获后变为 nil,所以定义为弱引用,unowned 解释见下文
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// 这里是闭包的函数体
}
由于 self.delegate 指向的是外部对象,生命周期与self无关,所以可能在被捕获后变为nil。(delegate 一般都声明为weak以避免循环引用)
unowned
含义:无主引用,与弱引用一样,当把一个实例声明为无主引用时,此实例不会持有这个对象,即不会使对象的引用计数加1。但与弱引用不同的是,当对象被废弃,其无主引用并不会被置为 nil。
适用场景:
- 类实例之间的循环强引用:实例之间形成引用循环,且可以确定某一实例之外的其他实例有相同或者更长的生命周期时,将此实例声明为无主引用。无主引用总被期望拥有值,当你访问对象被销毁的无主引用时,会触发运行时错误。
class Country {
let name: String
var capitalCity: City! //由于 capitalCity 的生命周期等于 country 的生命周期,可以隐式解析可选属性
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
虽然在这个例子中,capitalCity 与 country 的生命周期相同,理论上讲将其中任何一个声明为无主引用都可以打破引用循环,但 capitalCity 与 country 之间有从属关系,所以倾向于将“大”的一方,即 country 声明为无主引用。
- 闭包引起的循环强引用:在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用。
// someClosure 是类成员变量
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// 这里是闭包的函数体
}
self 与属于 self 的成员变量 someClosure 生命周期相同,同时销毁,所以声明为无主引用。
6.instancetype和id的异同
1、相同点
都可以作为方法的返回类型
2、不同点
①instancetype可以返回和方法所在类相同类型的对象,id只能返回未知类型的对象;
②instancetype只能作为返回值,不能像id那样作为参数
7.iOS内存泄露及检测方法
内存泄漏出现:
- AFNet, //请求队列管理者 单例创建形式 防止内存泄漏
- Block循环引用
防止Block循环引用就是要防止对象之间引用的闭环出现。 - delegate循环引用问题
delegate循环引用问题比较基础,只需注意将代理属性修饰为weak即可 - NSTimer循环引用
若将cleanTimer方法调用在dealloc方法中会产生如下问题,当前类销毁执行dealloc的前提是定时器需要停止并滞空,而定时器停止并滞空的时机在当前类调用dealloc方法时,这样就造成了互相等待的场景,从而内存一直无法释放。因此需要注意cleanTimer的调用时机从而避免内存无法释放,如上的解决方案为将cleanTimer方法外漏,在外部调用即可。 - 非OC对象内存处理
- 地图类处理
若项目中使用地图相关类,一定要检测内存情况,因为地图是比较耗费App内存的,因此在根据文档实现某地图相关功能的同时,我们需要注意内存的正确释放,大体需要注意的有需在使用完毕时将地图、代理等滞空为nil,注意地图中标注(大头针)的复用,并且在使用完毕时清空标注数组等。
内存泄漏检测:
- 通过Xcode中Product->Analyze静态分析代码,找出潜在的内存泄露。
方案1的优点是不需要写代码,只需要运行一次,就能检测出潜在的内存泄露;缺点是由于是静态代码检查,无法覆盖全部场景(比如动态运行场景),有可能误报。 - 使用Xcode自带工具Instruments来检测内存泄露。
方案2的优点是不需要写代码,直接运行Instruments工具进行检测,适用于开发、测试来使用;缺点是需要一个一个页面去点击。 - 微信读书开源的内存泄露检测库MLeaksFinder,MLeaksFinder具有如下功能:
- 1、能检测出内存泄露和循环引用,并且弹框提醒。
- 2、只在Debug模式下起作用,Release不起作用。
- 3、支持检查VC和View里面任意对象的内存泄露。
8.NSString使用copy或strong
1.__NSCFConstantString
这些对象地址相同,是因为他们都是__NSCFConstantString对象,也就是字符串常量对象,可以看到其isa都是__NSCFConstantString,该对象存储在栈上,创建之后由系统来管理内存释放,相同内容的NSCFConstantString对象地址相同。该对象引用计数很大,为固定值不会变化,表示无限运行的retainCount,对其进行retain或release也不会影响其引用计数。
当创建一个NSCFConstantString对象时,会检测这个字符串内容是否已经存在,如果存在,则直接将地址赋值给变量;不存在的话,则创建新地址,再赋值。
对于NSCFConstantString对象,只要字符串内容不变,就不会分配新的内存地址,无论你是赋值、retain、copy。这种优化在大量使用NSString的情况下可以节省内存,提高性能。
2.__NSCFString
__NSCFString对象是一种NSString子类,存储在堆上,不属于字符串常量对象。该对象创建之后和其他的Obj对象一样引用计数为1,对其执行retain和release将改变其retainCount。
3.声明NSString属性一般来说用copy,因为父类指针可以指向子类对象,而NSMutableNSString是NSString的子类,使用strong的话该NSString属性可能指向一个NSMutableNSString可变对象,如果这个可变对象的内容在外部被修改了,那该属性所属的对象可能对此毫不知情。
声明NSString为属性时,如果希望保护属性封装性不受外界影响,则应该使用copy关键字,让所属对象持有的是一份“不可变”(immutable)副本,不用担心字符串内容无意间变动。
9.AFN为什么添加一条常驻线程?
主线程是UI线程 你要在这做超过16ms的事情就会卡顿,时间再长就会造成界面卡死。 一般rom对主线程卡住10s以上的应用都会ANR,让用户选择强制杀死应用。
10. 控件主要响应3种事件
答:1). 基于触摸的事件 ; 2). 基于值的事件 ; 3).基于编辑的事件。
11. xib文件的构成分为哪3个图标?都具有什么功能。
答: File’s Owner 是所有 nib 文件中的每个图标,它表示从磁盘加载 nib 文件的对象;
First Responder 就是用户当前正在与之交互的对象;
View 显示用户界面;完成用户交互;是 UIView 类或其子类。
12.UIView与CLayer有什么区别?
答:
1).UIView 是 iOS 系统中界面元素的基础,所有的界面元素都是继承自它。它本身完全是由 CoreAnimation 来实现的。它真正的绘图部分,是由一个 CALayer 类来管理。 UIView 本身更像是一个 CALayer 的管理器,访问它的跟绘图和跟坐标有关的属性。
2).UIView 有个重要属性 layer ,可以返回它的主 CALayer 实例。
3).UIView 的 CALayer 类似 UIView 的子 View 树形结构,也可以向它的 layer 上添加子layer ,来完成某些特殊的表示。即 CALayer 层是可以嵌套的。
4).UIView 的 layer 树形在系统内部,被维护着三份 copy 。分别是逻辑树,这里是代码可以操纵的;动画树,是一个中间层,系统就在这一层上更改属性,进行各种渲染操作;显示树,其内容就是当前正被显示在屏幕上得内容。
5).动画的运作:对 UIView 的 subLayer (非主 Layer )属性进行更改,系统将自动进行动画生成,动画持续时间的缺省值似乎是 0.5 秒。
6).坐标系统: CALayer 的坐标系统比 UIView 多了一个 anchorPoint 属性,使用CGPoint 结构表示,值域是 0~1 ,是个比例值。这个点是各种图形变换的坐标原点,同时会更改 layer 的 position 的位置,它的缺省值是 {0.5,0.5} ,即在 layer 的中央。
7).渲染:当更新层,改变不能立即显示在屏幕上。当所有的层都准备好时,可以调用setNeedsDisplay 方法来重绘显示。
8).变换:要在一个层中添加一个 3D 或仿射变换,可以分别设置层的 transform 或affineTransform 属性。
9).变形: Quartz Core 的渲染能力,使二维图像可以被自由操纵,就好像是三维的。图像可以在一个三维坐标系中以任意角度被旋转,缩放和倾斜。 CATransform3D 的一套方法提供了一些魔术般的变换效果。
13. Quatrz 2D的绘图功能的三个核心概念是什么并简述其作用。
答:上下文:主要用于描述图形写入哪里;
路径:是在图层上绘制的内容;
状态:用于保存配置变换的值、填充和轮廓, alpha 值等。
14.有哪几种手势通知方法、写清楚方法名?
答:
-(void)touchesBegan:(NSSet)touchedwithEvent:(UIEvent)event;
-(void)touchesMoved:(NSSet)touched withEvent:(UIEvent)event;
-(void)touchesEnded:(NSSet)touchedwithEvent:(UIEvent)event;
-(void)touchesCanceled:(NSSet)touchedwithEvent:(UIEvent)event;
15.CFSocket使用有哪几个步骤。
答:创建 Socket 的上下文;创建 Socket ;配置要访问的服务器信息;封装服务器信息;连接服务器;
16. ios 平台怎么做数据的持久化?coredata 和sqlite有无必然联系?coredata是一个关系型数据库吗?
答:iOS 中可以有四种持久化数据的方式:属性列表(plist)、对象归档、 SQLite3 和 Core Data;
1>属性列表:只有NSString、NSArray、NSDictionary、NSData可writeToFile;存储依旧是plist文件。plist文件可以存储的7中数据类型:array、dictionary、string、bool、data、date、number。
2>对象序列化(对象归档):对象序列化通过序列化的形式,键值关系存储到本地,转化成二进制流。通过runtime实现自动化归档/解档,实现NSCoding协议必须实现的两个方法:
1.编码(对象序列化):把不能直接存储到plist文件中得到数据,转化为二进制数据,NSData,可以存储到本地;
2.解码(对象反序列化):把二进制数据转化为本来的类型。
3>SQLite 数据库:大量有规律的数据使用数据库。
4>CoreData :通过管理对象进行增、删、查、改操作的。它不是一个数据库,不仅可以使用SQLite数据库来保持数据,也可以使用其他的方式来存储数据。如:XML。
core data 可以使你以图形界面的方式快速的定义 app 的数据模型,同时在你的代码中容易获取到它。 coredata 提供了基础结构去处理常用的功能,例如保存,恢复,撤销和重做,允许你在 app 中继续创建新的任务。在使用 core data 的时候,你不用安装额外的数据库系统,因为 core data 使用内置的 sqlite 数据库。 core data 将你 app 的模型层放入到一组定义在内存中的数据对象。 coredata 会追踪这些对象的改变,同时可以根据需要做相反的改变,例如用户执行撤销命令。当 core data 在对你 app 数据的改变进行保存的时候, core data 会把这些数据归档,并永久性保存。 mac os x 中sqlite 库,它是一个轻量级功能强大的关系数据引擎,也很容易嵌入到应用程序。可以在多个平台使用, sqlite 是一个轻量级的嵌入式 sql 数据库编程。与 core data 框架不同的是, sqlite 是使用程序式的, sql 的主要的 API 来直接操作数据表。 Core Data 不是一个关系型数据库,也不是关系型数据库管理系统 (RDBMS) 。虽然 Core Dta 支持SQLite 作为一种存储类型,但它不能使用任意的 SQLite 数据库。 Core Data 在使用的过程种自己创建这个数据库。 Core Data 支持对一、对多的关系。
17.OC的理解与特性
OC作为一门面向对象的语言,自然具有面向对象的语言特性:封装、继承、多态。它既具有静态语言的特性(如C++),又有动态语言的效率(动态绑定、动态加载等)。总体来讲,OC确实是一门不错的编程语言,
Objective-C具有相当多的动态特性,表现为三方面:动态类型(Dynamic typing)、动态绑定(Dynamic binding)和动态加载(Dynamic loading)。动态——必须到运行时(run time)才会做的一些事情。
- 动态类型:即运行时再决定对象的类型,这种动态特性在日常的应用中非常常见,简单来说就是id类型。事实上,由于静态类型的固定性和可预知性,从而使用的更加广泛。静态类型是强类型,而动态类型属于弱类型,运行时决定接受者。
- 动态绑定:基于动态类型,在某个实例对象被确定后,其类型便被确定了,该对象对应的属性和响应消息也被完全确定。
- 动态加载:根据需求加载所需要的资源,最基本就是不同机型的适配,例如,在Retina设备上加载@2x的图片,而在老一些的普通苹设备上加载原图,让程序在运行时添加代码模块以及其他资源,用户可根据需要加载一些可执行代码和资源,而不是在启动时就加载所有组件,可执行代码可以含有和程序运行时整合的新类。
18. Object C中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么?
答:线程创建有三种方法:使用NSThread创建、使用GCD的dispatch、使用子类化的NSOperation,然后将其加入NSOperationQueue;在主线程执行代码,方法是performSelectorOnMainThread,如果想延时执行代码可以用performSelector:onThread:withObject:waitUntilDone:
19.Objective-C 中是否支持垃圾回收机制?
OC是支持垃圾回收机制的(Garbage collection简称GC),但是apple的移动终端中,是不支持GC的,Mac桌面系统开发中是支持的.
移动终端开发是支持ARC(Automatic Reference Counting的简称),ARC是在IOS5之后推出的新技术,它与GC的机制是不同的。我们在编写代码时, 不需要向对象发送release或者autorelease方法,也不可以调用delloc方法,编译器会在合适的位置自动给用户生成release消息(autorelease),ARC 的特点是自动引用技术简化了内存管理的难度.
20.循环引用的产生原因,以及解决方法
产生原因:对象A和对象B相互引用了对方作为自己的成员变量,只有自己销毁的时候才能将成员变量的引用计数减1。对象A的销毁依赖于对象B的销毁,同时对象B销毁也依赖与对象A的销毁,从而形成循环引用,此时,即使外界没有任何指针访问它,它也无法释放。
多个对象间依然会存在循环引用问题,形成一个环,在编程中,形成的环越大越不容易察觉.
解决方法:
事先知道存在循环引用的地方,在合理的位置主动断开一个引用,是对象回收;
使用弱引用的方法。
21.switch 语句 if 语句区别与联系
均表示条件的判断,switch语句表达式只能处理的是整型、字符型和枚举类型,而选择流程语句则没有这样的限制。但switch语句比选择流程控制语句效率更高。
22.如何查找某一导航栈中是否有某一个特定VC,isMemberOfClass 和 isKindOfClass 联系与区别
联系:两者都能检测一个对象是否是某个类的成员
区别:isKindOfClass 不仅用来确定一个对象是否是一个类的成员,也可以用来确定一个对象是否派生自该类的类的成员 ,而isMemberOfClass 只能做到第一点。
举例:如 ClassA派 生 自NSObject 类 , ClassA *a = [ClassA alloc] init];,[a isKindOfClass:[NSObject class]] 可以检查出 a 是否是 NSObject派生类 的成员,但 isMemberOfClass 做不到。
23.在多人合作开发环境下,可能会有重复添加KVO的时候,或者重复移除KVO的时候,重复添加或删除KVO会怎么样?采取什么方案去解决?
这个问题其实就是看你解决问题的能力及思路,我觉得有两种方法都可以实现,一种就是手动实现一个KVO,KVO本质是苹果偷偷添加了一个派生类,将指针指向派生的子类,去监听子类的set方法从而实现监听的效果,手动实现一个KVO,并修改其添加和移除的方法,使用BOOL值作为属性在方法内部进行判断当前这个对象是否添加过或删除过即可;第二种方法,就是写一个类别,利用runtime去交换kvo的添加或移除方法到自定义的添加或移除方法,修改内部方法实现以达到该效果。
24.如果让你去设计SDWebImage的缓存机制,怎么去设计?
SDWebImage的缓存分为两种,一种是内存缓存(MemoryCache),另一种是硬盘缓存(DiskCache),我会自定义一个单例缓存类,其中会有对应的两种缓存作为属性,另外还会暴露一些自定义属性让用户可以去修改缓存的策略,例如最大缓存字节数,自动进行清理缓存还是不自动去清理等,还会暴露一些方法比如通过key去查找该缓存文件,当前key是否存在于沙盒或者内存中等。
25.如果一个自定义的Cell可能被用在多处,每处后台返回的数据源模型(json原始数据)字段都是不同的,如果让你去设计一种Model去兼容所有的业务需求,即不管原始数据什么样都能一样进行解析赋给Cell,你怎么去设计?
这个问题我觉得有很多种解决方法,我当时列举了两种,
第一种是采用一个万能Model,即设计一个只针对于View控件的Model,不去管数据源的模型。可以这么理解,这个Model中的所有字段都是针对于View的,而不是原始数据源的,这样当你从AFN请求下来数据后需要再进行二次转换,即将原始数据转换成Model里的字段,这样就不管你是什么原始数据最终都将转换成只针对于View的Model里的字段,即万能字段(这个想法说出之后,不知道为什么那个面试官楞了一下,可能没理解还是我表达的不够清楚,他最后想了想说这个也可以还有没有更好的方法?我心中默念:mmp,难道只有你心里那个答案才是正确的么?)
第二种解决方法是采用 协议/接口 模式,即在Cell中去创建一个代理人,代理人遵守Cell数据的接口,这个协议的所有方法即是赋给Cell数据的方法,当然也只考虑当前这个Cell,不考虑原始数据;
26.__block 能修改外部变量根本原因是什么?
Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。
27.自动释放池中的变量什么时候释放
自动释放池其实会在其作用域结束时释放池子,所有在池中的变量都会执行一次release操作,所以如果在池子中的变量执行过release操作后引用计数仍然大于0,那么就不会被释放,反之为0就会被释放,所以自动释放池中的变量也不一定出了池作用域后就会被释放的
28.block根据内存区间分为几种类型?
__NSMallocBlock /__NSStackBlock/ __ NSGlobalBlock
在ARC下,默认的block创建后都是GlobalBlock,当block内部引用到外部变量的值时,就会从GlobalBlock变到NSMallocBloc,注意即使你引用的是在栈区的局部变量也是NSMallocBloc,因为ARC下会将栈区的block copy到堆区,如果在MRC下需要手动去copy才会到堆区,所以关键在于block是否引用外部的变量,外部变量内存地址决定了block的内存地址,即NSStackBlock还是NSMallocBlock。
29.block在什么时候会造成循环引用,如何避免循环引用以及weakSelf、strongSelf的区别。
循环引用指两个对象相互强引用了对方,即retain了对方,从而导致谁也释放不了谁的内存泄露问题。
block的循环引用问题,因为block在拷贝到堆上的时候,会retain其引用的外部变量,那么如果block中如果引用了他的宿主对象,那很有可能引起循环引用
因此防止循环引用的方法如下:__weak TestCycleRetain *weakSelf = self;
在 block 之前定义对 self 的一个弱引用 wself,因为是弱引用,所以当 self 被释放时 wself 会变为 nil;然后在 block 中引用该弱应用,考虑到多线程情况,通过使用强引用 sself 来引用该弱引用,这时如果 self 不为 nil 就会 retain self,以防止在后面的使用过程中 self 被释放;然后在之后的 block 块中使用该强引用 sself,注意在使用前要对 sself 进行了 nil 检测,因为多线程环境下在用弱引用 wself 对强引用 sself 赋值时,弱引用 wself 可能已经为 nil 了。通过这种手法,block 就不会持有 self 的引用,从而打破了循环引用。
30.通知中心添加事件监听需要注意什么?如果添加监听后忘记移除监听会有什么样的坑?通知中心是什么设计模式的实现,如何实现这种模式?postNotification方法是同步的还是异步的?
优势:
1.不需要编写多少代码,实现比较简单;
2.对于一个发出的通知,多个对象能够做出反应,即1对多的方式实现简单
3.controller能够传递context对象(dictionary),context对象携带了关于发送通知的自定义的信息
缺点:
1.在编译期不会检查通知是否能够被观察者正确的处理;
2.在释放注册的对象时,需要在通知中心取消注册;
3.在调试的时候应用的工作以及控制过程难跟踪;
4.需要第三方对喜爱那个来管理controller与观察者对象之间的联系;
5.controller和观察者需要提前知道通知名称、UserInfo dictionary keys。如果这些没有在工作区间定义,那么会出现不同步的情况;
6.通知发出后,controller不能从观察者获得任何的反馈信息。
观察者模式
postNotification方法是同步的,用时间差检测;
31. ios crash的原因与抓取crash日志的方法
IOS策略
1)低内存闪退
前面提到大多数crash日志都包含着执行线程的栈调用信息,但是低内存闪退日志除外,这里就先看看低内存闪退日志是什么样的。
我们使用Xcode 5和iOS 7的设备模拟一次低内存闪退,然后通过Organizer查看产生的crash日志,可以发现Process和Type都为Unknown:
2) Watchdog超时
Apple的iOS Developer Library网站上,QA1693文档中描述了Watchdog机制,包括生效场景和表现。如果我们的应用程序对一些特定的UI事件(比如启动、挂起、恢复、结束)响应不及时,Watchdog会把我们的应用程序干掉,并生成一份响应的crash报告。
3) 用户强制退出
一看到“用户强制退出”,首先可能想到的双击Home键,然后关闭应用程序。不过这种场景是不会产生crash日志的,因为双击Home键后,所有的应用程序都处于后台状态,而iOS随时都有可能关闭后台进程,所以这种场景没有crash日志。
另一种场景是用户同时按住电源键和Home键,让iPhone重启。这种场景会产生日志(仅验证过一次),但并不针对特定应用程序。
这里指的“用户强制退出”场景,是稍微比较复杂点的操作:先按住电源键,直到出现“滑动关机”的界面时,再按住Home键,这时候当前应用程序会被终止掉,并且产生一份相应事件的crash日志。
通常,用户应该是遇到应用程序卡死,并且影响到了iOS响应,才会进行这样的操作——不过感觉这操作好高级,所以这样的crash日志应该比较少见。
代码bug
此外,比较常见的崩溃基本都源于代码bug,比如数组越界、插空、空引用、引用未定义方法、多线程安全性、访问野指针、发送未实现的selector等。
再来谈谈获取iOS设备上崩溃日志(Crash Log)的方法
第一个方法:XCode 的菜单Window->Organizer 选择Devices -> 选中的手机 -> 点击手机名称左边的箭头 会等到如下图
在右边竖蓝色矩形框中 Type里面出现两种类型:Unknown和Crash 这两种类型分别是 内存不够回收内存kill应用程序导致Crash和程序异常Crash的日志。
第二种方法 打开手机 - > 设置 -> 通用 - > 关于本机 - > 诊断与用量 - > 诊断与用量数据 这里面就是所有应用的Crash日志。(本人没找到这个)
第三种方法 使用第三方软件:itools等
如果你平时不用iTunes,而是使用itools这类第三方的软件对iPhone设备进行管理,也是没问题的。
打开itools,在你的设备下,找到“高级功能”,点击“崩溃日志”,然后将需要的日志导出到电脑里面就可以了!
第四种方法 通过iTunes Connect(Manage Your Applications - View Details - Crash Reports)获取用户的crash日志
大部分用户可能都会使用iTunes软件来管理iPhone或者iPad设备,这时候同步的Crash日志就会同步到电脑上,我们只需要在特定的路径里面寻找即可。
Mac OS X:~/Library/Logs/CrashReporter/MobileDevice
Windows XP:C:\Documents and Settings\Application Data\Apple computer\Logs\CrashReporter
Windows 7/Vista: C:\Users\计算机登录名\AppData\Roaming\Apple Computer\Logs\CrashReporter\MobileDevice
32.OC的消息转发机制
OC中的方法调用是利用消息转发实现的。
objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行。如果在层层的寻找中,均未找到方法的实现,就是会抛出unrecognized selector sent to XXX的异常,导致程序崩溃。
OC的方法调用,称为消息发送,就是给被调用的对象发送了一条消息 ,像这样【receiver msg】其实就是告诉对象要调用某个方法(其实就是函数),每一个方法都对应 SEL,这个 SEL叫做选择器,表示一个方法的 selector指针,两个类之间不管之间有什么关系,只要方法名字相同,那么就对应了同一个 SEL,所以在 OC 中同一个类或者同一个继承体系中,不能存在两个相同的方法名,即使参数类型不同也不行。不同类的实例对象执行相同的 selector 时,会在各自的 methodList 中寻找各自对应的 IMP ,那么这个 IMP 是什么鬼,那就说说吧,IMP 其实就相当于函数指针,指向方法实现的首地址,IMP 就是为 SEL 而生的,SEL 是一个方法的唯一标识,通过取得 IMP,我们可以跳过 OC 的 Runtime 机制,直接指向 IMP 的方法实现,这样就省去了 runtime 过程中的一系列的消息查找工作,会比直接向对象发送消息更加高效一些。
那么下面就是消息转发了,当 OC 向某个对象发送了一条未知的消息时,他并不会马上报错,而是会经历几个步骤:
1 动态方法解析
2 备用接收者
3 完整转发
(1)动态方法解析:首先调用所属类的的类方法+resolveInstanceMrthod 可以有机会为该未知消息新增一个处理方法,不过前提是我们已经实现了这个处理方法,我们可以通过 class_addMethod 函数动态的添加未知消息到类里,让原来没有处理这个消息的类具有处理这个消息的能力
(2)备用接收者:-(id)forwardingTargetForSelector:(SEL)aselector
如果一个对象实现了方法并返回一个非 nil 的对象,则返回的对象会作为消息的接收者,且消息会被分发到这个对象,当然如果没有指定的对象来处理这个 aselector,则应该调用父类的实现来返回结果,但是有一点这个方法通常在对象的内部比如.m 文件里,还有一系列对象能处理该消息,我们可以借这些对象来处理该消息并且返回,这样好像看起来就是该类处理了消息,做好事不留名,像我一样
(3)完整消息转发:
—(void)forwardInvocation(NSInvocation *)aInvocation
NSInvocation 初始化需要有一个方法签名 NSMethodSignature
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
—(void)forwardInvocation(NSInvocation *)aInvocation{
if([object instanceRespondToSelector:aInvocaion.selector]){
[aInvocation invokeWithTarget:[object alloc]init]]
}
}
这样相应的消息就会转发到这个 object 对象这里来实现了
33.api hook原理及应用,如何防止按钮重复点击。
iOS中的按钮事件机制 >>> Target-Action机制
用户点击时,产生一个按钮点击事件消息
这个消息发送给注册的Target处理
Target接收到消息,然后查找自己的SEL对应的具体实现IMP正儿八经的去处理点击事件
实际上该点击消息包含三个东西:
Target处理者
SEL方法Id
按钮事件当时触发时的状态
所有的按钮事件状态
typedef NS_OPTIONS(NSUInteger, UIControlState) {
UIControlStateNormal = 0,
UIControlStateHighlighted = 1 << 0, // used when UIControl isHighlighted is set
UIControlStateDisabled = 1 << 1,
UIControlStateSelected = 1 << 2, // flag usable by app (see below)
UIControlStateFocused NS_ENUM_AVAILABLE_IOS(9_0) = 1 << 3, // Applicable only when the screen supports focus
UIControlStateApplication = 0x00FF0000, // additional flags available for application use
UIControlStateReserved = 0xFF000000 // flags reserved for internal framework use
};
点击按钮时候,会产生一个包装了Target、SEL、按钮事件状态三个东西的消息发送给Target处理
问题: 是谁来包装UIButton的点击事件消息,并且完成发送消息了?
这个是解决连续点击按钮的关键问题所在,必须搞清楚。因为如果搞清楚具体包装和发送按钮点击时间消息的地方和时机,那么可以拦截这个地方执行,然后加入是否在指定的间隔时间内决定是否让其继续执行发送消息的操作。
那么问题不就解决了吗,我都不让他发送消息了,他还能执行?
首先从UIButton.h头文件中查找,是否有send message 、send Action …等等包含send的方法无法找到.
UIButton继承自UIControl,而UIControl又负责很多的UI事件处理,那么可以继续从UIControl.h中查找找到两个send相关的函数:
// send the action. the first method is called for the event and is a point at which you can observe or override behavior. it is called repeately by the second.
- (void)sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event;
- (void)sendActionsForControlEvents:(UIControlEvents)controlEvents; // send all actions associated with events
突破口 >>> UIControl完成按钮点击事件消息的包装与发送的阶段,可以做一些间隔时间处理点击消息发送我们可以在UIControl的sendAction:to:forEvent:做防止按钮连续处理.那么大概有如下几种做法:
第一种、自定义我们的UIButton类,以后程序中都使用我们UIButton类(只适合新项目,不太适合老项目,用的地方太多了)
第二种、使用UIButton Category封装防止按钮连续点击处理的逻辑(这种挺好,对原来的UIButton使用代码绿色无公害)
第三种、直接在main.m中执行main()之前,就替换掉UIControl的sendAction:to:forEvent:
具体实现:
使用UIButton子类实现
#import@interface MyButton : UIButton
/**
* 按钮点击的间隔时间
*/
@property (nonatomic, assign) NSTimeInterval time;
@end
#import "MyButton.h"
// 默认的按钮点击时间
static const NSTimeInterval defaultDuration = 3.0f;
// 记录是否忽略按钮点击事件,默认第一次执行事件
static BOOL _isIgnoreEvent = NO;
// 设置执行按钮事件状态
static void resetState() {
_isIgnoreEvent = NO;
}
@implementation MyButton
- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
//1. 按钮点击间隔事件
_time = _time == 0 ? defaultDuration : _time;
//2. 是否忽略按钮点击事件
if (_isIgnoreEvent) {
//2.1 忽略按钮事件
// 直接拦截掉super函数进行发送消息
return;
} else if(_time > 0) {
//2.2 不忽略按钮事件
// 后续在间隔时间内直接忽略按钮事件
_isIgnoreEvent = YES;
// 间隔事件后,执行按钮事件
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_time * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
resetState();
});
// 发送按钮点击消息
[super sendAction:action to:target forEvent:event];
}
}
@end
ViewController中测试
@implementation ViewController
- (void)btnDidClick:(id)sender {
NSLog(@"我被点击了 >>> %@", NSStringFromSelector(_cmd));
}
网友评论