面向对象的三大特性:封装、继承、多态
OC内存管理
_strong
引用计数器来控制对象的生命周期。
_weak
不会持有对象,防止循环引用。
_autorelasing
自动释放池。
ARC
ARC会自动插入retain和release语句。ARC编译器有两部分,分别是前端编译器和优化器。
1. 前端编译器
前端编译器会为“拥有的”每一个对象插入相应的release语句。如果对象的所有权修饰符是__strong,那么它就是被拥有的。如果在某个方法内创建了一个对象,前端编译器会在方法末尾自动插入release语句以销毁它。而类拥有的对象(实例变量/属性)会在dealloc方法内被释放。事实上,你并不需要写dealloc方法或调用父类的dealloc方法,ARC会自动帮你完成一切。此外,由编译器生成的代码甚至会比你自己写的release语句的性能还要好,因为编辑器可以作出一些假设。在ARC中,没有类可以覆盖release方法,也没有调用它的必要。ARC会通过直接使用objc_release来优化调用过程。而对于retain也是同样的方法。ARC会调用objc_retain来取代保留消息。
2. ARC优化器
虽然前端编译器听起来很厉害的样子,但代码中有时仍会出现几个对retain和release的重复调用。ARC优化器负责移除多余的retain和release语句,确保生成的代码运行速度高于手动引用计数的代码。
runtime 中,SEL和IMP的区别
答:SEL:类成员的方法指针,不同于C中的函数指针,SEL只是一个编号。
IMP:函数指针,指向我们定义的函数。
每一个继承于NSObject的类都能自动获得runtime的支持。在这样的一个类中,有一个isa指针,指向该类定义的数据结构体,这个结构体是由编译器编译时为类(需继承于NSObject)创建的.在这个结构体中有包括了指向其父类类定义的指针以及 Dispatch table. Dispatch table是一张SEL和IMP的对应表。(http://blog.csdn.net/fengsh998/article/details/8614486)
也就是说方法编号SEL最后还是要通过Dispatch table表寻找到对应的IMP,IMP就是一个函数指针,然后执行这个方法
事件的传递与响应:
1、当一个事件发生后,事件会从父控件传给子控件,也就是说由UIApplication -> UIWindow -> UIView -> initial view,以上就是事件的传递,也就是寻找最合适的view的过程。
2、接下来是事件的响应。首先看initial view能否处理这个事件,如果不能则会将事件传递给其上级视图(inital view的superView);如果上级视图仍然无法处理则会继续往上传递;一直传递到视图控制器view controller,首先判断视图控制器的根视图view是否能处理此事件;如果不能则接着判断该视图控制器能否处理此事件,如果还是不能则继续向上传 递;(对于第二个图视图控制器本身还在另一个视图控制器中,则继续交给父视图控制器的根视图,如果根视图不能处理则交给父视图控制器处理);一直到 window,如果window还是不能处理此事件则继续交给application处理,如果最后application还是不能处理此事件则将其丢弃
3、在事件的响应中,如果某个控件实现了touches…方法,则这个事件将由该控件来接受,如果调用了[supertouches….];就会将事件顺着响应者链条往上传递,传递给上一个响应者;接着就会调用上一个响应者的touches….方法。
事件的传递和响应的区别:
事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件。
1、事件传递查找到用户点击的视图(C)后,当前视图(C)是否能够响应触摸事件(处理触摸事件)。
2、如果不能响应触摸事件,则交给当前视图(C)的父视图来响应。
3、如果父视图不能响应触摸事件,则由父视图的父视图来响应。
4、若最后谁都不对此触摸事件做处理,则丢弃此事件。
hitTest:用来找出最适合响应事件的视图。
1、首先在当前视图的hitTest方法中调用pointInside方法判断触摸点是否在当前视图内
2、若pointInside方法返回NO,说明触摸点不在当前视图内,则当前视图的hitTest返回nil,该视图不处理该事件
3、若pointInside方法返回YES,说明触摸点在当前视图内,则从最上层的子视图开始(即从subviews数组的末尾向前遍历),遍历当前视图的所有子视图,调用子视图的hitTest方法重复步骤1-3
4、直到有子视图的hitTest方法返回非空对象或者全部子视图遍历完毕
5、若第一次有子视图的hitTest方法返回非空对象,则当前视图的hitTest方法就返回此对象,处理结束
6、若所有子视图的hitTest方法都返回nil,则当前视图的hitTest方法返回当前视图本身,最终由该对象处理触摸事件
https://www.jianshu.com/p/4fea8fa60d75
属性的实质是什么?包括哪几个部分?属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的?
属性的实质:@property = ivar + getter + setter -> 实例变量+get方法+set方法,也就是说使用@property 系统会自动生成 成员变量以及setter和getter方法;
在普通的OC对象中,@property就是编译其自动帮我们生成一个私有的成员变量和setter与getter方法的声明和实现。
包括:实例变量+get方法+set方法
@synthesize和@dynamic分别有什么作用?
答:
@property有两个对应的词,一个是@synthesize,一个是@dynamic。如果@synthesize和@dynamic都没写,那么默认的就是@syntheszie var = _var;
@synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。
@dynamic告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成。(当然对于readonly的属性只需提供getter即可)。假如一个属性被声明为@dynamic var,然后你没有提供@setter方法和@getter方法,编译的时候没问题,但是当程序运行到instance.var =someVar,由于缺setter方法会导致程序崩溃;或者当运行到 someVar = var时,由于缺getter方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
tableview
tableview的delegate和datasource执行顺序
1.//有多少列
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
2.//cell 页眉高度
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
3.//cell页脚高度
-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
4.//每组有多少行
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
5.//cell高度
-(CGFloat)tableView:(UITableView )tableView heightForRowAtIndexPath:(NSIndexPath )indexPath
6.//布局UItableviewcell
-(UITableViewCell )tableView:(UITableView )tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
UITableViewCell的可重用机制
1.使用可重用机制创建cell(系统)
1)定义可重用标识
2)从可重用队列中取出cell
3)若队列中无可用cell,利用alloc,init新建cell
原理
在UITableView的头文件中有visibleCells,存放当前显示的的cells
@property (nonatomic,readonly)NSArray<__kindofUITableViewCell *> *visibleCells;
当需要更新显示数据时,dequeueReusableCellWithIdentifier会先在可重用cell队列 reusable-cell queue中返回一个cell对象,若不存在,则返回nil;
存在的问题
重取出来的cell是有可能已经捆绑过数据或者加过子视图的,造成视图叠加混乱的现象
解决:
1)删除已有数据或子视图
2)放弃了重用机制,每次根据indexPath获取对应的cell返回。
将方法: UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIndentifier];
替换为: UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
tableView滑动为什么会卡顿?
cell赋值内容时,会根据内容设置布局,也就可以知道cell的高度,若有1000行,就会调用1000次 cellForRow方法,而我们对cell的处理操作,都是在这个方法中赋值,布局等等,开销很大。
优化总结
1.提前计算并缓存好高度,因为heightForRow最频繁的调用。
2.异步绘制,遇到复杂界面,性能瓶颈时,可能是突破口。
3.滑动时按需加载,这个在大量图片展示,网络加载时,很管用。(SDWebImage已经实现异步加载)。
4.重用cells。
5.如果cell内显示得内容来自web,使用异步加载,缓存结果请求。
6.少用或不用透明图层,使用不透明视图。
7.尽量使所有的view opaque,包括cell本身。
8.减少subViews
9.少用addView给cell动态添加view,可以初始化的时候就添加,然后通过hide控制是否显示。
Objective-C 中 Block 有三种类型:
1、NSGlobalBlock
block内部没有引用外部变量的 Block 类型都是 NSGlobalBlock 类型,存储于全局数据区,由系统管理其内存,retain、copy、release操作都无效。
2、NSStackBlock
block内部引用外部变量,retain、release 操作无效,存储于栈区,变量作用域结束时,其被系统自动释放销毁。MRC 环境下,[[mutableAarry addObject: blockA],(在arc中不用担心此问题,因为arc中会默认将实例化的block拷贝到堆上)在其所在作用域结束也就是函数出栈后,从mutableAarry中取到的blockA已经被回收,变成了野指针。正确的做法是先将blockA copy到堆上,然后加入数组。支持copy,copy之后生成新的NSMallocBlock类型对象。
3、NSMallocBlock
如上例中的_block,[blockA copy]操作后变量类型变为 NSMallocBlock,支持retain、release,虽然 retainCount 始终是 1,但内存管理器中仍然会增加、减少计数,当引用计数为零的时候释放(可多次retain、release 操作验证)。copy之后不会生成新的对象,只是增加了一次引用,类似retain,尽量不要对Block使用retain操作。
http://www.cocoachina.com/ios/20150601/11970.html
runloop
一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
作用:
1、等待用户操作
2、决定处理事件
3、调用解耦
4、节省cpu时间(没事的时候等待)
一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,所以,RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 "接受消息->等待->处理" 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。
NSTimer与Runloop的关系
我们前面做演示的代码创建的NSTimer会默认为我们添加到Runloop的NSDefaultRunLoopMode中,而且由于是在主线程中,所以Runloop是开启的,不需要我们手动打开。
在我们进行多线程编程时,所有的Source都需要添加到Runloop中才能生效,对于我们的NSTimer当然也需要添加到Runloop中才能生效,NSTimer也在source里。如果一个Runloop中没有任何Source的话,会立即退出的。而主线程的Runloop在程序运行时,系统就已经为我们添加了很多Source到Runloop中,所以主线程的Runloop是一直存在的,我们可以通过打印MainThread中的Runloop来查看所包含的Source。
NSTimer添加到Runloop中,但是不运行
在iOS多线程中,每一个线程都有一个Runloop,但是只有主线程的Runloop默认是打开的,其他子线程也就是我们创建的线程的Runloop默认是关闭的,需要我们手动运行。
我们可以通过[NSRunLoop currentRunLoop]来获得当前线程的Runloop,并且调用[runloop addTimer:timer forMode:NSDefaultRunLoopMode]方法将定时器添加到Runloop中,最后一定不要忘记调用Runloop的run方法将当前Runloop开启,否则NSTimer永远也不会运行。
https://www.jianshu.com/p/32265cbb2a26
Autorelease
UIKit通过RunLoopObserver在Runloop俩次sleep间对AutoreleasePool进行pop和push,将这次loop中产生的Autorelease对象释放。
AutoreleasePoolPage由双向链表链接,每个AutoreleasePoolPage所能存储的对象有限,当一个AutoreleasePoolPage满时会创建另一个AutoreleasePoolPage的对象来存放对象。
objc_autoreleasePoolPush 方法:
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
它调用 AutoreleasePoolPage 的类方法 push,也非常简单:
static inline void *push() {
return autoreleaseFast(POOL_SENTINEL);
}
在这里会进入一个比较关键的方法 autoreleaseFast,并传入哨兵对象 POOL_SENTINEL:
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
上述方法分三种情况选择不同的代码执行:
有 hotPage 并且当前 page 不满
调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
有 hotPage 并且当前 page 已满
调用 autoreleaseFullPage 初始化一个新的页
调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
无 hotPage
调用 autoreleaseNoPage 创建一个 hotPage
调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
最后的都会调用 page->add(obj) 将对象添加到自动释放池中。
hotPage 可以理解为当前正在使用的 AutoreleasePoolPage。
page->add 添加对象
autoreleaseFullPage(当前 hotPage 已满)
autoreleaseFullPage 会在当前的 hotPage 已满的时候调用:
static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
它会从传入的 page 开始遍历整个双向链表,直到:
1、查找到一个未满的 AutoreleasePoolPage
或者
2、使用构造器传入 parent 创建一个新的 AutoreleasePoolPage
在查找到一个可以使用的 AutoreleasePoolPage 之后,会将该页面标记成 hotPage,然后调动上面分析过的 page->add 方法添加对象。
autoreleaseNoPage(没有 hotPage)
如果当前内存中不存在 hotPage,就会调用 autoreleaseNoPage 方法初始化一个 AutoreleasePoolPage:
static id *autoreleaseNoPage(id obj) {
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
if (obj != POOL_SENTINEL) {
page->add(POOL_SENTINEL);
}
return page->add(obj);
}
既然当前内存中不存在 AutoreleasePoolPage,就要从头开始构建这个自动释放池的双向链表,也就是说,新的 AutoreleasePoolPage 是没有 parent 指针的。
初始化之后,将当前页标记为 hotPage,然后会先向这个 page 中添加一个 POOL_SENTINEL 对象,来确保在 pop 调用的时候,不会出现异常。
最后,将 obj 添加到自动释放池中。
多线程
NSThread
这套方案是经过苹果封装后的,并且完全面向对象的。所以你可以直接操控线程对象,非常直观和方便。但是,它的生命周期还是需要我们手动管理。
–优点:NSThread 比其他两个轻量级,使用简单
–缺点:需要自己管理线程的生命周期、线程同步、加锁、睡眠以及唤醒等。线程同步对数据的加锁会有一定的系统开销
https://www.cnblogs.com/wendingding/p/3806821.html
GCD
GCD有两种队列:串行队列和并行队列。
它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉干什么就行。同时它使用的也是 c语言,不过由于使用了 Block(Swift里叫做闭包),使得使用起来更加方便,而且灵活。
说明:同步函数不具备开启线程的能力,无论是什么队列都不会开启线程;异步函数具备开启线程的能力,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)
同步函数
(1)并发队列:不会开线程
(2)串行队列:不会开线程
异步函数
(1)并发队列:能开启N条线程
(2)串行队列:开启1条线程
https://www.cnblogs.com/zh-li/p/5140610.html
http://ios.jobbole.com/82622/
GCD死锁的几种情况
经典:
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
分析:
1 dispatch_sync表示是一个同步线程;
2 dispatch_get_main_queue表示运行在主线程中的主队列;
3 任务2是同步线程的任务。
首先执行任务1,这是肯定没问题的,只是接下来,程序遇到了同步线程,那么它会进入等待,等待任务2执行完,然后执行任务3。但这是队列,有任务来,当然会将任务加到队尾,然后遵循FIFO原则执行任务。那么,现在任务2就会被加到最后,任务3排在了任务2前面,
问题来了:
任务3要等任务2执行完才能执行,任务2由排在任务3后面,意味着任务2要在任务3执行完才能执行,所以他们进入了互相等待的局面。【既然这样,那干脆就卡在这里吧】这就是死锁。
NSOperation和NSOperationQueue
NSOperation 是苹果公司对 GCD 的封装,完全面向对象,不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上,所以使用起来更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列 。
NSOperation 只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperation 和 NSBlockOperation 。
NSOperationQueue与cancel
对于NSOperationQueue,只有add到NSOperationQueue的NSOperation才能够执行,关于cancel,NSOperation的cancel 并不是立即取消掉,而是过一会,
最重要的是无论是NSOperation的cancel还是NSOperationQueue的cancelAllOperations,都不会停止正在执行的operation,只能取消在队列中等待的operation
NSOperationQueue串行队列
NSOperationQueue可以设置设置线程池中的线程数,也就是并发操作数。默认情况下是-1,也就是没有限制,同时运行队列中的全部操作。设置为1即为串行,即当前只能执行单个task,以及FIFO原则执行完了之后再执行下一个。
OC 中load方法和initialize方法的异同
+load:
对于加入运行期系统中的每个类(class)及分类(category)来说,都会调用此方法,且只会调用一次。如果分类和其所属的类都调用了load方法,则先调用类里面的,再调用分类里的。
load方法并不像普通方法那样,它并不遵从继承规则。即如果某个类本身没有load方法,那么不管其超类是否实现load方法,系统都不会调用。
子类 +load 方法等父类先执行完 +load 方法才执行。
分类 +load 方法会在它的主类 +load 方法之后执行。
+initialize:
对每个类来说,该方法会在程序首次使用该类前调用,且只调用一次。它是由运行期系统来调用的,绝不应该通过代码直接调用。
initialize方法与其他消息一样,如果某个类未实现它,而其超类实现了,就会运行超类的实现代码。
load和initialize之间的区别如下:
initialize是”惰性调用的”,即只有当用到了相关的类时,才会调用。如果某个类一直都没有使用,则其initialize方法就一直不会运行。这也就是说,应用程序无须把每个类的initialize都执行一遍。
这就与load不同,对于load来说,应用程序必须阻塞并等待所有类的load都执行完,才能继续。
initialize在运行期系统执行该方法时,是处于正常状态,因此从运行期系统完整度上来讲,此时可以安全使用并调用任意类中的任意方法。而且,运行期系统也能保证initialize方法一定会在“线程安全的环境(thread-safe environment)”中执行,这就是说,只有执行initialize的那个线程可以操作类或者类实例。其他线程都要先阻塞,等着initialize执行完。
load方法的问题在于,执行该方法时,运行期系统处于“脆弱状态(fragile state)”。在执行子类的load方法之前,必定会执行所有超类的load方法。
https://www.jianshu.com/p/14efa33b3562
Block
// OC代码如下
void(^myBlock)() = ^{
NSLog(@"global = %d", global);
};
// 转为C++代码如下
void(*myBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, global));
将变量类型精简之后C++代码如下,我们发现Block变量实际上就是一个指向结构体__main_block_impl_0的指针
_block修饰符
__block在MRC下有两个作用
1. 允许在Block中访问和修改局部变量
2. 禁止Block对所引用的对象进行隐式retain操作
__block在ARC下只有一个作用
允许在Block中访问和修改局部变量
_block修饰符同样可以解决block的循环引用,但是有个必须执行的过程,详见第131页
SDWebImage
01 设置imageView的图片
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon]
placeholderImage:[UIImage imageNamed:@"placehoder"]];
// 02 设置图片并计算下载进度
//下载并设置图片
/*
第一个参数:要下载图片的url地址
第二个参数:设置该imageView的占位图片
第三个参数:传一个枚举值,告诉程序你下载图片的策略是什么
第一个block块:获取当前图片数据的下载进度
receivedSize:已经下载完成的数据大小
expectedSize:该文件的数据总大小
第二个block块:当图片下载完成之后执行该block中的代码
image:下载得到的图片数据
error:下载出现的错误信息
SDImageCacheType:图片的缓存策略(不缓存,内存缓存,沙盒缓存)
imageURL:下载的图片的url地址
*/
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon]
placeholderImage:[UIImage imageNamed:@"placehoder"]
options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize) {
//计算当前图片的下载进度
NSLog(@"%.2f",1.0 *receivedSize / expectedSize);
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
}];
// 03 系统级内存警告如何处理(面试)
// 利用通知中心观察(监听系统警告)
- UIApplicationDidReceiveMemoryWarningNotification接收到内存警告的通知
执行 clearMemory 方法,清理内存缓存!
- UIApplicationWillTerminateNotification接收到应用程序将要终止通知
执行 cleanDisk 方法,清理磁盘缓存!(按照文件创建顺序:从远到近)先删除过去的缓存,计算当前缓存的大小
maxCacheSize设置缓存的大小,默认为0
比较是否大于设定的缓存大小,如果超过,则继续删除,直到小于为止
- UIApplicationDidEnterBackgroundNotification接收到应用程序进入后台通知
执行 backgroundCleanDisk 方法,后台清理磁盘!
通过以上退出的通知,能够保证缓存文件的大小始终在控制范围之内!
clearDisk 清空磁盘缓存,将所有缓存目录中的文件,全部删除!
实际工作,将缓存目录直接删除,再次创建一个同名空目录!
04 SDWebImage默认的缓存时间是: 1周
05 SDWebImage的内存缓存是用什么实现的: NSCache
06 SDWebImag的最大并发数是: maxConcurrentDownloads = 6是程序固定死了,可以通过属性进行调整
07 如何播放gif图片
/*
5-1 把用户传入的gif图片->NSData
5-2 根据该Data创建一个图片数据源(NSData->CFImageSourceRef)
5-3 计算该数据源中一共有多少帧,把每一帧数据取出来放到图片数组中
5-4 根据得到的数组+计算的动画时间-》可动画的image
[UIImage animatedImageWithImages:images duration:duration];
*/
08 如何判断当前图片类型
+ (NSString *)sd_contentTypeForImageData:(NSData *)data;
09 SDWebImage是如何区分不同格式的图像的
根据图像数据第一个字节来判断的!
PNG:压缩比没有JPG高,但是无损压缩,解压缩性能高,苹果推荐的图像格式!
JPG:压缩比最高的一种图片格式,有损压缩,压缩的性能不好!最多使用的场景,照相机解
GIF:序列桢动图,特点:只支持256种颜色!最流行的时候在1998~1999,有专利的
10 图片下载顺序(先进先出 | 先进先出) 默认是先进先出
下载了图片后为什么要解码?
答:由于UIImage的imageWithData函数是每次画图的时候才将Data解压成ARGB的图像,所以在每次画图的时候,会有一个解压操作,这样效率很低,但是只有瞬时的内存需求。
为了提高效率通过SDWebImageDecoder将包装在Data下的资源解压,然后画在另外一张图片上,这样这张新图片就不再需要重复解压了。这种做法是典型的空间换时间的做法。
UIView的生命周期总结
1 -[ViewController initWithCoder:]或-[ViewController initWithNibName:Bundle]:首先从归档文件中加载UIViewController对象。即使是纯代码,也会把nil作为参数传给后者。
2 -[UIView awakeFromNib]:作为第一个方法的助手,方便处理一些额外的设置。
3 -[ViewController loadView]:创建或加载一个view并把它赋值给UIViewController的view属性
4 -[ViewController viewDidLoad]:此时整个视图层次(view hierarchy)已经被放到内存中,可以移除一些视图,修改约束,加载数据等
5 -[ViewController viewWillAppear:]:视图加载完成,并即将显示在屏幕上,还没有设置动画,可以改变当前屏幕方向或状态栏的风格等。
6 -[ViewController viewWillLayoutSubviews]:即将开始子视图位置布局
7 -[ViewController viewDidLayoutSubviews]:用于通知视图的位置布局已经完成
8 -[ViewController viewDidAppear:]:视图已经展示在屏幕上,可以对视图做一些关于展示效果方面的修改。
9 -[ViewController viewWillDisappear:]:视图即将消失
10 -[ViewController viewDidDisappear:]:视图已经消失
如果考虑UIViewController可能在某个时刻释放整个view。那么再次加载视图时显然会从步骤3开始。因为此时的UIViewController对象依然存在。
为什么IBOutlet修饰的UIView也适用weak关键字?
我们将控件拖到Storyboard上,相当于创建了一个对象,而这个对象是加到试图控制器的view上,存放在view的subviews数组中。即我们的控件对象是属于的view的,view对其子控件之前的关系是强引用。当我们使用Outlet属性的时候,这个Outlet属性是有view来进行强引用的。我们是在viewController中仅仅对其进行使用,没有必要拥有它,所以使用weak进行修饰。
nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?
nonatomic和atomic用来决定编译器生成的getter和setter操作是否为原子操作。atomic不是绝对的线程安全。atomic的本意是指属性的存取方法是线程安全的,并不保证整个对象是线程安全的。如:
声明一个NSMutableArray的原子属性stuff,此时self.stuff 和 self.stuff = otherstuff都是线程安全的。但是使用[self.stuff objectAtIndex:index]就不是线程安全的。需要用互斥锁来保证线程安全性。
NSCache
1)NSCache是苹果官方提供的缓存类,具体使用和NSMutableDictionary类似,在AFN和SDWebImage框架中被使用来管理缓存
2)苹果官方解释NSCache在系统内存很低时,会自动释放对象(但模拟器演示不会释放)
建议:接收到内存警告时主动调用removeAllObject方法释放对象
3)NSCache是线程安全的,在多线程操作中,不需要对NSCache加锁
4)NSCache的Key只是对对象进行Strong引用,不是拷贝,在清理的时候计算的是实际大小而不是引用的大小
NSCache优于NSDictionary的几点?
NSCache胜过NSDictionary之处在于,当系统资源将要耗尽时,它可以自动删减缓存。如果采用普通的字典,那么就要自己编写挂钩,在系统发出“低内存”通知时手工删减缓存。
NSCache并不会“拷贝”键,而是会“保留”它。此行为用NSDictionary也可以实现,然而需要编写相当复杂的代码。NSCache对象不拷贝键的原因在于:很多时候,键都是不支持拷贝操作的对象来充当的。因此,NSCache不会自动拷贝键,所以说,在键不支持拷贝操作的情况下,该类用起来比字典更方便。另外,NSCache是线程安全的,而NSDictionary则绝对不具备此优势。
description方法
https://www.jianshu.com/p/08b59e425d2c
什么时候会报unrecognized selector错误?
1 对象未实现该方法。
2 对象已经被释放。
能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
不能,能。
因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表 和 instance_size 实例变量的内存大小已经确定,同时runtime 会调用 class_setIvarLayout 或 class_setWeakIvarLayout 来处理 strong weak 引用。所以不能向存在的类中添加实例变量;
运行时创建的类是可以添加实例变量,调用 class_addIvar 函数。但是得在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。
UITableview的优化方法
缓存高度,异步绘制,减少层级,hide,减少空间,估算高度,避免离屏渲染。
有没有用过运行时,用它都能做什么?
交换方法,创建类,给新创建的类增加方法,改变isa指针
5、AFN为什么添加一条常驻线程?
https://www.jianshu.com/p/e59bb8f59302
KVO的使用?实现原理?(为什么要创建子类来实现)
当观察某对象 A 时,KVO 机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性 keyPath 的 setter 方法。setter 方法随后负责通知观察对象属性的改变状况。
深入剖析:
Apple使用了 isa 混写(isa-swizzling)来实现 KVO 。当观察对象A时,KVO机制动态创建一个新的名为:NSKVONotifying_A 的新类,该类继承自对象A的本类,且 KVO 为 NSKVONotifying_A 重写观察属性的 setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象属性值的更改情况。
(备注: isa 混写(isa-swizzling)isa:is a kind of ; swizzling:混合,搅合;)
①NSKVONotifying_A 类剖析:在这个过程,被观察对象的 isa 指针从指向原来的 A 类,被 KVO 机制修改为指向系统新创建的子类 NSKVONotifying_A 类,来实现当前类属性值改变的监听;
所以当我们从应用层面上看来,完全没有意识到有新的类出现,这是系统“隐瞒”了对 KVO 的底层实现过程,让我们误以为还是原来的类。但是此时如果我们创建一个新的名为“NSKVONotifying_A”的类(),就会发现系统运行到注册 KVO 的那段代码时程序就崩溃,因为系统在注册监听的时候动态创建了名为 NSKVONotifying_A 的中间类,并指向这个中间类了。
(isa 指针的作用:每个对象都有 isa 指针,指向该对象的类,它告诉 Runtime 系统这个对象的类是什么。所以对象注册为观察者时,isa 指针指向新子类,那么这个被观察的对象就神奇地变成新子类的对象(或实例)了。) 因而在该对象上对 setter 的调用就会调用已重写的 setter,从而激活键值通知机制。
—>我猜,这也是 KVO 回调机制,为什么都俗称KVO技术为黑魔法的原因之一吧:内部神秘、外观简洁。
②子类setter方法剖析:KVO 的键值观察通知依赖于 NSObject 的两个方法:willChangeValueForKey:和 didChangevlueForKey:,在存取数值的前后分别调用 2 个方法:
被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该 keyPath 的属性值即将变更;当改变发生后, didChangeValueForKey: 被调用,通知系统该 keyPath 的属性值已经变更;之后, observeValueForKey:ofObject:change:context: 也会被调用。且重写观察属性的 setter 方法这种继承方式的注入是在运行时而不是编译时实现的。
KVO为子类的观察者属性重写调用存取方法的工作原理在代码中相当于:
-(void)setName:(NSString *)newName{
[self willChangeValueForKey:@"name"]; //KVO在调用存取方法之前总调用
[super setValue:newName forKey:@"name"]; //调用父类的存取方法
[self didChangeValueForKey:@"name"]; //KVO在调用存取方法之后总调用
}
KVC的使用?实现原理?(KVC拿到key以后,是如何赋值的?知不知道集合操作符,能不能访问私有属性,能不能直接访问_ivar)
kvc最常见的两种用法就是:1,对私有变量进行赋值 2,字典转模型
http://blog.csdn.net/coyote1994/article/details/52454600
http://hl1987.com/2015/12/16/理解Objective-C中的消息发送/
objective—c消息转发机制
objc_msgSend的具体流程如下:
1、通过isa指针找到所属类
2、查找类的cache列表, 如果没有则下一步
3、查找类的”方法列表”
4、如果能找到与选择子名称相符的方法, 就跳至其实现代码
5、找不到, 就沿着继承体系继续向上查找
6、如果能找到与选择子名称相符的方法, 就跳至其实现代码
7、找不到, 执行”消息转发”.
消息转发
上面我们提到, 如果到最后都找不到, 就会来到消息转发,消息转发的流程如下:
1、动态方法解析 : 先问接收者所属的类, 你看能不能动态添加个方法来处理这个”未知的消息”? 如果能, 则消息转发结束.
2、备胎(后备接收者) : 请接收者看看有没有其他对象能处理这条消息? 如果有, 则把消息转给那个对象, 消息转发结束.
3、消息签名 : 这里会要求你返回一个消息签名, 如果返回nil, 则消息转发结束.
4、完整的消息转发 : 备胎都搞不定了, 那就只能把该消息相关的所有细节都封装到一个NSInvocation对象, 再问接收者一次, 快想办法把这个搞定了. 到了这个地步如果还无法处理, 消息转发机制也无能为力了。
OC中给空对象发送消息程序会Crash吗?
首先,OC中向nil发消息,程序是不会崩溃的。
因为OC的函数调用都是通过objc_msgSend进行消息发送来实现的,相对于C和C++来说,对于空指针的操作会引起Crash的问题,而objc_msgSend会通过判断self来决定是否发送消息,如果self为nil,那么selector也会为空,直接返回,所以不会出现问题。视方法返回值,向nil发消息可能会返回nil(返回值为对象)、0(返回值为一些基础数据类型)或0X0(返回值为id)等。但是对[NSNull null]对象发送消息时,是会crash的,因为这个NSNull类只有一个null方法。
当然,如果一个对象已经被释放了(引用计数为0了),那么这个时候再去调用方法肯定是会Crash的,因为这个时候这个对象就是一个野指针(指向僵尸对象(对象的引用计数为0,指针指向的内存已经不可用)的指针)了,安全的做法是释放后将对象重新置为nil,使它成为一个空指针,大家可以在关闭ARC后手动release对象验证一下。
iOS数据加密方式
md5加密、aes加密、base64加密
delegate、notification、KVO各优缺点
delegate 的 优势 :
1.非常严格的语法。所有将听到的事件必须是在delegate协议中有清晰的定义。
2.如果delegate中的一个方法没有实现那么就会出现编译警告/错误
3.协议必须在controller的作用域范围内定义
4.在一个应用中的控制流程是可跟踪的并且是可识别的;
5.在一个控制器中可以定义定义多个不同的协议,每个协议有不同的delegates
6.没有第三方对象要求保持/监视通信过程。
7.能够接收调用的协议方法的返回值。这意味着delegate能够提供反馈信息给controller
缺点 :
1.需要定义很多代码:1.协议定义;2.controller的delegate属性;3.在delegate本身中实现delegate方法定义
2.在释放代理对象时,需要小心的将delegate改为nil。一旦设定失败,那么调用释放对象的方法将会出现内存crash
3.在一个controller中有多个delegate对象,并且delegate是遵守同一个协议,但还是很难告诉多个对象同一个事件,不过有可能。
notification 的 优势 :
1.不需要编写多少代码,实现比较简单;
2.对于一个发出的通知,多个对象能够做出反应,即1对多的方式实现简单
3.controller能够传递context对象(dictionary),context对象携带了关于发送通知的自定义的信息
缺点 :
1.在编译期不会检查通知是否能够被观察者正确的处理;
2.在释放注册的对象时,需要在通知中心取消注册;
3.在调试的时候应用的工作以及控制过程难跟踪;
4.需要第三方对喜爱那个来管理controller与观察者对象之间的联系;
5.controller和观察者需要提前知道通知名称、UserInfo dictionary keys。如果这些没有在工作区间定义,那么会出现不同步的情况;
6.通知发出后,controller不能从观察者获得任何的反馈信息。
KVO 的 优势 :
1.能够提供一种简单的方法实现两个对象间的同步。例如:model和view之间同步;
2.能够对非我们创建的对象,即内部对象的状态改变作出响应,而且不需要改变内部对象(SKD对象)的实现;
3.能够提供观察的属性的最新值以及先前值;
4.用key paths来观察属性,因此也可以观察嵌套对象;
5.完成了对观察对象的抽象,因为不需要额外的代码来允许观察值能够被观察
缺点 :
1.我们观察的属性必须使用strings来定义。因此在编译器不会出现警告以及检查;
2.对属性重构将导致我们的观察代码不再可用;
3.复杂的“IF”语句要求对象正在观察多个值。这是因为所有的观察代码通过一个方法来指向;
4.当释放观察者时不需要移除观察者。
1. 效率 肯定是delegate比NSNotification高。
delegate方法比notification更加直接,最典型的特征是,delegate方法往往需要关注返回值, 也就是delegate方法的结果。比如-windowShouldClose:,需要关心返回的是yes还是no。所以delegate方法往往包含 should这个很传神的词。也就是好比你做我的delegate,我会问你我想关闭窗口你愿意吗?你需要给我一个答案,我根据你的答案来决定如何做下一 步。相反的,notification最大的特色就是不关心接受者的态度, 我只管把通告放出来,你接受不接受就是你的事情,同时我也不关心结果。所以notification往往用did这个词汇,比如 NSWindowDidResizeNotification,那么NSWindow对象放出这个notification后就什么都不管了也不会等待接受者的反应。
2、KVO和NSNotification的区别 :
和delegate一样,KVO和NSNotification的作用也是类与类之间的通信,与delegate不同的是1)这两个都是负责发出通知,剩下的事情就不管了,所以没有返回值;2)delegate只是一对一,而这两个可以一对多。这两者也有各自的特点。
NSUrlSession与NSURLConnection区别
https://www.jianshu.com/p/5c09d6976d8b
为什么弃用NSURLConnection?
1、NSUrlSession不用每次都新建tcp链接。2、NSUrlSession速度更快。3、NSUrlSession更加安全。
https://www.jianshu.com/p/079e5cf0f014
UIView 和 CALayer 的关系如何?他们分别负责什么功能?为什么这样设计?
1.首先UIView可以响应事件,Layer不可以.
2.一个 Layer 的 frame 是由它的 anchorPoint,position,bounds,和 transform 共同决定的,而一个 View 的 frame 只是简单的返回 Layer的 frame,同样 View 的 center和 bounds 也是返回 Layer 的一些属性。
3.UIView主要是对显示内容的管理而 CALayer 主要侧重显示内容的绘制。
4.在做 iOS 动画的时候,修改非 RootLayer的属性(譬如位置、背景色等)会默认产生隐式动画,而修改UIView则不会。
view负责了与人的动作交互以及对layer的管理,layer则负责了所有能让人看到的东西。
uiview super class:UIResponder
calayer super class:NSObject
uiresponder super class:NSObject
http://www.cnblogs.com/flyFreeZn/p/4264220.html
ARC内存管理技术要点
https://www.jianshu.com/p/17e158a666b1
https://www.jianshu.com/p/927c8384855a
runtime机制
我们写的代码在程序运行过程中都会被转化成runtime的C代码执行,例如[target doSomething];会被转化成objc_msgSend(target, @selector(doSomething));。
OC中一切都被设计成了对象,我们都知道一个类被初始化成一个实例,这个实例是一个对象。实际上一个类本质上也是一个对象,在runtime中用结构体表示。
//类在runtime中的表示
struct objc_class {
Class isa;//指针,顾名思义,表示是一个什么,
//实例的isa指向类对象,类对象的isa指向元类
#if !__OBJC2__
Class super_class; //指向父类
const char *name; //类名
long version;
long info;
long instance_size
struct objc_ivar_list *ivars //成员变量列表
struct objc_method_list **methodLists; //方法列表
struct objc_cache *cache;//缓存
//一种优化,调用过的方法存入缓存列表,下次调用先找缓存
struct objc_protocol_list *protocols //协议列表
#endif
} OBJC2_UNAVAILABLE;
https://www.jianshu.com/p/a6b675f4d073
方法交换
OC中每个方法的名字(SEL)跟函数的实现(IMP)是一一对应,Swizzle的原理只是在这个地方做下手脚,将原来方法名与实现的指向交叉处理,就能达到一个新的效果。
静态库和动态库
framework为什么既是静态库又是动态库?
答:系统的.framework是动态库,我们自己建立的.framework是静态库。
a与.framework有什么区别?
答:.a是一个纯二进制文件,.framework中除了有二进制文件之外还有资源文件。
.a文件不能直接使用,至少要有.h文件配合,.framework文件可以直接使用。
.a + .h + sourceFile = .framework。
建议用.framework.
FMDB
在多个线程中同时使用一个FMDatabase实例是不明智的。不要让多个线程分享同一个FMDatabase实例,它无法在多个线程中同时使用。 如果在多个线程中同时使用一个FMDatabase实例,会造成数据混乱等问题。所以,请使用 FMDatabaseQueue,它是线程安全的
UIView生命周期
loadView方法
loadView方法在UIViewController对象的view属性被访问到且为空的时候调用。这个方法不应该被直接调用,而是由系统自动调用。它会加载或创建一个view并把它赋值给UIViewController的view属性。
viewDidLoad方法
@synthesize 和 @dynamic 分别有什么作用?
1 @property有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize 和 @dynamic 都没写,那么默认的就是 @syntheszie var = _var;
2 @synthesize的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。
3 @dynamic告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter 方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
objc中的类方法和实例方法有什么本质区别和联系
类方法:
1,类方法是属于类对象的 2,类方法只能通过类对象调用 3,类方法中的self是类对象 4,类方法中不能访问成员变量 5,类方法可以调用其他的类方法 6,类方法中不能直接调用对象方法
实例方法:
1,实例方法是属于实例对象的 2,实例方法只能通过实例对象调用,3,实例方法中的self是实例对象 4,实例方法中可以访问成员变量 5,实例方法中直接调用实例方法6,实例方法中可以直接调用类方法(通过类名)
runtime如何通过selector找到对应的IMP地址?
答:selector是通过方法名去查找,而在oc中,类因为runtime会被转化成结构体,结构体里的成员变量 structobjc_method_list **methodLists 里面存储的是类方法列表和实例方法列表,所以在runtime中,selector是通过找方法列表去找到对应的imp地址。
struct objc_method_list **methodLists 存储的内容:http://blog.csdn.net/nynllwp/article/details/49926619
1.首先到该类的方法cache中去寻找,如果找到了,返回改函数
2.如果没有找到就到该类的方法列表中查找,如果找到了,将该IMP返回并且将它加入到cache中缓存起来,这样可以节省再次调用的开销。
3.如果还没有找到,通过该类结构中的super_class指针去它的弗雷的方法列表中寻找,如果找到了了对应的IMP,返回IMP并加入cache
4.如果在自身及父类的方法列表中都没有找到,则看是否可以动态决议(本文先不讲)
5.如果动态方法决议没有解决,进入消息转发流程(本文先不讲)
SEL是否只和方法名有关
答:相同名字的SEL指向同一块内存地址,不通的类可以拥有相同的SEL, 根据同一个SEL找到的对应的IMP是不同的。
直接调用函数会节省方法调用时消息传递的开销,那么直接调用函数是否比oc本身的消息传递效率更高?
答:在调用次数少于10000次时,直接调用函数指针的效率优势并不明显,有时候还是oc的消息传递效率更好,只有循环次数非常大的时候,直接调用函数指针才有效率优势。
https://www.jianshu.com/p/700f58eb0b86
copy,MutableCopy的区别
浅复制:不拷贝对象本身,仅仅是拷贝指向对象的指针,一般来说像这种使用’=’号赋值的对象,基本上都是浅复制。
深复制:是直接拷贝整个对象内存到另一块内存中
copy的字面意思就是“复制”,它是产生一个副本的过程,再来看在iOS里,copy与mutableCopy都是NSObject里的方法,一个NSObject的对象要想使用这两个函数,那么类必须实现NSCopying协议或NSMutableCopying协议,并且是实现了一般来说我们用的很多系统里的容器类已经实现了这些方法。
copy到底是深拷贝还是浅拷贝?
答:我相信有的同学认为只要是使用copy关键字,那么肯定都是深拷贝,这样是很不严谨的,就比如上个例子,虽然使用了copy,但是指针地址是一样,那么它就应该是浅拷贝。
所以是否是深浅拷贝,是否创建新的对象,是由程序运行的环境所造成的,并不是一概而论。
NSString为什么用的copy
假如有一个NSMutableString,现在用他给一个retain修饰 NSString赋值,那么只是将NSString指向了NSMutableString所指向的位置,并对NSMUtbaleString计数器加一,此时,如果对NSMutableString进行修改,也会导致NSString的值修改,原则上这是不允许的. 如果是copy修饰的NSString对象,在用NSMutableString给他赋值时,会进行深拷贝,及把内容也给拷贝了一份,两者指向不同的位置,即使改变了NSMutableString的值,NSString的值也不会改变.
所以用copy是为了安全,防止NSMutableString赋值给NSString时,前者修改引起后者值变化而用的.
高效圆角
第一种:设置CALayer的cornerRadius
imageView.image = [UIImage imageNamed:@"img"];
imageView.image.layer.cornerRadius = 5;
imageView.image.layer.masksToBounds =YES;
这样设置会触发离屏渲染,比较消耗性能。比如当一个页面上有十几头像这样设置了圆角
会明显感觉到卡顿。
这种就是最常用的,也是最耗性能的。
注意:ios9.0之后对UIImageView的圆角设置做了优化,UIImageView这样设置圆角
不会触发离屏渲染,ios9.0之前还是会触发离屏渲染。而UIButton还是都会触发离屏渲染。
第二种
imageView.clipsToBounds = YES;
imageView.layer setCornerRadius:50];
imageView.layer.shouldRasterize = YES;
shouldRasterize=YES设置光栅化,可以使离屏渲染的结果缓存到内存中存为位图, 使用的时候直接使用缓存,节省了一直离屏渲染损耗的性能。
但是如果layer及sublayers常常改变的话,它就会一直不停的渲染及删除缓存重新 创建缓存,所以这种情况下建议不要使用光栅化,这样也是比较损耗性能的。
第三种 :通过Core Graphics重新绘制带圆角的视图
这种方式性能最好,但是UIButton上不知道怎么绘制,可以用UIimageView添加个 点击手势当做UIButton使用
@implementation UIImage (CircleImage)
- (UIImage *)drawCircleImage {
UIGraphicsBeginImageContextWithOptions(self.bounds.size,NO, [UIScreen mainScreen].scale);
[[UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:50] addClip];
[self drawInRect:self.bounds];
UIImage *output = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return output;
}
@end
//在需要圆角时调用如下
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *img = [[UIImage imageNamed:@"image.png"] drawCircleImage];
dispatch_async(dispatch_get_main_queue(), ^{
imageView.image = img;
});
});
四、通过混合图层
此方法就是在要添加圆角的视图上再叠加一个部分透明的视图,只对圆角部分进行遮挡。图层混合的透明度处理方式与mask正好相反。此方法虽然是最优解,没有离屏渲染,没有额外的CPU计算,但是应用范围有限。
总结
1 在可以使用混合图层遮挡的场景下,优先使用第四种方法。
2 即使是非iOS9以上系统,第一种方法在综合性能上依然强于后两者,iOS9以上由于没有了离屏渲染更是首选。
3 方法三由于需要大量计算和增加部分内存,需要实际情况各自取舍。
http://www.cocoachina.com/ios/20170502/19163.html
Category和Extension
Category:
• category只能给某个已有的类扩充方法,不能扩充成员变量。
• category中也可以添加属性,只不过@property只会生成setter和getter的声明,不会生成setter和getter的实现以及成员变量。
• 如果category中的方法和类中原有方法同名,运行时会优先调用category中的方法。也就是,category中的方法会覆盖掉类中原有的方法。所以开发中尽量保证不要让分类中的方法和原有类中的方法名相同。避免出现这种情况的解决方案是给分类的方法名统一添加前缀。比如category_。
• 如果多个category中存在同名的方法,运行时到底调用哪个方法由编译器决定,最后一个参与编译的方法会被调用。
调用优先级
分类(category) > 本类 > 父类。即,优先调用cateory中的方法,然后调用本类方法,最后调用父类方法。 注意:category是在运行时加载的,不是在编译时。
Category与属性和变量:
在类中,声明一个属性,编译器会自动帮我们生成带下划线的成员变量和_ivar以及setter/getter方法,ivar会添加到类转化的结构体的成员变量struct objc_ivar_list *ivars里面,而setter/getter方法则会放到struct objc_method_list **methodLists里面。
由于category是在运行时加载的,不是在编译时,并且在Runtime中,类会转化成结构体来表示,所以转化的objc_class结构体大小在runtime时期就已经是固定的了,已经编译好了,那么就不可能往这个结构体中添加数据,只能修改。
所谓的可以在Category里面可以添加成员变量仅仅是关联了另外一个属性,这些东西和之前的类并不在一个内存空间。
extension:
extension在编译期决议,它就是类的一部分。extension在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它、extension伴随类的产生而产生,亦随之一起消亡。
extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension,除非创建子类再添加extension。而category不需要有类的源码,我们可以给系统提供的类添加category。
extension可以添加实例变量,而category不可以。
extension和category都可以添加属性,但是category的属性不能生成成员变量和getter、setter方法的实现。
NSNotification
NSNotification在发送通知的时候,是同步。可以使用gcd改成异步。在不使用的时候记住要移除通知。
iOS消息推送
1、应用程序注册消息推送。
2、iOS从APNS Server获取device token,应用程序接收device token。
3、应用程序将device token发送给PUSH服务端程序。
4、服务端程序向APNS服务发送消息。
5、APNS服务将消息发送给iPhone应用程序。
https://www.jianshu.com/p/4db013fa8c02
hook(不是很懂)
不进行在结构体里查找方法的过程,直接进入到消息转发的第一个过程。
在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。
大体分两步:
1、直接替换原方法的实现为_objc_msgForward。当原来的函数被调用时,就不会在类方法,父类方法列表里查找实现了,直接表示找不到,进入转发流程。代码如下:
class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod));
用_objc_msgForward来代替原来的函数。
2、替换forwardInvocation:的实现,当进入转发流程时,阶段一和阶段二都不接手,在阶段三forwardInvocation:里会接手。
屏幕渲染
OpenGL中,GPU屏幕渲染有两种方式.
On-Screen Rendering (当前屏幕渲染)
指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区进行.
Off-Screen Rendering (离屏渲染)
指的是在GPU在当前屏幕缓冲区以外开辟一个缓冲区进行渲染操作.
特殊的离屏渲染:
如果将不在GPU的当前屏幕缓冲区中进行的渲染都称为离屏渲染,那么就还有另一种特殊的“离屏渲染”方式: CPU渲染。
如果我们重写了drawRect方法,并且使用任何Core Graphics的技术进行了绘制操作,就涉及到了CPU渲染。整个渲染过程由CPU在App内 同步地完成,渲染得到的bitmap最后再交由GPU用于显示。
备注:CoreGraphic通常是线程安全的,所以可以进行异步绘制,显示的时候再放回主线程,一个简单的异步绘制过程大致如下
Off-Screen Rendering (离屏渲染)
离屏渲染的代价很高,想要进行离屏渲染,首选要创建一个新的缓冲区,屏幕渲染会有一个上下文环境的一个概念,离屏渲染的整个过程需要切换上下文环境,先从 当前屏幕切换到离屏,等结束后,又要将上下文环境切换回来.这也是为什么会消耗性能的原因了(有个屏幕和离屏切换的过程)。由于垂直同步的机制,如果在一个 HSync(水平同步信号) 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。
当使用圆角,阴影,遮罩的时候,图层属性的混合体被指定为在未预合成之前(下一个HSync信号开始前)不能直接在屏幕中绘制,所以就需要屏幕外渲染。 你可以这么理解. 老板叫我短时间间内做一个 app.我一个人能做,但是时间太短,所以我得让我朋友一起来帮着我做.(性能消耗: 也就是耗 你跟你朋友之间沟通的这些成本,多浪费啊).但是没办法 谁让你做不完呢.
上下文切换
上下文切换,不管是在GPU渲染过程中,还是一直所熟悉的进程切换,上下文切换在哪里都是一个相当耗时的操作。首先我要保存当前屏幕渲染环境,然后切换到一个新的绘制环境,申请绘制资源,初始化环境,然后开始一个绘制,绘制完毕后销毁这个绘制环境,如需要切换到On-Screen
Rendering或者再开始一个新的离屏渲染重复之前的操作。
会离屏渲染的操作
shouldRasterize(光栅化)、masks(遮罩)、shadows(阴影)、edge antialiasing(抗锯齿)、group opacity(不透明)、复杂形状设置圆角等、渐变、Text(UILabel, CATextLayer, Core Text, etc)等
如何检测我的项目里面离屏渲染?
*之前看了一些文章说在intruments里面的 CoreAnimation里面有工具.检测.(没找着.求补充)
iOS版本上的优化
iOS 9.0之前UIimageView跟UIButton设置圆角都会触发离屏渲染
iOS 9.0之后UIButton设置圆角会触发离屏渲染,而UIImageView里png图片设置圆角不会触发离屏渲染了,如果设置其他阴影效果之类的还是会触发离屏渲染的。
UIButton设置圆角不触发离屏渲染可以使用贝塞尔曲线UIBezierPath和Core Graphics框架画出一个圆角。
这可能是苹果也意识到离屏渲染会产生性能问题,所以能不产生离屏渲染的地方苹果也就不用离屏渲染了。
http://blog.csdn.net/hopedark/article/details/49640939
@synchronized
@synchronized(obj) {
// do work
}
转化成这样的东东
@try {
objc_sync_enter(obj);
// do work
} @finally {
objc_sync_exit(obj);
}
objc_sync_enter 的文档告诉我们一些新东西: @synchronized 结构在工作时为传入的对象分配了一个递归锁。
方法和选择器有何不同?
答:selector(选择器)是方法的名字,通过它可以找到方法
方法是相对于对象来说的,包含名字和实现等。
layoutSubview
在以下情况下会被调用:
1、init初始化不会触发layoutSubviews
但是是用initWithFrame 进行初始化时,当rect的值不为CGRectZero时,也会触发
2、addSubview会触发layoutSubviews
3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化
4、滚动一个UIScrollView会触发layoutSubviews
5、旋转Screen会触发父UIView上的layoutSubviews事件
6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件
什么时候重写layoutSubviews?
1 自动布局无法满足要求(例如要自定义一个按钮,图片在文字的右侧)
2 不要直接调用调用这个方法,可以调用setNeedsLayout、layoutIfNeeded。
1、layoutSubviews
在iOS5.1和之前的版本,此方法的缺省实现不会做任何事情(实现为空),iOS5.1之后(iOS6开始)的版本,此方法的缺省实现是使用你设置在此view上面的constraints(Autolayout)去决定subviews的position和size。 UIView的子类如果需要对其subviews进行更精确的布局,则可以重写此方法。只有在autoresizing和constraint-based behaviors of subviews不能提供我们想要的布局结果的时候,我们才应该重写此方法。可以在此方法中直接设置subviews的frame。 我们不应该直接调用此方法,而应当用下面两个方法。
2、setNeedsLayout (设置成需要布局)
标记为需要重新布局,异步调用layoutIfNeeded刷新布局,不立即刷新,在下一轮runloop结束前刷新,对于这一轮runloop之内的所有布局和UI上的更新只会刷新一次,layoutSubviews一定会被调用。
3、layoutIfNeeded
使用此方法强制立即进行layout,从当前view开始,此方法会遍历整个view层次(包括superviews)请求layout。因此,调用此方法会强制整个view层次布局。
关键点
layoutIfNeeded不一定会调用layoutSubviews方法。
setNeedsLayout一定会调用layoutSubviews方法(有延迟,在下一轮runloop结束前)。
如果想在当前runloop中立即刷新,调用顺序应该是
[self setNeedsLayout];
[self layoutIfNeeded];
反之可能会出现布局错误的问题。
https://www.jianshu.com/p/915356e280fc
isEquel和hash的关系
NSArray与NSMutableArray
往数组里加入nil是否会发生崩溃?
答:NSMutableArray会发生崩溃现象。无法往输入里加入nil对象。NSArray不会,可以在初始化的时候把里面加入nil元素。
NSTimer
和block一样,会对self进行引用,如果没有按照流程释放的话对造成循环引用而导致内存泄漏。它由于要对self进行引用,所以self将不会主动释放,从而不会主动调用delloc函数,所以问题就来了!如果在delloc里面释放timer是不可取的,因为在没有外部干涉的条件下它永远不会调用delloc函数,所以也就永远不会释放timer,造成内存泄漏!
如果有侵犯到其他作者,请联系我删除!造成的不便,十分抱歉!
网友评论