ios中级面试题(二)

1.如何追踪app崩溃率,如何解决线上闪退前端

当iOS设备上的App应用闪退时,操做系统会生成一个crash日志,保存在设备上。crash日志上有不少有用的信息,好比每一个正在执行线程的完整堆栈跟踪信息和内存映像,这样就可以经过解析这些信息进而定位crash发生时的代码逻辑,从而找到App闪退的缘由。一般来讲,crash产生来源于两种问题:违反iOS系统规则致使的crash和App代码逻辑BUG致使的crash,下面分别对他们进行分析。mysql

违反iOS系统规则产生crash的三种类型:git

(1) 内存报警闪退程序员

当iOS检测到内存太低时,它的VM系统会发出低内存警告通知,尝试回收一些内存;若是状况没有获得足够的改善,iOS会终止后台应用以回收更多内存;最后,若是内存仍是不足,那么正在运行的应用可能会被终止掉。在Debug模式下,能够主动将客户端执行的动做逻辑写入一个log文件中,这样程序童鞋能够将内存预警的逻辑写入该log文件,当发生以下截图中的内存报警时,就是提醒当前客户端性能内存吃紧,能够经过Instruments工具中的Allocations 和 Leaks模块库来发现内存分配问题和内存泄漏问题。github

(2) 响应超时web

当应用程序对一些特定的事件(好比启动、挂起、恢复、结束)响应不及时,苹果的Watchdog机制会把应用程序干掉,并生成一份相应的crash日志。这些事件与下列UIApplicationDelegate方法相对应,当遇到Watchdog日志时,能够检查上图中的几个方法是否有比较重的阻塞UI的动做。sql

1
2
3
4
5
6
application:didFinishLaunchingWithOptions:
applicationWillResignActive:
applicationDidEnterBackground:
applicationWillEnterForeground:
applicationDidBecomeActive:
applicationWillTerminate:

(3) 用户强制退出数据库

一看到“用户强制退出”,首先可能想到的双击Home键,而后关闭应用程序。不过这种场景通常是不会产生crash日志的,由于双击Home键后,全部的应用程序都处于后台状态,而iOS随时都有可能关闭后台进程,当应用阻塞界面并中止响应时这种场景才会产生crash日志。这里指的“用户强制退出”场景,是稍微比较复杂点的操做:先按住电源键,直到出现“滑动关机”的界面时,再按住Home键,这时候当前应用程序会被终止掉,而且产生一份相应事件的crash日志。编程

应用逻辑的Bugcanvas

大多数闪退崩溃日志的产生都是由于应用中的Bug,这种Bug的错误种类有不少,好比:

  • SEGV:(Segmentation Violation,段违例),无效内存地址,好比空指针,未初始化指针,栈溢出等;

  • SIGABRT:收到Abort信号,可能自身调用abort()或者收到外部发送过来的信号;

  • SIGBUS:总线错误。与SIGSEGV不一样的是,SIGSEGV访问的是无效地址(好比虚存映射不到物理内存),而SIGBUS访问的是有效地址,但总线访问异常(好比地址对齐问题);

  • SIGILL:尝试执行非法的指令,可能不被识别或者没有权限;

  • SIGFPE:Floating Point Error,数学计算相关问题(可能不限于浮点计算),好比除零操做;

  • SIGPIPE:管道另外一端没有进程接手数据;

常见的崩溃缘由基本都是代码逻辑问题或资源问题,好比数组越界,访问野指针或者资源不存在,或资源大小写错误等。

crash的收集

若是是在windows上你能够经过itools或pp助手等辅助工具查看系统产生的历史crash日志,而后再根据app来查看。若是是在Mac 系统上,只须要打开xcode->windows->devices,选择device logs进行查看,以下图,这些crash文件均可以导出来,而后再单独对这个crash文件作处理分析。

a9078e8653368c9c291ae2f8b74012e7.jpg

看日志

市场上已有的商业软件提供crash收集服务,这些软件基本都提供了日志存储,日志符号化解析和服务端可视化管理等服务:

开源的软件也能够拿来收集crash日志,好比Razor,QuincyKit(git连接)等,这些软件收集crash的原理其实大同小异,都是根据系统产生的crash日志进行了一次提取或封装,而后将封装后的crash文件上传到对应的服务端进行解析处理。不少商业软件都采用了Plcrashreporter这个开源工具来上传和解析crash,好比HockeyApp,Flurry和crittercism等。

a9078e8653368c9c291ae2f8b74012e71.jpg

crash信息

因为本身的crash信息太长,找了一张示例:

1)crash标识是应用进程产生crash时的一些标识信息,它描述了该crash的惟一标识(E838FEFB-ECF6-498C-8B35-D40F0F9FEAE4),所发生的硬件设备类型(iphone3,1表明iphone4),以及App进程相关的信息等;

2)基本信息描述的是crash发生的时间和系统版本;

3)异常类型描述的是crash发生时抛出的异常类型和错误码;

4)线程回溯描述了crash发生时全部线程的回溯信息,每一个线程在每一帧对应的函数调用信息(这里因为空间限制没有所有列出);

5)二进制映像是指crash发生时已加载的二进制文件。以上就是一份crash日志包含的全部信息,接下来就须要根据这些信息去解析定位致使crash发生的代码逻辑, 这就须要用到符号化解析的过程(洋名叫:symbolication)。

解决线上闪退

首先保证,发布前充分测试。发布后依然有闪退现象,查看崩溃日志,及时修复并发布。

2.什么是事件响应链,点击屏幕时是如何互动的,事件的传递。

a9078e8653368c9c291ae2f8b74012e72.jpg

事件响应链

对于IOS设备用户来讲,他们操做设备的方式主要有三种:触摸屏幕、晃动设备、经过遥控设施控制设备。对应的事件类型有如下三种:

一、触屏事件(Touch Event)

二、运动事件(Motion Event)

三、远端控制事件(Remote-Control Event)

响应者链(Responder Chain)

响应者对象(Responder Object),指的是有响应和处理事件能力的对象。响应者链就是由一系列的响应者对象构成的一个层次结构。

UIResponder是全部响应对象的基类,在UIResponder类中定义了处理上述各类事件的接口。咱们熟悉的UIApplication、 UIViewController、UIWindow和全部继承自UIView的UIKit类都直接或间接的继承自UIResponder,因此它们的实例都是能够构成响应者链的响应者对象。

响应者链有如下特色:

一、响应者链一般是由视图(UIView)构成的;

二、一个视图的下一个响应者是它视图控制器(UIViewController)(若是有的话),而后再转给它的父视图(Super View);

三、视图控制器(若是有的话)的下一个响应者为其管理的视图的父视图;

四、单例的窗口(UIWindow)的内容视图将指向窗口自己做为它的下一个响应者

须要指出的是,Cocoa Touch应用不像Cocoa应用,它只有一个UIWindow对象,所以整个响应者链要简单一点;

五、单例的应用(UIApplication)是一个响应者链的终点,它的下一个响应者指向nil,以结束整个循环。

点击屏幕时是如何互动的

iOS系统检测到手指触摸(Touch)操做时会将其打包成一个UIEvent对象,并放入当前活动Application的事件队列,单例的UIApplication会从事件队列中取出触摸事件并传递给单例的UIWindow来处理,UIWindow对象首先会使用hitTest:withEvent:方法寻找这次Touch操做初始点所在的视图(View),即须要将触摸事件传递给其处理的视图,这个过程称之为hit-test view。

UIWindow实例对象会首先在它的内容视图上调用hitTest:withEvent:,此方法会在其视图层级结构中的每一个视图上调用pointInside:withEvent:(该方法用来判断点击事件发生的位置是否处于当前视图范围内,以肯定用户是否是点击了当前视图),若是pointInside:withEvent:返回YES,则继续逐级调用,直到找到touch操做发生的位置,这个视图也就是要找的hit-test view。

hitTest:withEvent:方法的处理流程以下:首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内;若返回NO,则hitTest:withEvent:返回nil;若返回YES,则向当前视图的全部子视图(subviews)发送hitTest:withEvent:消息,全部子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者所有子视图遍历完毕;若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束;如全部子视图都返回非,则hitTest:withEvent:方法返回自身(self)。

事件的传递和响应分两个链:

传递链:由系统向离用户最近的view传递。UIKit –> active app’s event queue –> window –> root view –>……–>lowest view

响应链:由离用户最近的view向系统传递。initial view –> super view –> …..–> view controller –> window –> Application

3.Run Loop是什么,使用的目的,什么时候使用和关注点

Run Loop是一让线程能随时处理事件但不退出的机制。RunLoop 其实是一个对象,这个对象管理了其须要处理的事件和消息,并提供了一个入口函数来执行Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 “接受消息->等待->处理” 的循环中,直到这个循环结束(好比传入 quit 的消息),函数返回。让线程在没有处理消息时休眠以免资源占用、在有消息到来时马上被唤醒。

OSX/iOS 系统中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,全部这些 API 都是线程安全的。NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,可是这些 API 不是线程安全的。

线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚建立时并无 RunLoop,若是你不主动获取,那它一直都不会有。RunLoop 的建立是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。

系统默认注册了5个Mode:

kCFRunLoopDefaultMode: App的默认 Mode,一般主线程是在这个 Mode 下运行的。

UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其余 Mode 影响。

UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就再也不使用。

GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,一般用不到。

kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际做用。

Run Loop的四个做用:

  • 使程序一直运行接受用户输入

  • 决定程序在什么时候应该处理哪些Event

  • 调用解耦

  • 节省CPU时间

主线程的run loop默认是启动的。iOS的应用程序里面,程序启动后会有一个以下的main() 函数:

1
2
3
4
5
6
int main(int argc, char *argv[])
{
     @autoreleasepool {
         return  UIApplicationMain(argc, argv, nil, NSStringFromClass([appDelegate class]));
     }
}

重点是UIApplicationMain() 函数,这个方法会为main thread 设置一个NSRunLoop 对象,这就解释了本文开始说的为何咱们的应用能够在无人操做的时候休息,须要让它干活的时候又能立马响应。

对其它线程来讲,run loop默认是没有启动的,若是你须要更多的线程交互则能够手动配置和启动,若是线程只是去执行一个长时间的已肯定的任务则不须要。在任何一个Cocoa程序的线程中,均可以经过:

1
NSRunLoop   *runloop = [NSRunLoop currentRunLoop];

来获取到当前线程的run loop。

一个run loop就是一个事件处理循环,用来不停的监听和处理输入事件并将其分配到对应的目标上进行处理。

NSRunLoop是一种更加高明的消息处理模式,他就高明在对消息处理过程进行了更好的抽象和封装,这样才能是的你不用处理一些很琐碎很低层次的具体消息的处理,在NSRunLoop中每个消息就被打包在input source或者是timer source中了。使用run loop可使你的线程在有工做的时候工做,没有工做的时候休眠,这能够大大节省系统资源。

a9078e8653368c9c291ae2f8b74012e73.jpg

RunLoop

何时使用run loop

仅当在为你的程序建立辅助线程的时候,你才须要显式运行一个run loop。Run loop是程序主线程基础设施的关键部分。因此,Cocoa和Carbon程序提供了代码运行主程序的循环并自动启动run loop。IOS程序中UIApplication的run方法(或Mac OS X中的NSApplication)做为程序启动步骤的一部分,它在程序正常启动的时候就会启动程序的主循环。相似的,RunApplicationEventLoop函数为Carbon程序启动主循环。若是你使用xcode提供的模板建立你的程序,那你永远不须要本身去显式的调用这些例程。

对于辅助线程,你须要判断一个run loop是不是必须的。若是是必须的,那么你要本身配置并启动它。你不须要在任何状况下都去启动一个线程的run loop。好比,你使用线程来处理一个预先定义的长时间运行的任务时,你应该避免启动run loop。Run loop在你要和线程有更多的交互时才须要,好比如下状况:

  • 使用端口或自定义输入源来和其余线程通讯

  • 使用线程的定时器

  • Cocoa中使用任何performSelector…的方法

  • 使线程周期性工做

关注点

Cocoa中的NSRunLoop类并非线程安全的

咱们不能再一个线程中去操做另一个线程的run loop对象,那极可能会形成意想不到的后果。不过幸运的是CoreFundation中的不透明类CFRunLoopRef是线程安全的,并且两种类型的run loop彻底能够混合使用。Cocoa中的NSRunLoop类能够经过实例方法:

1
- (CFRunLoopRef)getCFRunLoop;

获取对应的CFRunLoopRef类,来达到线程安全的目的。

Run loop的管理并不彻底是自动的。

咱们仍必须设计线程代码以在适当的时候启动run loop并正确响应输入事件,固然前提是线程中须要用到run loop。并且,咱们还须要使用while/for语句来驱动run loop可以循环运行,下面的代码就成功驱动了一个run loop:

1
2
3
4
BOOL isRunning = NO;
     do  {
         isRunning = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDatedistantFuture]];
while  (isRunning);

Run loop同时也负责autorelease pool的建立和释放。

在使用手动的内存管理方式的项目中,会常常用到不少自动释放的对象,若是这些对象不可以被即时释放掉,会形成内存占用量急剧增大。Run loop就为咱们作了这样的工做,每当一个运行循环结束的时候,它都会释放一次autorelease pool,同时pool中的全部自动释放类型变量都会被释放掉。

4. ARC和MRC

Objective-c中提供了两种内存管理机制MRC(MannulReference Counting)和ARC(Automatic Reference Counting),分别提供对内存的手动和自动管理,来知足不一样的需求。Xcode 4.1及其之前版本没有ARC。

在MRC的内存管理模式下,与对变量的管理相关的方法有:retain,release和autorelease。retain和release方法操做的是引用记数,当引用记数为零时,便自动释放内存。而且能够用NSAutoreleasePool对象,对加入自动释放池(autorelease调用)的变量进行管理,当drain时回收内存。

(1) retain,该方法的做用是将内存数据的全部权附给另外一指针变量,引用数加1,即retainCount+= 1;

(2) release,该方法是释放指针变量对内存数据的全部权,引用数减1,即retainCount-= 1;

(3) autorelease,该方法是将该对象内存的管理放到autoreleasepool中。

在ARC中与内存管理有关的标识符,能够分为变量标识符和属性标识符,对于变量默认为__strong,而对于属性默认为unsafe_unretained。也存在autoreleasepool。

其中assign/retain/copy与MRC下property的标识符意义相同,strong相似与retain,assign相似于unsafe_unretained,strong/weak/unsafe_unretained与ARC下变量标识符意义相同,只是一个用于属性的标识,一个用于变量的标识(带两个下划短线__)。所列出的其余的标识符与MRC下意义相同。

5. 线程和进程

进程,是并发执行的程序在执行过程当中分配和管理资源的基本单位,是一个动态概念,竟争计算机系统资源的基本单位。每个进程都有一个本身的地址空间,即进程空间或(虚空间)。进程空间的大小 只与处理机的位数有关,一个 16 位长处理机的进程空间大小为 216 ,而 32 位处理机的进程空间大小为 232 。进程至少有 5 种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态。

线程,在网络或多用户环境下,一个服务器一般须要接收大量且不肯定数量用户的并发请求,为每个请求都建立一个进程显然是行不通的,——不管是从系统资源开销方面或是响应用户请求的效率方面来看。所以,操做系统中线程的概念便被引进了。线程,是进程的一部分,一个没有线程的进程能够被看做是单线程的。线程有时又被称为轻权进程或轻量级进程,也是 CPU 调度的一个基本单位。

进程的执行过程是线状的,尽管中间会发生中断或暂停,但该进程所拥有的资源只为该线状执行过程服务。一旦发生进程上下文切换,这些资源都是要被保护起来的。这是进程宏观上的执行过程。而进程又可有单线程进程与多线程进程两种。咱们知道,进程有 一个进程控制块 PCB ,相关程序段 和 该程序段对其进行操做的数据结构集 这三部分,单线程进程的执行过程在宏观上是线性的,微观上也只有单一的执行过程;而多线程进程在宏观上的执行过程一样为线性的,但微观上却能够有多个执行操做(线程),如不一样代码片断以及相关的数据结构集。线程的改变只表明了 CPU 执行过程的改变,而没有发生进程所拥有的资源变化。除了 CPU 以外,计算机内的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。与进程控制表和 PCB 类似,每一个线程也有本身的线程控制表 TCB ,而这个 TCB 中所保存的线程状态信息则要比 PCB 表少得多,这些信息主要是相关指针用堆栈(系统栈和用户栈),寄存器中的状态数据。进程拥有一个完整的虚拟地址空间,不依赖于线程而独立存在;反之,线程是进程的一部分,没有本身的地址空间,与进程内的其余线程一块儿共享分配给该进程的全部资源。

线程能够有效地提升系统的执行效率,但并非在全部计算机系统中都是适用的,如某些不多作进程调度和切换的实时系统。使用线程的好处是有多个任务须要处理机处理时,减小处理机的切换时间;并且,线程的建立和结束所须要的系统开销也比进程的建立和结束要小得多。最适用使用线程的系统是多处理机系统和网络系统或分布式系统。

6. 日常经常使用的多线程处理方式及优缺点

iOS有四种多线程编程的技术,分别是:NSThread,Cocoa NSOperation,GCD(全称:Grand Central Dispatch),pthread。

四种方式的优缺点介绍:

1)NSThread优势:NSThread 比其余两个轻量级。缺点:须要本身管理线程的生命周期,线程同步。线程同步对数据的加锁会有必定的系统开销。

2)Cocoa NSOperation优势:不须要关心线程管理, 数据同步的事情,能够把精力放在本身须要执行的操做上。Cocoa operation相关的类是NSOperation, NSOperationQueue.NSOperation是个抽象类,使用它必须用它的子类,能够实现它或者使用它定义好的两个子类: NSInvocationOperation和NSBlockOperation.建立NSOperation子类的对象,把对象添加到NSOperationQueue队列里执行。

3)GCD(全优势)Grand Central dispatch(GCD)是Apple开发的一个多核编程的解决方案。在iOS4.0开始以后才能使用。GCD是一个替代NSThread, NSOperationQueue,NSInvocationOperation等技术的很高效强大的技术。

4) pthread是一套通用的多线程API,适用于Linux\Windows\Unix,跨平台,可移植,使用C语言,生命周期须要程序员管理,IOS开发中使用不多。

GCD线程死锁

GCD 确实好用 ,很强大,相比NSOpretion 没法提供 取消任务的功能。

如此强大的工具用很差可能会出现线程死锁。 以下代码:

1
2
3
4
5
6
7
- (void)viewDidLoad{
     [ super  viewDidLoad];
     NSLog(@ "=================4" );
     dispatch_sync(dispatch_get_main_queue(),
     ^{ NSLog(@ "=================5" ); });
     NSLog(@ "=================6" );
}

GCD Queue 分为三种:

1,The main queue :主队列,主线程就是在个队列中。

2,Global queues : 全局并发队列。

3,用户队列:是用函数 dispatch_queue_create建立的自定义队列

dispatch_sync 和 dispatch_async 区别:

dispatch_async(queue,block) async 异步队列,dispatch_async函数会当即返回, block会在后台异步执行。

dispatch_sync(queue,block) sync 同步队列,dispatch_sync函数不会当即返回,及阻塞当前线程,等待 block同步执行完成。

分析上面代码:

viewDidLoad 在主线程中, 及在dispatch_get_main_queue() 中,执行到sync 时 向dispatch_get_main_queue()插入 同步 threed。sync 会等到 后面block 执行完成才返回, sync 又再 dispatch_get_main_queue() 队列中,它是串行队列,sync 是后加入的,前一个是主线程,因此 sync 想执行 block 必须等待主线程执行完成,主线程等待 sync 返回,去执行后续内容。照成死锁,sync 等待mainThread 执行完成, mianThread 等待sync 函数返回。下面例子:

1
2
3
4
5
6
7
8
- (void)viewDidLoad{
     [ super  viewDidLoad];
     dispatch_async(dispatch_get_global_queue(0, 0), ^{
         NSLog(@ "=================1" );
     dispatch_sync(dispatch_get_main_queue(), ^{
         NSLog(@ "=================2" ); });
         NSLog(@ "=================3" ); });
}

程序会完成执行,为何不会出现死锁。

首先: async 在主线程中 建立了一个异步线程 加入 全局并发队列,async 不会等待block 执行完成,当即返回,

1,async 当即返回, viewDidLoad 执行完毕,及主线程执行完毕。

2,同时,全局并发队列当即执行异步 block , 打印 1, 当执行到 sync 它会等待 block 执行完成才返回, 及等待dispatch_get_main_queue() 队列中的 mianThread 执行完成, 而后才开始调用block 。由于1 和 2 几乎同时执行,由于2 在全局并发队列上, 2 中执行到sync 时 1 可能已经执行完成或 等了一会,mainThread 很快退出, 2 等已执行后继续内容。若是阻塞了主线程,2 中的sync 就没法执行啦,mainThread 永远不会退出, sync 就永远等待着。

7. 大量数据表的优化方案

1.对查询进行优化,要尽可能避免全表扫描,首先应考虑在 where 及 order by 涉及的列上创建索引。

2.应尽可能避免在 where 子句中对字段进行 null 值判断,不然将致使引擎放弃使用索引而进行全表扫描,如:

1
select id from t where num is  null

最好不要给数据库留NULL,尽量的使用 NOT NULL填充数据库.

备注、描述、评论之类的能够设置为 NULL,其余的,最好不要使用NULL。

不要觉得 NULL 不须要空间,好比:char(100) 型,在字段创建时,空间就固定了, 无论是否插入值(NULL也包含在内),都是占用 100个字符的空间的,若是是varchar这样的变长字段, null 不占用空间。

能够在num上设置默认值0,确保表中num列没有null值,而后这样查询:

1
select id from t where num=0

3.应尽可能避免在 where 子句中使用 != 或 <> 操做符,不然将引擎放弃使用索引而进行全表扫描。

4.应尽可能避免在 where 子句中使用 or 来链接条件,若是一个字段有索引,一个字段没有索引,将致使引擎放弃使用索引而进行全表扫描,如:

1
select id from t where num=10 or Name= 'admin'

能够这样查询:

1
select id from t where num=10 union all select id from t where Name= 'admin'

5.in 和 not in 也要慎用,不然会致使全表扫描,如:

1
select id from t where num  in  (1,2,3)

对于连续的数值,能用 between 就不要用 in 了:

1
select id from t where num between 1 and 3

不少时候用 exists 代替 in 是一个好的选择:

1
select num from a where num  in  (select num from b)

用下面的语句替换:

1
select num from a where exists (select 1 from b where num=a.num)

6.下面的查询也将致使全表扫描:

1
select id from t where name like ‘%abc%’

若要提升效率,能够考虑全文检索。

7.若是在 where 子句中使用参数,也会致使全表扫描。由于SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,若是在编译时创建访问计划,变量的值仍是未知的,于是没法做为索引选择的输入项。以下面语句将进行全表扫描:

1
select id from t where num=@num

能够改成强制查询使用索引:

1
select id from t  with  (index(索引名)) where num=@num

应尽可能避免在 where 子句中对字段进行表达式操做,这将致使引擎放弃使用索引而进行全表扫描。如:

1
select id from t where num/2=100

应改成:

1
select id from t where num=100*2

9.应尽可能避免在where子句中对字段进行函数操做,这将致使引擎放弃使用索引而进行全表扫描。如:

1
2
select id from t where substring(name,1,3)=’abc’ -–name以abc开头的id
select id from t where datediff(day,createdate,’2015-11-30′)=0 -–‘2015-11-30’ --生成的id

应改成:

1
2
select id from t where name like 'abc%'
select id from t where createdate>= '2005-11-30'  and createdate< '2005-12-1'

10.不要在 where 子句中的“=”左边进行函数、算术运算或其余表达式运算,不然系统将可能没法正确使用索引。

11.在使用索引字段做为条件时,若是该索引是复合索引,那么必须使用到该索引中的第一个字段做为条件时才能保证系统使用该索引,不然该索引将不会被使用,而且应尽量的让字段顺序与索引顺序相一致。

12.不要写一些没有意义的查询,如须要生成一个空表结构:

1
select col1,col2 into  #t from t where1=0

这类代码不会返回任何结果集,可是会消耗系统资源的,应改为这样:

1
create table  #t(…)

13.Update 语句,若是只更改一、2个字段,不要Update所有字段,不然频繁调用会引发明显的性能消耗,同时带来大量日志。

14.对于多张大数据量(这里几百条就算大了)的表JOIN,要先分页再JOIN,不然逻辑读会很高,性能不好。

15.select count(*) from table;这样不带任何条件的count会引发全表扫描,而且没有任何业务意义,是必定要杜绝的。

16.索引并非越多越好,索引当然能够提升相应的 select 的效率,但同时也下降了 insert 及 update 的效率,由于 insert 或 update 时有可能会重建索引,因此怎样建索引须要慎重考虑,视具体状况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。

17.应尽量的避免更新 clustered 索引数据列,由于 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将致使整个表记录的顺序的调整,会耗费至关大的资源。若应用系统须要频繁更新 clustered 索引数据列,那么须要考虑是否应将该索引建为 clustered 索引。

18.尽可能使用数字型字段,若只含数值信息的字段尽可能不要设计为字符型,这会下降查询和链接的性能,并会增长存储开销。这是由于引擎在处理查询和连 接时会逐个比较字符串中每个字符,而对于数字型而言只须要比较一次就够了。

19.尽量的使用 varchar/nvarchar 代替 char/nchar ,由于首先变长字段存储空间小,能够节省存储空间,其次对于查询来讲,在一个相对较小的字段内搜索效率显然要高些。

20.任何地方都不要使用

1
select * from t

用具体的字段列表代替“*”,不要返回用不到的任何字段。

21.尽可能使用表变量来代替临时表。若是表变量包含大量数据,请注意索引很是有限(只有主键索引)。

22.避免频繁建立和删除临时表,以减小系统表资源的消耗。临时表并非不可以使用,适当地使用它们可使某些例程更有效,例如,当须要重复引用大型表或经常使用表中的某个数据集时。可是,对于一次性事件, 最好使用导出表。

23.在新建临时表时,若是一次性插入数据量很大,那么可使用 select into 代替 create table,避免形成大量 log ,以提升速度;若是数据量不大,为了缓和系统表的资源,应先create table,而后insert。

24.若是使用到了临时表,在存储过程的最后务必将全部的临时表显式删除,先 truncate table ,而后 drop table ,这样能够避免系统表的较长时间锁定。

25.尽可能避免使用游标,由于游标的效率较差,若是游标操做的数据超过1万行,那么就应该考虑改写。

26.使用基于游标的方法或临时表方法以前,应先寻找基于集的解决方案来解决问题,基于集的方法一般更有效。

27.与临时表同样,游标并非不可以使用。对小型数据集使用 FAST_FORWARD 游标一般要优于其余逐行处理方法,尤为是在必须引用几个表才能得到所需的数据时。在结果集中包括“合计”的例程一般要比使用游标执行的速度快。若是开发时 间容许,基于游标的方法和基于集的方法均可以尝试一下,看哪种方法的效果更好。

28.在全部的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每一个语句后向客户端发送 DONE_IN_PROC 消息。

29.尽可能避免大事务操做,提升系统并发能力。

30.尽可能避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

实际案例分析:拆分大的 DELETE 或INSERT 语句,批量提交SQL语句

若是你须要在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,你须要很是当心,要避免你的操做让你的整个网站中止相应。由于这两个操做是会锁表的,表一锁住了,别的操做都进不来了。

Apache 会有不少的子进程或线程。因此,其工做起来至关有效率,而咱们的服务器也不但愿有太多的子进程,线程和数据库连接,这是极大的占服务器资源的事情,尤为是内存。

若是你把你的表锁上一段时间,好比30秒钟,那么对于一个有很高访问量的站点来讲,这30秒所积累的访问进程/线程,数据库连接,打开的文件数,可能不只仅会让你的WEB服务崩溃,还可能会让你的整台服务器立刻挂了。

因此,若是你有一个大的处理,你必定把其拆分,使用 LIMIT oracle(rownum),sqlserver(top)条件是一个好的方法。下面是一个mysql示例:

1
2
3
4
5
6
while (1){ //每次只作1000条
         mysql_query(“ delete  from logs where log_date <= ’2015-11-01’ limit 1000”);
         if (mysql_affected_rows() == 0){ //删除完成,退出!break;
     } //每次暂停一段时间,释放表让其余进程/线程访问。
     usleep(50000)
}

8. 经常使用到的动画库

Facebook 开源动画库 Pop 的 GitHub 主页:facebook/pop · GitHub,介绍:Playing with Pop (i)

Canvas 项目主页:Canvas – Simplify iOS Development,介绍:Animate in Xcode Without Code

拿 Canvas 来和 Pop 比其实不大合适,虽然二者都自称「动画库」,可是「库」这个词的含义有所区别。本质上 Canvas 是一个「动画合集」而 Pop 是一个「动画引擎」。

先说 Canvas。Canvas 的目的是「Animate in Xcode Without Code」。开发者能够经过在 Storyboard 中指定 User Defined Runtime Attributes 来实现一些 Canvas 中预设的动画,也就是他网站上能看到的那些。可是除了更改动画的 delay 和 duration 基本上不能调整其余的参数。

Pop 就不同了。若是说 Canvas 是对 Core Animation 的封装,Pop 则是对 Core Animation(以及 UIDynamics)的再实现。

Pop 语法上和 Core Animation 类似,效果上则不像 Canvas 那么生硬(时间四等分,振幅硬编码)。这使得对 Core Animation 有了解的程序员能够很轻松地把原来的「静态动画」转换成「动态动画」。

同时 Pop 又往前多走了一步。既然动画的本质是根据时间函数来作插值,那么理论上任何一个对象的任何一个值均可以用来作插值,而不只仅是 Core Animation 里定死的那一堆大小、位移、旋转、缩放等 animatable properties。

9. Restful架构

REST是一种架构风格,其核心是面向资源,REST专门针对网络应用设计和开发方式,以下降开发的复杂性,提升系统的可伸缩性。REST提出设计概念和准则为:

1.网络上的全部事物均可以被抽象为资源(resource)

2.每个资源都有惟一的资源标识(resource identifier),对资源的操做不会改变这些标识

3.全部的操做都是无状态的

REST简化开发,其架构遵循CRUD原则,该原则告诉咱们对于资源(包括网络资源)只须要四种行为:建立,获取,更新和删除就能够完成相关的操做和处理。您能够经过统一资源标识符(Universal Resource Identifier,URI)来识别和定位资源,而且针对这些资源而执行的操做是经过 HTTP 规范定义的。其核心操做只有GET,PUT,POST,DELETE。

因为REST强制全部的操做都必须是stateless的,这就没有上下文的约束,若是作分布式,集群都不须要考虑上下文和会话保持的问题。极大的提升系统的可伸缩性。

RESTful架构:

(1)每个URI表明一种资源;

(2)客户端和服务器之间,传递这种资源的某种表现层;

(3)客户端经过四个HTTP动词,对服务器端资源进行操做,实现”表现层状态转化”。

10. 请分析下SDWebImage的原理

这个类库提供一个UIImageView类别以支持加载来自网络的远程图片。具备缓存管理、异步下载、同一个URL下载次数控制和优化等特征。

SDWebImage 加载图片的流程:

1.入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage 显示,而后 SDWebImageManager 根据 URL 开始处理图片。

2.进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:.

3.先从内存图片缓存查找是否有图片,若是内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。

4.SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展现图片。

5.若是内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。

6.根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操做,因此回主线程进行结果回调 notifyDelegate:。

7.若是上一操做从硬盘读取到了图片,将图片添加到内存缓存中(若是空闲内存太小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展现图片。

8.若是从硬盘缓存目录读取不到图片,说明全部缓存都不存在该图片,须要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。

9.共享或从新生成一个下载器 SDWebImageDownloader 开始下载图片。

10.图片下载由 NSURLConnection 来作,实现相关 delegate 来判断图片下载中、下载完成和下载失败。

11.connection:didReceiveData: 中利用 ImageIO 作了按图片下载进度加载效果。

12.connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 作图片解码处理。

13.图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。若是有须要对下载的图片进行二次处理,最好也在这里完成,效率会好不少。

14.在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。

15.imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。

16.通知全部的 downloadDelegates 下载完成,回调给须要的地方展现图片。

17.将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。

18.SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过时图片。

19.SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。

20.SDWebImagePrefetcher 能够预先下载图片,方便后续使用。

SDWebImage库的做用

经过对UIImageView的类别扩展来实现异步加载替换图片的工做。

主要用到的对象:

一、UIImageView (WebCache)类别,入口封装,实现读取图片完成后的回调

二、SDWebImageManager,对图片进行管理的中转站,记录那些图片正在读取。

向下层读取Cache(调用SDImageCache),或者向网络读取对象(调用SDWebImageDownloader) 。

实现SDImageCache和SDWebImageDownloader的回调。

三、SDImageCache,根据URL的MD5摘要对图片进行存储和读取(实现存在内存中或者存在硬盘上两种实现)

实现图片和内存清理工做。

四、SDWebImageDownloader,根据URL向网络读取数据(实现部分读取和所有读取后再通知回调两种方式)

其余类:

SDWebImageDecoder,异步对图像进行了一次解压??

一、SDImageCache是怎么作数据管理的?

SDImageCache分两个部分,一个是内存层面的,一个是硬盘层面的。内存层面的至关是个缓存器,以Key-Value的形式存储图片。当内存不够的时候会清除全部缓存图片。用搜索文件系统的方式作管理,文件替换方式是以时间为单位,剔除时间大于一周的图片文件。当SDWebImageManager向SDImageCache要资源时,先搜索内存层面的数据,若是有直接返回,没有的话去访问磁盘,将图片从磁盘读取出来,而后作Decoder,将图片对象放到内存层面作备份,再返回调用层。

二、为啥必须作Decoder??

因为UIImage的imageWithData函数是每次画图的时候才将Data解压成ARGB的图像,因此在每次画图的时候,会有一个解压操做,这样效率很低,可是只有瞬时的内存需求。为了提升效率经过SDWebImageDecoder将包装在Data下的资源解压,而后画在另一张图片上,这样这张新图片就再也不须要重复解压了。

这种作法是典型的空间换时间的作法。

相关文章
相关标签/搜索