1、Objective-C的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么?
Objective-C不可以多重继承;可以实现多个接口;Category是分类;
重写一个类的方法最好是用分类,因为分类不会影响其他类的方法实现;方法的修改只在本类范围内有效
2、#import 跟#include 又什么区别,@class呢, #import<> 跟 #import“”又什么区别?
#import和#include都可以作为引入头文件的关键字;区别在与#import用于Objective-C引入头文件,只会被自动导入一次,不会导致文件的重复引用;#include用于c/c++引入头文件,会导致重复引用,可以使用#pragma once或者#ifndef解决重复引用;
@class用于告诉编译器类的声明,只有在执行的时候才会去检查类的实现;可以解决头文件的相互引用;#import导入的是工程中自定义类的头文件;#inport<>用于引入系统框架或者第三方框架的头文件;
3、属性readwrite,readonly,assign,retain,copy,nonatomic 各是什么作用,在那种情况下用?
readwrite读写属性,多用于需要生成getter和setter方法的时候;
readonly只读属性,只会生成getter方法,多用于不希望在外部改变属性;
assign赋值属性,setter方法将传入参数赋值给实例变量;仅设置变量时;
retain持有属性,setter方法将传入参数先保存一份,在赋值;传入参数的retainCount会加1;
copy赋值属性,setter方法将传入参数复制一份;多用于完全需要一份新的变量时;
nonatomic非原子属性,告诉编译器生成getter和setter采用非原子操作,是线程不安全;
atomic原子属性,是线程安全(仅对getter和setter)
4、对于语句NSString * obj = [[NSData alloc] init]; obj在编译时和运行时分别时什么类型的对象?
obj编译时是NSString对象,运行时是NSData对象
5、写一个setter方法用于完成@property (nonatomic,retain) NSString * name,写一个setter方法用于完成@property (nonatomic,copy) NSString * name
@property(nonatomic,retain) NSString* name
(void) setName:(NSString*) name {
[name retain];
[_name release];
_name = name;
}
@property(nonatomic,copy) NSString* name
(void) setName:(NSString*) name {
id obj = [name copy];
[_name release];
_name = obj;
}
6、常见的Objective-C的数据类型有那些, 和C的基本数据类型有什么区别?如:NSInteger和int
Objective-C的数据类型有:NSString、NSArray、NSDictionary、NSData、NSNumber、NSMutableString、NSMutableArray、NSMutableDictionary等;
C的基本数据类型有:int、float、double、long等;
区别在于Objective-C的数据类型都是class类型,创建后便是对象;而C的数据类型int只是一定大小的内存空间,用于存放数值;
NSInteger不属于NSNumber,当然也不属于NSObject;NSInteger是数据类型int和long的别名;区别在于NSInteger会根据系统是32位或者64位来确定本身是int或者long;
7、Objective-C如何对内存管理的,说说你的看法和解决方法?
内存管理方法有:手动管理内存(MRC)、自动管理内存(ARC)、内存池;
MRC遵循的原则是:谁创建,谁释放;谁添加,谁释放;对象retain之后,必须在相应的地方release;只有当对象的retainCount为0的时候,对象内存才会被回收;对象调用alloc、init、retain、copy等方法,引用计数会+1;数组的添加方法也会导致引用计数+1;
ARC:编译器会在编译期间,给我们的代码中自动添加retain、release等操作,减少了程序员手动维护的烦恼和可能会遗漏的地方;
内存池:由autorelease添加到系统内存池中,这样的对象不会立即释放,只有当内存池被释放的时候,内存池中的对象才会被释放;
8、原子(atomic)跟非原子(non-atomic)属性有什么区别?
atomic是多线程安全的,在getter和setter过程中,会加锁来确保线程安全;
nonatomic是非线程安全的,相比atomic速度较快;
9、看看下面的代码,分别说出其引用计数是多少?
NSObject* obj1 = [[NSObject alloc] init];
int count = [obj1 retainCount];
NSLog(@"------%d",count);//对象创建,此时的引用计数为1
NSObject* obj2 = obj1;
[obj2 retain];
count = [obj2 retainCount];
NSLog(@"------%d",count);//此时的引用计数为2,因为对象的引用计数都是针对的内存来说,而obj2对象的内存实际指向为obj1的内存
NSArray* array = [[NSArray alloc] init];
NSString* str = [NSString stringWithFormat:@"test"];
[str retain];
[array addObject:str];
count = [str retainCount];
NSLog(@"------%d",count);//此时的引用计数为3,因为str对象创建+1,retain+1,加入数组+1
[str retain];
[str release];
[str release];
count = [str retainCount];
NSLog(@"------%d",count);//此时的引用计数为2,因为str对象retain+1,release-1
[array removeAllObjects];
count = [str retainCount];
NSLog(@"------%d",count);//此时的引用计数为1,因为数组移除,数组里面的所有对象引用计数-1
10、MVC是什么?Objective-C有那些设计模式?
MVC是一种经典的设计模式,整体分为model(模型层,用来存储和处理数据)、view(视图层,用来展示数据)、controller(控制层,用来进行业务逻辑处理);
Objective-C的设计模式有:代理模式、通知模式、工厂模式、单例模式、观察者模式;
11、浅复制和深复制的区别?
浅复制只是对象指针的复制,指向都还是同一块内存;
深复制是对象的完全拷贝,开辟一块新的内存存放对象;
12、类别的作用?继承和类别在实现中有何区别?类扩展又是什么?
类别是一种非正式协议,它只能添加新的方法;不能修改和删除原有方法;也不能添加属性(通过runtime可以);如果类别中的方法名称冲突,则会覆盖本类的方法,因为类别的优先级较高;
类别的作用:
a、将类的实现分散到多个不同的文件或者框架中
b、创建对私有方法的前向引用
c、为对象添加非正式协议
继承可以添加、修改和删除方法,也可以添加属性;
类扩展是一种私有的类别,可以添加属性,也可以添加方法,类扩展添加的方法必须要实现;它寄托于某个类本身,作用域仅限寄托类本身,外部类不可访问;
13、什么是KVO和KVC?KVO有什么优缺点?
KVC是一种键值编码机制,提供了一种间接访问对象属性而不是通过存取方法的机制,使用字符串来标识属性;
KVO是基于KVC的扩展,是一种观察者机制;通过对象属性的观察,间接通知观察者其变化,极大的简化代码复杂度;
>KVO的优点
提供了一种简单的方法实现两个对象的同步
能够对非我们创建的对象,即内部对象的状态改变做出响应,而不需要改变内部对象的实现
利用keyPath的机制,可以观察嵌套对象
>KVO的缺点
观察的属性必须使用NSString来定义,因此在编译器不会出现错误;不易于后期错误排查;
对属性的重构将导致观察者功能失效;
如果观察了多个对象,需要使用复杂的if判断语句来进行区分
当释放观察者时需要移除观察者
14、谈一谈KVC中setter和getter方法的实现原理?
KVC中常用的方法有:
valueForKey://通过key来获取值
setValue:forKey:// 通过key来赋值
valueForKeyPath://通过keyPath来获取值
setValue:forKeyPath://通过keyPath来赋值
accessInstanceVariablesDirectly//是否允许直接访问成员变量;如果返回YES,则set的时候会根据_key,_isKey,key,isKey来搜索成员变量;如果返回NO,则禁止使用KVC;
validateValue:forKey:error://提供属性值正确性的验证API,它可以对值做一个验证,如果不正确则可以进行替换、拒绝并且返回出错原因
valueForUndefinedKey://如果key不存在,且KVC无法搜索到与key相关的属性,则会调用此方法,系统默认是抛出异常
setValue:forUndefinedKey://与上述方法一致,该方法用在赋值的时候
setNilValueForKey://当调用setter方法,传入参数为nil的时候调用
>setter原理
a、首先寻找setKey,_setKye方法,没有则会搜索setIsKey,_setIskey方法;
b、如果上述方法都没有找到,则检查accessInstanceVariablesDirectly方法:
如果返回YES,则搜索_key,_isKey,key,isKey的属性;如果这些属性也没有找到的话,则会调用setValue:forUndefinedKey方法;
如果返回NO,则直接调用setValue:forUndefinedKey方法;
结论:setKey、_setKey、setIsKey、_setIsKey、_key、_isKey、key、isKey(搜索优先级从左到右);
>getter原理
a、首先寻找getKey、key、isKey方法;
b、如果上述方法没找到,则检查accessInstanceVariablesDirectly方法:
如果返回YES,则搜索_key,_isKey,key,isKey的属性;如果这些属性也没有找到的话,则会调用valueForUndefinedKey方法;
如果返回NO,则直接调用valueForUndefinedKey方法;
结论:getKey、key、isKey、_key、_isKey、key、isKey(搜索优先级从左到右);
15、谈谈APNS的推送原理
a、首先设备向APNS服务器申请注册推送服务,APNS服务器返回唯一标识token;
b、设备获取返回的token进行本地缓存,同时也将token传递到业务服务器进行保存;
c、业务服务器根据业务需要调用APNS服务器接口,传递token和消息内容;
d、APNS服务器根据token找到对应设备,同时下发消息内容和appId,设备根据appId找到具体的应用并进行消息展示;
16、为什么说Objective-C是动态运行时语言?
这个问题其实设计到多态和运行时两个问题:
运行时:其实就是将数据类型的确定,由编译器阶段推迟到了运行时阶段;只有在程序运行时,才会真正确定对象的类型,以及对象的方法调用;
多态:不同的对象采用不用的方法相应相同消息的机制;
17、如何创建一个单例?
在Objective-C中实现一个单例类,有以下几步:
a、创建一个静态变量,并初始化,设置为nil;
b、实现一个构造函数,在该函数中检测静态变量是否为空,如果为空则创建一个本类的实例并赋值给实例变量;改过程需要确保在整个APP运行过程中只会被执行一次,比如:dispath_once
c、重写allocWithZone方法,确保直接使用alloc、init方法创建实例的时候不会新建一个实例;
d、适当实现allocWithZone、copyWithZone、release和autoRelease;
18、frame和bounds的区别
frame:指view在父view中的坐标(参照的是父视图坐标体系)
bounds:指view在本身系统的坐标(参照的是本身坐标体系)
19、方法与选择器的区别
方法:包含了方法名称、传入参数和输出参数;
选择器:只是一个字符串表示的方法名称
20、说说NSArray和NSMutableArray的区别
NSArray:是一个不可变对象,是线程安全的;在运行时不能添加、删除元素,但不表示其元素的内容不能被改变;
NSMutableArray:是一个可变对象,是非线程安全的;在运行时可以动态添加、删除元素;
21、什么是简便构造方法
Foundation框架提供了很多简便构造方法,方便我们快速获取对象实例,而且不需要我们手动管理内存;比如:NSNumber的numberWithBool、numberWithInt等
22、UIView的动画有哪些
系统UIKitk框架中的UIView.h文件提供了UIView动画的2中调用方式,普通方式和block方式,我们一般采用block方式;
>普通方式:
a、开始动画
[UIView beginAnimations:@"test" context:nil];
参数一:动画标识
参数二:附加参数,在设置代理的情况下;此参数将发送到setAnimationWillStartSelector和setAnimationDidStopSelector所指定的方法,大部分情况下,设置为nil;
b、设置动画参数
[UIView setAnimationDuration:2.f];//设置动画时间
[UIView setAnimationDelegate:self];//设置动画代理
[UIView setAnimationWillStartSelector:@selector(xxx)];//设置动画开始时代理所执行的方法
[UIView setAnimationDidStopSelector:@selector(xxx)];//设置动画结束时代理所执行的方法
[UIView setAnimationDelay:1.f];//设置动画延迟时间
[UIView setAnimationRepeatCount:4];//设置动画重复次数
[UIView setAnimationStartDate:[NSDate date]];//设置动画开始时间,默认now
/**
UIViewAnimationCurve枚举值
UIViewAnimationEaseInOut 慢进慢出(默认值)
UIViewAnimationEaseIn 慢进
UIViewAnimationEaseOut 慢出
UIViewAnimationLinear 匀速
**/
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];//设置动画曲线
/**
假设上一个动画正在播放,并未完成;此时我们需要播放一个新的动画:
YES:动画将从上一个动画的所在状态开始播放;
NO:动画将从上一个动画所指定的最终状态开始播放(上一个动画会立马结束)
**/
[UIView setAnimationBeginsFromCurrentState:YES];//设置动画是否从当前状态开始播放
[UIView setAnimationRepeatAutoreverses:YES];//设置动画是否继续执行相反的动画
[UIView setAnimationEnabled:YES];//是在是否使用动画,YES使用,NO禁止
/**
UIViewAnimationTransition 过渡效果,枚举类型
UIViewAnimationTransitionNone 无动画(默认值)
UIViewAnimationTransitionFlipFromLeft 从左往右旋转翻页
UIViewAnimationTransitionFlipFromRight 从右往左旋转翻页
UIViewAnimationTransitionCurlUp 从下往上卷曲翻页
UIViewAnimationTransitionCurlDown 从上往下卷曲翻页
**/
[UIView setAnimationTransition:UIViewAnimationTransitionNone forView:nil cache:YES];//设置动画的过渡效果
c、结束动画方法
[UIView commitAnimations];
>block方式
a、最简单的动画方式:包含时间和动画
[UIView animateWithDuration:1.0f animations:^{
// 动画执行代码
}];
b、带有动画提交回调的block
[UIView animateWithDuration:1.0f animations:^{
// 动画执行代码
} completion:^(BOOL isFinished){
//动画执行完的代码
}];
c、可以设置动画延时时间、过渡效果的动画
/**
UIViewAnimationOptions 过渡动画,枚举类型
UIViewAnimationOptionLayoutSubviews 提交动画的时候布局子视图,表示子视图和父视图一起动画
UIViewAnimationOptionAllowUserInteraction 允许动画期间用户交流,比如:触摸
UIViewAnimationOptionBeginFromCurrentState 从当前状态开始动画
UIViewAnimationOptionRepeat 动画无限重复
UIViewAnimationOptionAutoreverse 执行动画回路,前提是设置了动画无限重复
UIViewAnimationOptionOverrideInheritedDuration 忽略外层动画嵌套时间
UIViewAnimationOptionOverrideInheritedCurve 忽略外层动画嵌套的变化曲线
UIViewAnimationOptionAllowAnimatedContent 转场:进行动画时重绘视图
UIViewAnimationOptionShowHideTransitionViews 转场:移除动画效果(添加和删除图层)
UIViewAnimationOptionOverrideInheritedOptions 不继承父动画设置
// 时间函数曲线相关
UIViewAnimationOptionCurveEaseInOut 时间函数,慢进慢出(中间快)
UIViewAnimationOptionCurveEaseIn 时间函数,慢进(快出)
UIViewAnimationOptionCurveEaseOut 时间函数,慢出(快进)
UIViewAnimationOptionCurveLinear 时间函数,匀速
// 转场动画相关
UIViewAnimationOptionTransitionNone //转场,不使用动画
UIViewAnimationOptionTransitionFlipFromLeft //转场,从左向右旋转翻页
UIViewAnimationOptionTransitionFlipFromRight //转场,从右向左旋转翻页
UIViewAnimationOptionTransitionCurlUp //转场,下往上卷曲翻页
UIViewAnimationOptionTransitionCurlDown //转场,从上往下卷曲翻页
UIViewAnimationOptionTransitionCrossDissolve //转场,交叉消失和出现
UIViewAnimationOptionTransitionFlipFromTop //转场,从上向下旋转翻页
UIViewAnimationOptionTransitionFlipFromBottom //转场,从下向上旋转翻页
**/
[UIView animateWithDuration:1.0f
delay:1.0f
options:UIViewAnimationOptionCurveLinear
animations:^{
// 动画执行代码
} completion:^(BOOL isFinished){
//动画执行完的代码
}];
23、Objective-C中的数据存储方式
NSUserDefault
NSCoder归档
SQLLite、CoreData
24、写一个block的定义
typedef void (^TestBlock)(NSString* str);
25、谈谈你对block的理解,在使用的时候有哪些需要注意的地方?在MRC和ARC中block有什么区别?
block:俗称闭包,是一个代码块;可以理解为是一种匿名函数,提供了一直回传值的机制,可以在一定程度上简化代码结构;
block的类型,取决于block中捕获的内容:
全局block,没有引用外部变量或者引用了静态变量的block,不持有对象;
栈block,引用了外部变量(非静态)的block,不持有对象;
堆block,对栈block进行了copy操作(比如:手动copy,把block当做返回值自动copy,block被强引用自动copy),持有对象;
ARC和MRC中block的区别:
ARC中block使用=会进行block的copy操作;MRC中不会,所以同样的代码在ARC中内存存放在堆中,而在MRC中存放在栈中;
需要注意的地方:
如果要在block中改变外部变量的值,需要使用__block来修饰变量;
如果要在block中避免循环引用,需要使用__weak来修饰;
如果要在block中延时执行引用对象,需要在block中使用__strong来修改引用对象
block在创建的时候,他的内存是存放在栈上的;它的作用域仅限创建时候的作用域;如果要在其他地方(作用域之外)使用,则需要手动对其进行copy或者retain,否则会导致程序崩溃。比如:
typedef void(^testBlock)(int number);
-(void) viewDidLoad {
testBlock = ^(int number){
NSlog(@"---%d",number);
};
[testBlock copy]; // 如果不加,会导致在viewDidLoad方法之外的其他地方调用时程序崩溃;
}
-(void)testMethod{
testBlock(5);
}
26、+sendSynchronousRequest:returningResponse:error:与– initWithRequest:delegate:两个方法的区别?
sendSynchronousRequest:returningResponse:error:方法是同步网络请求的方法,该方法会阻塞当前线程,直到获取request返回的response;
initWithRequest:delegate:方法是异步网络请求方法,该网络请求完成后,会通过delegate回到主线程;
27、Objective-C有私有方法吗?有私有变量吗?
Objective-C没有私有方法,只有静态方法(+)和实例方法(-);
Objective-C有私有变量,@private可以修饰私有变量;
Objective-C所有的实例变量都是私有的,所有的实例方法都是共有的;
28、C和Objective-C如何混用?
.m后缀的文件,可以使用Objective-C和C语言;
.mm后缀的文件,可以使用Objective-C、C和C++语言;
.cpp后缀的文件,只能使用C和C++语言,而且include的头文件中也不用是有Objective-C的语言;
29、Objective-C中堆与栈的区别?
栈:是向低地址扩展的数据结构,是一块连续的内存区域;栈的空间大小是有限的,如果申请的内存大于栈的剩余空间,则会导致overflow
堆:是向高地址扩展的数据结构,是不连续的内存区域;这是因为系统使用链表来存储空闲的内存区域,所以是不连续的;堆的大小是受限于系统中有效的虚拟内存,所以相对来讲,堆可用的内存较大,灵活性也大一些;
管理方式:对于存放在栈中的内存,由编译器自动管理,无需手动管理;对于存放在堆中的内存,需要开发者手动管理,管理不当容易产生内存泄露;
碎片问题:对于堆来讲,连续的new/delete会导致大量的内存空间不连续,从而导致大量的碎片,降低系统的使用效率;栈则不会有此问题,因为栈是先进后出的队列,他们是一一对应的;
分配方式:堆只有动态分配;栈有动态分配与静态分配,静态分配是由编译器完成,比如:局部变量;动态分配是由alloc函数进行分配,栈的动态分配是由编译器进行释放;
分配效率:栈是由系统提供的数据结构,它的进栈和出栈都有专门的指令,所以效率较高;堆是有c/c++函数库提供的数据结构,相对较为复杂;
30、用预处理指令#define声明一个常数,用以表明1年中有多少秒?
#define SECOND_FOR_YEAR (60*60*24*365)UL
结论:#define不能以分号结束,使用括号等
31、写一个标准的宏定义,输入两个参数并返回较小的哪一个
#define MIN(A,B) ((A)>(B)?(B):(A))
结论:宏定义中需要对参数和整个宏都使用括号;
32、关键字const有什么含意?修饰类呢?static的作用,用于类呢?还有extern c的作用?volatile又有什么作用?
const:只读的意思;合理的使用const来修饰,可以有效的保护不被修改的参数,防止被无意的修改代码;
对于指针来说,const可以修饰指针所指向的数据,也可以同事修饰指针和被指向的数据;
对于一个函数的声明,const可以修饰形参,表明它是一个输入参数,不希望在函数内部被改变;
对于类的成员函数,如果被const修饰,表明其是一个常函数;在函数内部不能修改类的成员变量;
对于类的成员函数,有时候必须指定其返回值为const类型,避免其返回值为“左值”;
static修饰局部变量:
a、局部变量只会初始化一次;
b、局部变量只会有一份内存;
c、局部变量的作用域不变,但生命周期改变了(程序销毁时才会示释放内存)
static修饰全局变量:
a、全局变量的作用域仅限当前文件,外部类无法使用改变量;
extern外部常量的最佳方法
extern const 关键字,只是表面变量已经声明,只能引用,不可改变;
volatile表示不稳定的,说明变量有可能被改变;编译器就不会随便假设变量的值,编译器在读取变量的时候会小心的读取,而不是从缓存中读取;
32、进程与线程的区别和联系
进程与线程都是系统程序运行的基本单元,系统利用该基本单元实现应用的并发性;
进程有独立的地址空间,一个进程崩溃了,不会影响其他进程;线程只是进程中的一个执行路径;
线程有自己的堆栈和局部变量,没有独立的地址空间,所以一个线程崩溃了会导致整个进程奔溃;所以多进程的应用比多线程的应用健壮,但在切换进程的时候,消耗的资源比较多,效率较差;
对于一些要求同时进行并共享变量的并发操作,只能使用多线程,不能使用多进程;
33、简单说一说进程的同步机制有哪些?进程的死锁的原因?进程的通信方式?
同步机制:原子操作、信号量机制、自旋锁、管程、会合、分布式系统;
死锁原因:资源竞争、进程推进顺序非法;
通信方式:以文件系统为基础
34、谈谈死锁的必要条件以及死锁的解决方式
死锁的四个必要条件:互斥条件、请求与保持条件、不可剥夺条件、循环等待条件;
死锁的解决方式:预防死锁、避免死锁、检测与解除死锁
35、自动释放池是什么?如何工作?
自动释放池是由系统创建和维护的内存池;当我们使用autorelease的时候,系统自动将对象的引用添加到内存池中,并不会立即释放;所以任然和向对象发送消息;当程序运行到结束位置时,内存池会被释放,存放在内存池中的所有对象也会被释放;
36、Objective-C的优缺点
>优点
多态
运行时
分类
动态识别
指针计算
可与C++混编
弹性消息传递
>缺点
不支持命名空间
不支持运算符重载
不支持多重继承
37、sprintf,strcpy,memcpy使用上有什么要注意的地方
sprintf:字符串格式化函数,将一段数据通过特定的格式,格式化到字符串缓冲区;由于字符串长度不可控,可能会导致格式化后的字符串超出缓冲区的大小,造成溢出;
strcpy:字符串拷贝函数,由于字符串的长度不可控,容易导致拷贝函数出错;
memcpy:内存拷贝函数,这个函数长度可控,但会造成内存叠加的问题;
38、http和socket的区别
http:客户端采用http协议发送请求,需要封装请求头,并绑定请求体;客户端发送一次请求,服务端返回数据后,请求立即中断,一次请求完成。服务器不能主动向客户端发送消息(除非采用http长连接,通过在请求中设置connection:keep-alive)
socket:客户端与服务器主要是通过socket(套接字)进行连接,是一种长连接方式,客户端和服务器都可以主动发送消息;
39、TCP与UDP的区别
TCP:一种传输控制协议,它提供面向连接,可靠的数据传输协议;相对较安全
UDP:它不是面向连接的,不可控的数据传输协议,特点是速度快,但安全性一般
>数据传输协议主要功能:
a、确保IP数据包的成功传递;
b、对程序发送的大块数据进行分段和重组;
c、确保程序正确排序和按顺序传递分段的数据;
d、通过计算校验和,验证传输数据的完整性;
40、简单说说沙盒机制
沙盒机制是苹果提供的一种对应用程序数据的保护机制,每个应用都有一个独立的沙盒,不允许跨沙盒读写数据;
沙盒主要包含:
a、Documents目录:应用永久性数据一般存放在此目录
b、app目录:这是应用包目录,由于应用程序需要签名,所以在运行过程中不可修改此目录,可能会导致应用无法启动
c、tmp目录:临时文件存放目录,应用程序重新启动会清空
d、Library目录:包含Caches和Preferences两个目录,preferences目录主要存放应用程序的偏好设置,一般NSUserDefault保存的数据存放在此目录;Caches主要用来存放需要缓冲的数据,应用程序重新启动不会清空;
获取沙盒路径的方法:
// 获取沙盒主目录路径
NSHomeDirectory()
// 获取Documents路径
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
[NSString stringWithFormat:@"%@/Documents",NSHomeDirectory];
// 获取Libraray路径
NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask,YES);
[NSString stringWithFormat:@"%@/Library",NSHomeDirectory];
// 获取Caches路径
NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES);
[NSString stringWithFormat:@"%@/Library/Caches",NSHomeDirectory];
// 获取tmp路径
NSTemporaryDirectory();
[NSString stringWithFormat:@"%@/tmp",NSHomeDirectory];
常见的私有API:
>直接发送短信
>访问沙盒以外的磁盘文件
>根据包名判断APP是否存在
私有API详见
41、如果在项目中使用了私有API,如何绕过审核?
苹果审核禁止使用私有API,一般是通过一些关键字来进行搜索;可以先将类名、方法名、框架路径等内存进行加密处理,当使用私有API的时候,对其进行解密后在使用(仅供参考);
42、在一个对象中,self.name=@"test"和_name=@"test"有啥区别?
self.name会调用对象的setName()方法;
_name是直接将结果赋值给_name属性;
43、谈谈你对内存的理解
>内存分类(RAM和ROM)
RAM:运行内存,不能掉电存储;
ROM:存储内存,可以掉电存储;如:内存卡
RAM:运行速度远高于ROM,价格较高,CPU只能从RAM读取指令
ROM:app一般存储在ROM中,APP启动时,系统会将APP拷贝到RAM中
>内存分区
从高到低依次为:栈区、堆区、数据区(全局/静态变量和常量)和代码区;后面2个区域由程序运行加载
44、简述队列和栈的区别
队列:是一种先进先出的数据结构,它可以在两端进行操作,一端进入,一端出去;
栈:是一种先进后出的数据结构,它只可以在栈顶操作,进栈与出栈都在栈顶;
45、IOS的系统架构
IOS系统架构主要分为:核心系统层,核心服务层,媒体层和界面服务层;
46、控件主要响应的3种事件
基于触摸的事件、基于值的事件、基于编辑的事件;
47、动画有几种方式
>显式动画
显式动画是不存在的,如需显示需要创建;比如由beginsAnimation:context和commitAnimations创建
>隐式动画
隐式动画是一直存在的,如需关闭需要设置;比如由[UIView animateWithDuration:animations:]创建
48、实现简单表格UITableView的显示需要设置什么属性,实现什么协议?
需要设置其delegate和dataSource的属性;需要实现UITableViewDelegate和UITableViewDataSource协议
49、Cocoa Touch提供了哪几种Core Animation过渡类型?
4种:交叉淡化、挤推、覆盖和显示
50、简述UIView与CALayer的联系和区别
UIView是IOS系统中所有界面的基础单元,所有的界面元素都继承UIView;UIView是由Core Animation完成绘制的,其中有一个属性layer主要负责内容的绘制;UIView可以看做是CALayer的管理器,属性layer还可以设置子layer,即CALayer是可以嵌套的;
UIView主要负责内容的显示,CALayer主要负责内容的绘制;
51、Quatrz 2D的绘图功能的三个核心概念是什么并简述其作用
上下文:主要描述图层显示在哪里
路径:主要是图层显示的内容
状态:用于保存配置变化的值,填充、轮廓和alpha等:
52、Objective-C有几种手势通知的方法?
touchesBegan:withEvent: // 开始
touchesMoved:withEvent: // 移动
touchesEnded:withEvent: // 结束
touchesCancled:withEvent: // 取消
53、Core Foundation中提供了几种操作socket的方法
3种:CFNetwork、CFSocket和BSD socket
54、解析XML格式的方式有几种
3种:以DOM方式解析、以SAX方式解析和以XMLReader方式解析
55、谈谈MVC、MVVM和MVP的区别
>MVC
可分三层:
Model:模型层,用于存储和处理数据
View:展示层,用于显示数据视图
Controller:控制层,用于处理业务逻辑
数据流转:
View传递指令到Controller
Controller完成业务逻辑处理,通知Model状态改变
Model完成数据处理,将新的数据发送给View,用户得到反馈
结论:所有的通信都是单向的
>MVP
可分三层:
Model:模型层,用于存储和处理数据
View:展示层,用于显示数据视图
Presenter:用于处理业务逻辑
数据流转:
View传递指令到Presenter
Presenter完成业务逻辑处理,通知Model状态改变
Model完成数据处理,将新的数据发送给Presenter
Presenter接收新的数据,进行业务处理,最后发送给View,用户得到反馈
结论:所有通讯都是双向的,View与Model不直接发送联系,都是通过Presenter传递;View比较轻量化,但Presenter则较重;
>MVVM
可分三层:
Model:模型层,用于存储和处理数据
View:展示层,用于显示数据视图
ViewModel:用于处理业务逻辑
数据流转:
与MVP相比就是把Presenter换成了ViewModel,不同的是View与ViewModel之间实现的数据绑定,当ViewModel层数据发生变化,系统自动将数据传递到View层实现数据更新;
56、@property的本质是什么?ivar、getter和setter是如何生存并添加到类中的
@property的本质是ivar(实例变量)和getter、setter(存取方法)的集合
Objective-C中的对象往往需要采用ivar来保存数据,同时也需要setter写入数据值、getter获取数据值;
57、@property中有哪些属性关键字?@property后面可以有哪些修饰符?
@property按照属性特质可分五类:
a、原子性:nonatomic、atomic
b、读写性:readwrite、readonly
c、内存管理性:weak、retain、assgin、strong、copy、unsafe_unretained
d、方法名性:getter=<name>、setter=<name>
e、不常用:nullable、null_resettable、nonnull
58、什么情况下使用weak关键字?与assgin有什么不同?
在ARC中有可能出现循环引用的地方,可以使用weak来解决;比如:delegate可以使用weak修饰
如果已经对对象添加了强引用,此时不需要强引用可以使用weak修饰;比如:IBOutlet控件的属性一般使用weak修饰,因为父控件的subviews数据已经对控件做了强引用
assgin可以修饰非OC对象,而weak只能修饰OC对象;
weak对象在释放的时候,会自动将对象置为nil;而assgin不会,会变成野指针;
59、怎么用copy关键字?
Objective-C中的基本数据类型NSString、NSArray、NSDictionary可以采用copy修饰,因为他们都有对应的可变对象;
block也可以使用copy修饰
60、在Objective-C中经常使用copy修饰NSString、NSArray、NSDictionary,这是为什么?如果改成strong修饰,会有什么后果?
因为NSString、NSArray、NSDictionary是不可变对象,他们都有对应的可变对象;采用copy修饰,主要是为了完全复制一份新的,当被赋值的变量发生改变的时候,原来的那份一直可以保持不变;
如果使用strong,那么这个对象就有可能被改变
61、系统对象的copy与mutableCopy的区别
不管是系统的集合对象(NSArray,NSDictionary,NSSet等),还是非集合对象(NSString、NSNumber等)都遵循以下原则:copy:返回的是不可变对象;mutableCopy:返回的是可变对象;
非集合类对象:copy是指针复制,mutableCopy是内容复制;
集合类对象:对不可变对象进行copy是指针复制,mutableCopy是内容复制;对可变对象进行copy和mutableCopy都是内容复制;但内容复制进行对象本身,集合类的元素任然是指针复制;
总结:只有对不可变对象进行copy才是指针复制(浅复制),其他情况都是内容复制(深复制);
62、这个写法会出什么问题:@property (nonatomic, copy) NSMutableArray *arr;
因为采用了copy关键字,所以返回的是不可变对象;当进行对象的添加、删除原生等方法时,会导致程序崩溃;
63、如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
自定义的类要使用copy关键字,步骤如下:
a、必须实现NSCopying协议;如果对象分可变和不可变两个版本,就需要同时实现NSCopying和NSMutableCopying协议
b、实现协议方法:copyWithZone
重写带copy的setter:
-(void)setName:(NSString*) name{
_name = [name copy];
}
64、@synthesize 和 @dynamic 分别有什么作用?
@synthesize:合成实例变量,默认值;告诉编译器,如果没有手动实现getter、setter方法,系统自动实现;
@dynamic:告诉编译器,getter、setter方法用户自行实现,不需要系统自动实现
65、简述UIViewController的生命周期
initWithFrame: // 当从代码实例化view的时候调用
initWithCoder: // 当从文件实例化view的时候调用,比如nib(xib、storyboard)
awakeFromNib // 当从文件实例化view的时候,系统在完成initWithCoder之后,会调用此方法
loadView // 开始主动加载视图控制器自带的view
viewDidLoad // 视图view加载完成
viewWillAppear // 视图view即将显示在UIWindow上
updateViewConstraints // 视图view将更新autolayout的约束
viewWillLayoutSubviews // 视图view将更新子视图位置
viewDidLayoutSubviews // 视图view已经更新子视图位置
viewDidAppear // 视图view已经显示在UIWindow上
viewWillDisappear // 视图view即将从UIWindow上移除
viewDidDisappear // 视图view已经从UIWindow上移除
66、谈谈Objective-C中的反射机制?怎么用
>classs反射
通过类名的字符串实例化对象
Class class = NSClassFromString(@"NSObject");
NSObject* obj = [[class alloc] init];
将类名变为字符串
Class class2 = [obj class];
NSString* classStr = NSStringFromClass(class2);
>SEL反射
通过方法名的字符串实例化方法
SEL select = NSSelectorFromString(@"selector");
[obj preformSelector:select withObject:@""];
将方法名改为字符串
NSString* str = NSStringFromSelector(select);
67、Objective-C中调用方法的方式有几种
2种:通过方法名称直接调用;通过SEL的方式调用(preformSelector:withObject:)
68、类变量中的@public、@protected、@private和@package的区别
@public:所有类中都可以访问
@protected:本类和子类中可以访问
@private:仅本类可以访问
@package:本包内可以访问,跨包不能访问
69、说说你对isa指针的理解
isa指针是一个Class类型的指针,isa指针指向对象本身;而Class里面也有一个isa指针,它指向的是meteClass(对象的元类),元类中保存了对象的类方法;对象的元类也是类,它也有isa指针,元类的isa指针最终指向的是根元类,根元类的isa指针指向的是它本身;,这样就形成了一个闭环;如下图:
70、如何访问并修改一个类的私有属性
通过KVC的方式;通过runtime方式
71、一个OC对象在内存中占用多少空间
占用4个字节(32位)、8个字节(64位);因为OC对象实际上是一个Class类型的指针,那么它所占大小和指针的大小一致;
72、下面的代码输出什么?为什么?
@implementation Son : Father
- (id)init {
if (self = [super init]) {
NSLog(@"%@", NSStringFromClass([self class])); // Son
NSLog(@"%@", NSStringFromClass([super class])); // Son
}
return self;
}
@end
解析:
两者输出的都是Son,因为self本身是类的隐藏参数,所以它指向的累的实例对象;而super本身是一个标志符,和self是指向的同一个消息接收者,不同的是,super会告诉编译器采用父类的构造方法来回去实例对象,而不是本类里面的方法;
73、写一个完整的代理,包括声明与实现
// 创建
@protocol TestDelegate
@required // 必须实现
-(void) testMethod;
@optional // 可选
-(void) testMethod2;
@end
// 声明
@interfase TestClass : NSObject<TestDelegate>
@end
// 实现
@implementation TestClass
-(void) testMethod {
// 实现代码
}
@end
74、isKindOfClass、isMemberOfClass、selector作用分别是什么
isKindOfClass:判断对象类型属于某个类型或者继承自某个类型;
isMemberOfClass:判断对象类型是不是等于某个类型
selector:通过方法名获取方法的入口地址
75、lldb(gdb)常用的控制台调试命令?
p 基本数据类型的打印命令,需要指定类型;p (int)[self.view.subviews count]
po 对象的打印命令,会调用对象的description方法;po self.view
expr 可以在调试时动态执行指定表达式,并打印结果;常用于调试过程中修改变量的值
bt 打印堆栈的调用,加all可以打印所有Thread的堆栈信息
76、在Instruments中,你常用的工具有哪些?
Time Profiler:性能分析工具
Leaks:内存泄露检测工具
Allocations:内存分配检测工具
Zombies:僵尸对象检测工具
77、Objective-C中多线程的实现方式有哪些?
pthread、NSThread、GCD、NSOperation
78、谈谈GCD与NSOperation的区别
GCD:是基于C语言实现的API,一般结合block来实现,多用于简单的项目;
NSOperation:是Objective-C类型的对象,是基于GCD的高级封装,多用于比较复杂的项目,采用面向对象的方式管理线程;
79、写几个GCD中常用的方法
dispatch_once // 只执行一次,多用于创建单例类
dispatch_async // 创建异步线程
dispatch_sync // 创建同步线程
dispatch_semaphore // 信号量,多用于给线程加锁,避免死锁
dispatch_after // 延迟执行线程
dispatch_async(dispatch_get_main_queue,^{}) // 获取主线程
dispatch_async(dispatch_get_global_queue(0,0),^{}) // 创建子线程
80、用伪代码实现GCD,要求在n个图片下载完成后,合成为一张图片
// 创建线程分组
dispatch_group_t group = dispatch_group_create();
// 获取全局队列
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
// 将下载任务加到队列中
dispatch_group_async(group,queue,^{
// 图片下载任务1
});
dispatch_group_async(group,queue,^{
// 图片下载任务2
});
......
dispatch_group_async(group,queue,^{
// 图片下载任务n
});
// 所有图片都下载完成后,系统调用此方法通知图片合成
dispatch_group_notify(group,dispatch_get_main_queue,^{
// 图片合成代码
});
81、栅栏函数的作用是什么?
栅栏函数:dispatch_barrier_async(queue,^{});
特点:
在栅栏函数之前的任务执行完成,它才会执行;
在栅栏寒湿之后的任务,需要等待它执行完成才会执行;
栅栏函数主要用于避免资源竞争;
82、什么是Runloop?什么又是Runtime?
Runloop:是一中循环运行机制;一个线程对应一个Runloop,它的作用就是确保程序的持续运行,处理APP中的各种事件;其特点是:有任务执行就执行,没有任务执行就休眠,等有新的任务时在唤醒,不会造成CPU的浪费,提高性能;
Runtime:是一种运行时机制,它可以方便开发者通过代码的方式,在程序运行时动态的对类进行修改,包含添加、删除和修改类的方法和成员变量等;
83、Runtime的实现机制是什么?一般怎么使用?它可以做些什么事情?
使用的时候需要引入头文件:<objc/message.h>、<objc/runtime.h>
Runtime实际上是一套c语言库
实际上我们所编写的Objective-C代码,在运行时都会比转换为Runtime的内容;比如:
a、OC类在运行时,被转换成了Runtime的结构体
b、OC类的方法运行时,被转换成了Runtime的C语言函数
c、OC类的方法调用运行时,被转换成了objc_msgSend函数
Runtime可以做的事情:
a、获取类的所有成员变量和实例方法
b、动态为类添加成员变量
c、动态改变类的方法实现
d、动态为类添加新的方法等
84、什么是Method Swizzle,什么情况下使用它?
在没有类的源码情况下,想要对其中一个类的方法进行修改,除了了继承和分类强行修改,可以动态使用Method Swizzle来实现;
Method Swizzle的实现原理:
在OC中调用方法,实际是向该对象发送消息,而查找消息的唯一标识就是selector的名称,所以只要在运行时,我们动态修改selector的实际指向地址即可实现Method Swizzle;
Method Swizzle的实现方法:
可以采用method_exchangeImplementations来交换方法的IMP
可以利用class_replaceMethod来修改类
可以利用method_setImplementation来直接设置方法的IMP
85、_objc_msgForward函数的作用是什么?直接调用会有什么影响?
_objc_msgForward是IMP类型,向对象发送消息的时候,而它又没有实现的时候会调用_objc_msgForward进行消息转发;
直接调用可能会导致程序崩溃;
86、通信底层原来(OSI的分层)
物理层、数据链路层、网络层、传输层、会话层、表示层和应用层
87、OC中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?
创建线程的方法:
[self preformSelectorInBackground:nil withObject:nil];
[NSThread detachNewThreadSelector:nil toTarget:nil withObject:nil];
dispatch_async(dispatch_get_global_queue(0,0),^{});
[[NSThread alloc] initWithTarget:nil select:nil object:nil]
[[NSOperationQueue new] addOpreation:nil];
在主线程执行的方法:
dispatch_async(dispatch_get_main_queue(),^{});
[self preformSelectorOnMainThread:withObject:nil];
[[NSOpreationQueue mainQueue] addOpreation:nil];
88、说说UITableView的重用机制
UITableView是利用单元格的重用来节省内存;
通过给单元格设置标识符,当单元格滑出屏幕的时候,会将该单元格缓存在重用队列中;
当有新的单元格进入屏幕的时候,根据标识符,先从缓存的重用队列中获取,如果有直接使用,如果没有则创建;
89、用伪代码写一个线程安全的单例类
static id obj;
+(instance) getInstance {
static dispatch_once_t once;
dispatch_once(&once,^{
obj = [[self alloc] init];
});
return obj;
}
// 写此方法的目的,只要是确保通过alloc、init获取对象实例的时候,也只执行一次
+(id)allocWithZone:(struct _NSZone*) zone {
static dispatch_once_t once;
dispatch_once(&once,^{
obj = [super allocWithZone:zone];
});
return obj;
}
// 适当实现此方法,如果调用了copy会调用此方法
-(id)copyWithZone:(NSZone*) zone {
return obj;
}
90、如何实现视图的变形
通过修改视图的transform属性
91、常用的手势类型有哪些?哪些手势只会响应一次?
UITapGestureRecognizer——点击 //只会响应一次
UIPinchGestureRecognizer——捏合
UIRotationGestureRecognizer——旋转
UISwipeGestureRecognizer——轻扫 // 只会响应一次
UIPanGetureRecognizer——平移(拖动)
UIScreenEdgePanGestureRecognizer——屏幕边缘附近开始的平移(拖动)
UILongPressGestureRecognizer——长按
92、如何高效的给UIImageView添加圆角?
使用绘图技术:
- (UIImage *)circleImage {
// NO代表透明
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
// 获得上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 添加一个圆
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextAddEllipseInRect(ctx, rect);
// 裁剪
CGContextClip(ctx);
// 将图片画上去
[self drawInRect:rect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 关闭上下文
UIGraphicsEndImageContext();
return image;
}
使用贝塞尔曲线
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.center = CGPointMake(200, 300);
UIImage *anotherImage = [UIImage imageNamed:@"image"];
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds
cornerRadius:50] addClip];
[anotherImage drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
93、谈谈UITableView的优化
1). 正确的复用cell。
2). 设计统一规格的Cell
3). 提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;
4). 异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;
4). 滑动时按需加载,这个在大量图片展示,网络加载的时候很管用!
5). 减少子视图的层级关系
6). 尽量使所有的视图不透明化以及做切圆操作。
7). 不要动态的add 或者 remove 子控件。最好在初始化时就添加完,然后通过hidden来控制是否显示。
8). 使用调试工具分析问题。
94、如何实现UITableView中cell的动态行高
实现动态行高必须设置2个属性:预估行高和设置自定义行高
预估行高:tableview.estimatedRowHeight = 200;
设置自定义行高:tableview.rowHeight = UITableViewAutomaticDimension;
如果要自定义行高有效,必须让视图控制器有一个自上而下的约束
95、谈谈IOS中延迟执行的几种方式
preformSelector:withObject:afterDelay
preformSelector:onThread:withObject:waitUntilDone
[NSThread sleepForTimeInterval:3]
GCD中的dispatch_after(dispatch_time,queue,^{});
96、说一说OC中反向传值的方式
block传值
通知传值
代理传值
单例传值
extern传值
97、id与NSObject的区别
id可以修饰所有OC对象;NSObject修饰的必须是NSObject的子类或本类;OC中不是所有的对象都属于NSObject,还有一些继承自NSProxy
98、如何重写类方法
创建子类,并定义一个和基类一样名称的静态方法;在调用的时候,需要采用[[self class] methodName]来调用,因为用类名调用是早绑定,是在编译期间;用self class是晚绑定,是在运行时才确定
99、如何在定时器中调用静态方法
定时器中只能调用实例方法,但在实例方法中可以调用静态方法,在实例方法中采用[self class]的方式调用
100、NSTimer创建后在哪个线程执行?
用scheduledTimerWithTimeInterval创建的,在哪个线程创建就会加入到哪个线程的Runloop中就在哪个线程运行
自己创建的Timer,加入到那个线程的Runloop就在哪个线程执行
101、 runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
IMP class_getMethodImplementation(Class cls, SEL name);
IMP method_getImplementation(Method m)
实例方法与类方法的区别在于Class的获取:
objc_getClass // 实例方法
objc_getMateClass // 静态方法
102、runtime如何实现weak修饰的变量置为nil
weak的底层是hash表,以对象地址作为key,以对象作为value;
103、layoutSubviews和drawRect 的使用和区别
两个方法相同点
1 都是异步执行
2 都是UIView 的方法
两个方法不同点
1 layoutSubviews方便数据计算,
2 drawRect方便视图重绘。
layoutSubviews在以下情况下会被调用:
1、init初始化不会触发layoutSubviews
2、addSubview会触发layoutSubviews (向对象添加子视图,或者对象添加到父视图,frame为0时不会)
3、改变view的width和hight的时候会触发layoutSubviews
4、滚动一个UIScrollView会触发layoutSubviews(受contentSize 的影响)
5、旋转Screen会触发父UIView上的layoutSubviews事件
6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件
7、直接调用setLayoutSubviews。
layoutSubview的调用时机问题
- (void)viewWillAppear:(BOOL)animated; // 会调用一次layoutSubviews
- (void)viewDidAppear:(BOOL)animated; //触发了layoutSubviews 上面的几种情况,会调用layoutSubviews
什么时候用layoutSubviews
1仅仅在以下情况下:自动布局达不到想要效果时你才有必要重写这个方法.可以直接设置subviews的尺寸.
2不要直接调用这个方法,因为不会有任何的作用.如果你需要强制layout刷新,调用setNeedsLayout来代替,
如果你想要立即刷新你的view,调用layoutIfNeeded ,一个view是不能够自己调用layoutSubviews,如果要调用,需要调用setNeedsLayout或者 layoutIfNeeded
drawRect在以下情况下会被调用:
1、如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。
drawRect 调用是在Controller->loadView, Controller->viewDidLoad 两方法之后掉用的.所以不用担心在 控制器中,这些View的drawRect就开始画了.这样可以在控制器中设置一些值给View(如果这些View draw的时候需要用到某些变量 值).
2、该方法在调用sizeToFit后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。
3、通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。
4、直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0。以上1,2推荐;而3,4不提倡
drawRect方法使用注意点:
1、 若使用UIView绘图,只能在drawRect:方法中获取相应的contextRef并绘图。如果在其他方法中获取将获取到一个invalidate 的ref并且不能用于画图。drawRect:方法不能手动显示调用,必须通过调用setNeedsDisplay 或 者 setNeedsDisplayInRect,让系统自动调该方法。
2、若使用calayer绘图,只能在drawInContext: 中(类似鱼drawRect)绘制,或者在delegate中的相应方法绘制。同样也是调用setNeedDisplay等间接调用以上方法
3、若要实时画图,不能使用gestureRecognizer,只能使用touchbegan等方法来掉用setNeedsDisplay实时刷新屏幕