多线程的概念在各个操作系统上都会接触到,windows、Linux、mac os等等这些常用的操作系统,都支持多线程的概念。

当然ios中也不例外,但是线程的运行节点可能是我们平常不太注意的。

例如:

 - (void)viewDidLoad
{
[super viewDidLoad];
for(int i = ; i < ; i++)
{
NSLog(@"===%@===%d" , [NSThread currentThread].name , i);
if(i == )
{
// 创建线程对象
NSThread *thread = [[NSThread alloc]initWithTarget:self
selector:@selector(run) object:nil];
// 启动新线程
[thread start];
// // 创建并启动新线程
// [NSThread detachNewThreadSelector:@selector(run) toTarget:self
// withObject:nil];
}
}
}
- (void)run
{
for(int i = ; i < ; i++)
{
NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
}
}

上面打印的内容每一次都是不同的,什么意思呢?

当我们创建了4个线程后,加上UI主线程一共5个线程。

新的线程在执行start方法之后,并不会立即执行。他们会被cpu随机的执行,只是间隔非常短,以至于我们感觉上是多个线程在同时执行。

所以线程有这么一个特点:执行的随机性。

但是我们可以设置线程的优先级,让优先级更高的线程获得更多的执行机会。

那么什么时候要使用多线程编程呢?

相信有过开发经验的程序员都知道,当我们把代码写完后,程序是一行一行逐行执行代码的,当其中一行代码需要执行较长时间(例如select一个教复杂的语句或者较多的数据时),那么程序就会出现卡顿的现象,不会相应用户的操作。

因为开启程序后会默认开启一个主线程,即UI线程。当处于刚才那种情况时,比如一个windows程序,就会出现程序暂时无响应的提示,好像电脑卡主的感觉,这是非常不好的一种感受。。。。

当我们要避免这种情况的时候,最好的方式就是多线程,开启一个新的线程,用来执行一个耗时的操作,执行完成后再让主线程来修改ui页面(如果需要的话)。

介绍完了线程的一些知识,那么下面来具体看ios中多线程的几种实现方式,主要有一下三种:

1、NSThread :就是刚刚例子中使用的方式,但是使用上比较繁琐,而且需要控制好数据的同步和异步问题

2、NSOperation 和 NSOperationQueue : 这种方式代码比较简洁,可读性强,而且使用队列的形式管理多个任务,本人比较喜欢

3、使用GCD( Grand Central Dispatch ) :相较于NSThread使用简单,使用队列管理任务

一、首先来介绍NSThread

1、创建NSThread的两种方式

-(id) initWithTarget:(id) target selector:(SEL) selector object:(id) arg:

+(void)detachNewThreadSelector:(SEL) selector toTarget:(id) target withObject:(id) arg:

第二种方式,创建NSThread后会自动启动

2、NSThread的常用方法

+currentThread : 返回当前正在执行的线程对象

3、线程的状态

一开始的例子中提了一下,线程创建后,执行了start方法并不是立即就执行了。可能ui线程执行了几毫秒后,cpu才执行它,执行几毫秒后再执行ui线程,但这个过程是随机发生的。

如果想让线程立即执行,那么可以让ui线程sleep 1毫秒,这样cpu就会执行其他可执行的线程,可以达到立即执行的效果

 [NSThread sleepForTimeInterval:0.001];//让当前运行的线程睡眠1毫秒

线程正在执行时,调用isExecuting方法返回 YES ,线程执行完成后调用 isFinished 方法就会返回 YES

4、终止子线程

线程会以一下3种方式之一结束,结束后就处于死亡状态

1)线程执行的方法体执行完成,线程正常结束

2)执行过程中出现了错误

3)调用NSThread 类的 exit 方法来终止当前线程

在UI 线程中 ,NSThread 并没有提供方法来结束其他的子线程。但是我们可以利用 NSThread 的cancel 方法,执行该方法后, 该线程的状态为 isCancelled = YES,但并不会结束线程。

 NSThread* thread;
- (void)viewDidLoad
{
[super viewDidLoad];
// 创建新线程对象
thread = [[NSThread alloc] initWithTarget:self selector:@selector(run)
object:nil];
// 启动新线程
[thread start];
}
- (void)run
{
for(int i = ; i < ; i++)
{
if([NSThread currentThread].isCancelled)
{
// 终止当前正在执行的线程
[NSThread exit];
}
NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
// 每执行一次,线程暂停0.5秒
[NSThread sleepForTimeInterval:0.5];
}
}
- (IBAction)cancelThread:(id)sender
{
// 取消thread线程,调用该方法后,thread的isCancelled方法将会返回NO
[thread cancel];
}

利用例子中代码的形式,我们就可以达到在UI线程中结束其他子线程的目的了。

5、线程睡眠

要让线程进入阻塞状态或者睡眠状态,可以执行sleepXXX格式的方法:

+(void) sleepUntilDate:(NSDate *) aDate  : 让线程睡眠,知道aDate那个时间点再醒过来

-(void)sleepForTimeInterval :让线程睡眠多少秒

6、改变线程优先级

NSThread 提供了如下几个方法来获取和设置线程的优先级

+threadPriority: 获取当前正在执行的线程的优先级

-threadPriority:获取线程实例的优先级

+setThreadPriority :(double) priority : 设置当前正在执行的线程的优先级

-setThreadPriority :(double) priority : 设置线程实例的优先级

(double) priority的 取值范围是0.0~1.0;优先级越高的线程获得的执行机会越多

 - (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"UI线程的优先级为:%g" , [NSThread threadPriority]);
// 创建第一个线程对象
NSThread* thread1 = [[NSThread alloc]
initWithTarget:self selector:@selector(run) object:nil];
// 设置第一个线程对象的名字
thread1.name = @"线程A";
NSLog(@"线程A的优先级为:%g" , thread1.threadPriority);
// 设置使用最低优先级
thread1.threadPriority = 0.0;
// 创建第二个线程对象
NSThread* thread2 = [[NSThread alloc]
initWithTarget:self selector:@selector(run) object:nil];
// 设置第二个线程对象的名字
thread2.name = @"线程B";
NSLog(@"线程B的优先级为:%g" , thread2.threadPriority);
// 设置使用最高优先级
thread2.threadPriority = 1.0;
// 启动2个线程
[thread1 start];
[thread2 start];
}
- (void)run
{
for(int i = ; i < ; i++)
{
NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
}
}

二、使用GCD实现多线程

GCD简化了多线程的实现,主要有两个核心概念:

1、队列:队列负责管理开发者提交的任务,以先进先出的方式来处理任务。

1)串行队列:每次只执行一个任务,当前一个任务执行完成后才执行下一个任务

2)并行队列:多个任务并发执行,所以先执行的任务可能最后才完成(因为具体的执行过程导致)

2、任务:任务就是开发者提供给队列的工作单元,这些任务将会提交给队列底层维护的线程池,因此这些任务将会以多线程的方式执行。

3、创建队列

1)获取系统默认的全局并发队列:

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );

2) 获取系统主线程关联的穿行队列

 dispatch_queue_t queue = dispatch_get_main_queue();

如果将任务提交给主线程关联的串行队列,那么就相当于在程序主线程中去执行该任务。

3)创建穿行队列

 dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_SERIAL);

4)创建并发队列

 dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_CONCURRENT);

5)获取当前执行代码所在队列

dispatch_get_current_queue,返回一个dispatch_queue_t类型的值

4、提交任务

使用下面的方法将任务以同步或者异步的方式提交到队列

 //将代码块以异步的方式提交给指定队列
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block); //将函数以异步的方式提交给指定队列,一般执行函数的方法与执行代码块的方法比,方法名多了一个_f的后缀
void dispatch_async_f(dispatch_queue_t queue, void* context, dispatch_function_t work); //将代码块以同步的方式提交给指定队列
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block); //将函数以同步的方式提交给指定队列,一般执行函数的方法与执行代码块的方法比,方法名多了一个_f的后缀
void dispatch_sync_f(dispatch_queue_t queue, void* context, dispatch_function_t work);
 //将代码块以异步的方式提交给指定队列,队列的线程池负责在指定时间点 when 之后执行
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); //将函数以异步的方式提交给指定队列,队列的线程池负责在指定时间点 when 之后执行
void dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void* context, dispatch_function_t work); //将代码块以异步的方式提交给指定队列,队列的线程池将会重复多次执行该任务
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void(^block)(size_t)); //将函数以异步的方式提交给指定队列,队列的线程池将会重复多次执行该任务
void dispatch_apply_f(size_t iterations, dispatch_queue_t queue, void* context, void(*work)(void*, size_t)); //将代码块提交给指定队列,在应用的某个生命周期内金执行一次
void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);

下面给出一个以异步方式向串行队列、并发队列添加任务的实例

 // 定义2个队列
dispatch_queue_t serialQueue;
dispatch_queue_t concurrentQueue;
- (void)viewDidLoad
{
[super viewDidLoad];
// 创建串行队列
serialQueue = dispatch_queue_create("fkjava.queue", DISPATCH_QUEUE_SERIAL);
// 创建并发队列
concurrentQueue = dispatch_queue_create("fkjava.queue"
, DISPATCH_QUEUE_CONCURRENT);
}
- (IBAction)serial:(id)sender
{
// 依次将2个代码块提交给串行队列
// 必须等到第1个代码块完成后,才能执行第2个代码块。
dispatch_async(serialQueue, ^(void)
{
for (int i = ; i < ; i ++)
{
NSLog(@"%@=====%d" , [NSThread currentThread] , i);
}
});
dispatch_async(serialQueue, ^(void)
{
for (int i = ; i < ; i ++)
{
NSLog(@"%@------%d" , [NSThread currentThread] , i);
}
});
}
- (IBAction)concurrent:(id)sender
{
// 依次将2个代码块提交给并发队列
// 两个代码块可以并发执行
dispatch_async(concurrentQueue, ^(void)
{
for (int i = ; i < ; i ++)
{
NSLog(@"%@=====%d" , [NSThread currentThread] , i);
}
});
dispatch_async(concurrentQueue, ^(void)
{
for (int i = ; i < ; i ++)
{
NSLog(@"%@------%d" , [NSThread currentThread] , i);
}
});
}

提交同步任务:

 - (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)clicked:(id)sender
{
// 以同步方式先后提交2个代码块
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, )
, ^(void){
for (int i = ; i < ; i ++)
{
NSLog(@"%@=====%d" , [NSThread currentThread] , i);
[NSThread sleepForTimeInterval:0.1];
}
});
// 必须等第一次提交的代码块执行完成后,dispatch_sync()函数才会返回,
// 程序才会执行到这里,才能提交第二个代码块。
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, )
, ^(void){
for (int i = ; i < ; i ++)
{
NSLog(@"%@-----%d" , [NSThread currentThread] , i);
[NSThread sleepForTimeInterval:0.1];
}
});
}

多次执行的任务:

 - (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)clicked:(id)sender
{
// 控制代码块执行5次
dispatch_apply(
, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, )
// time形参代表当前正在执行第几次
, ^(size_t time)
{
NSLog(@"===执行【%lu】次===%@" , time
, [NSThread currentThread]);
});
}

只执行一次的任务

 @implementation FKViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)clicked:(id)sender
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"==执行代码块==");
// 线程暂停3秒
[NSThread sleepForTimeInterval:];
});
}

三、使用NSOperation 和 NSOPerationQueue 实现多线程

和GCD差不多,也是有队列和任务的概念

NSOperationQueue:代表一个先进先出的队列,负责管理系统提交的多个NSOperation。底层维护一个线程池,会按顺序启动线程来执行提交给队列的NSOperation

NSOperation:代表多线程任务。一般不直接使用NSOperation,而是使用NSOperation的子类。或者使用NSInvocationOperation和NSBlockOperation(这两个类继承自NSOperation);

1、NSOperation的使用

NSOperation 的使用相较于GCD是面向对象的,OC实现的,而GCD应该是C实现的(看函数的定义和使用)。

使用NSOperation 只需两步:

1)创建 NSOperationQueue 队列,并未该队列设置相关属性

2)创建 NSOperation 子类对象,并将该对象提交给 NSOperationQueue 队列,该队列将会按顺序依次启动每个 NSOperation。

2、NSOperationQueue的常用方法:

 +currentQueue //类方法,返回执行当前NSOperation的NSOperationQueue队列

 +mainQueue //返回系统主线程的NSOperationQueue队列

 -(void) addOperation:(NSOperation *) operation //将operation添加到NSOperationQueue队列中

 -(void) addOperations:(NSArray *) ops waitUnitlFinished:(BOLL) wait //将NSArray中包含的所有NSOperation添加到NSOperationQueue。如果第二个参数指定为YES,将会阻塞当前线程,直到提交的所有NSOperation执行完成。如果第二个参数为NO,该方法立即返回,NSArray包含的NSOperation将以异步方式执行,不会阻塞当前线程。

 - operations //只读属性,返回该NSOperationQueue管理的所有NSOperation
-operationCount //只读属性,返回该NSOperationQueue管理的所有NSOperation数量 -cancelAllOperations: //取消NSOperationQueue队列中所有正在排队和执行的NSOperation -waitUntilAllOperationsAreFinished://阻塞当前线程,直到该NSOperationQueue中所有排队和执行的NSOperation执行完成再接触阻塞 -(NSInteger) maxConcurrentOperationCount://返回该队列最大支持多少个并发线程 -setMaxConcurrentOperationCount:(NSInteger) count //设置该队列最大支持多少个并发线程 -setSuspended:(BOOL) suspend: //设置NSOperationQueue是否已经暂停调度正在排队的NSOperation -(BOLL) isSuspended: //返回NSOperationQueue是否已经暂停调度正在排队的NSOperation

3、使用NSInvocationOperation 和 NSBlockOperation

NSInvocationOperation 和 NSBlockOperation 继承自 NSOperation,所以可以直接使用,用于封装需要异步执行的任务。

使用它们实现图片异步下载:

 NSOperationQueue* queue;
- (void)viewDidLoad
{
[super viewDidLoad];
queue = [[NSOperationQueue alloc]init];
// 设置该队列最多支持10条并发线程
queue.maxConcurrentOperationCount = ;
}
- (IBAction)clicked:(id)sender
{
NSString* url = @"http://www.......jpg";
// 以传入的代码块作为执行体,创建NSOperation
NSBlockOperation* operation = [NSBlockOperation
blockOperationWithBlock:^{
// 从网络获取数据
NSData *data = [[NSData alloc]
initWithContentsOfURL:[NSURL URLWithString:url]];
// 将网络数据初始化为UIImage对象
UIImage *image = [[UIImage alloc]initWithData:data];
if(image != nil)
{
// 在主线程中执行updateUI:方法
[self performSelectorOnMainThread:@selector(updateUI:)
withObject:image waitUntilDone:YES];
}
else
{
NSLog(@"---下载图片出现错误---");
}
}];
// 将NSOperation添加给NSOperationQueue
[queue addOperation:operation];
}
-(void)updateUI:(UIImage*) image
{
self.iv.image = image;
}
 NSOperationQueue* queue;
- (void)viewDidLoad
{
[super viewDidLoad];
queue = [[NSOperationQueue alloc]init];
// 设置该队列最多支持10条并发线程
queue.maxConcurrentOperationCount = ;
}
- (IBAction)clicked:(id)sender
{
NSString* url = @"http://www.......jpg";
// 以self的downloadImageFromURL:方法作为执行体,创建NSOperation
NSInvocationOperation* operation = [[NSInvocationOperation alloc]
initWithTarget:self selector:@selector(downloadImageFromURL:)
object:url];
// 将NSOperation添加给NSOperationQueue
[queue addOperation:operation];
} // 定义一个方法作为线程执行体。
-(void)downloadImageFromURL:(NSString *) url
{
// 从网络获取数据
NSData *data = [[NSData alloc]
initWithContentsOfURL:[NSURL URLWithString:url]];
// 将网络数据初始化为UIImage对象
UIImage *image = [[UIImage alloc]initWithData:data];
if(image != nil)
{
// 在主线程中执行updateUI:方法
[self performSelectorOnMainThread:@selector(updateUI:)
withObject:image waitUntilDone:YES];
}
else
{
NSLog(@"---下载图片出现错误---");
}
}
-(void)updateUI:(UIImage*) image
{
self.iv.image = image;
}

4、自定义NSOperation 的子类

创建 NSOperation 的子类,需要重写一个方法:-(void) main,该方法的方法体将作为 NSOperationQueue 完成的任务

下面自定义一个NSOperation 子类来实现下载图片的功能

 @interface MyDownImageOperation : NSOperation
@property (nonatomic , strong) NSURL* url;
@property (nonatomic , weak) UIImageView* imageView;
- (id)initWithURL:(NSURL*)url imageView:(UIImageView*)iv;
@end
 @implementation MyDownImageOperation
- (id)initWithURL:(NSURL*)url imageView:(UIImageView*)iv
{
self = [super init];
if (self) {
_imageView = iv;
_url = url;
}
return self;
}
// 重写main方法,该方法将作为线程执行体
- (void)main
{
// 从网络获取数据
NSData *data = [[NSData alloc]
initWithContentsOfURL:self.url];
// 将网络数据初始化为UIImage对象
UIImage *image = [[UIImage alloc]initWithData:data];
if(image != nil)
{
// 在主线程中执行updateUI:方法
[self performSelectorOnMainThread:@selector(updateUI:)
withObject:image waitUntilDone:YES]; // ①
}
else
{
NSLog(@"---下载图片出现错误---");
}
}
-(void)updateUI:(UIImage*) image
{
self.imageView.image = image;
}

viewController代码:

 NSOperationQueue* queue;
- (void)viewDidLoad
{
[super viewDidLoad];
queue = [[NSOperationQueue alloc]init];
// 设置该队列最多支持10条并发线程
queue.maxConcurrentOperationCount = ;
}
- (IBAction)clicked:(id)sender
{
// 定义要加载的图片的URL
NSURL* url = [NSURL URLWithString:@"http://www.crazyit.org/logo.jpg"];
// 创建FKDownImageOperation对象
MyDownImageOperation* operation = [[MyDownImageOperation alloc]
initWithURL:url imageView:self.iv];
// 将NSOperation的子类的实例提交给NSOperationQueue
[queue addOperation:operation];
}

IOS高级编程之三:IOS 多线程编程的更多相关文章

  1. 新手浅谈Task异步编程和Thread多线程编程

    初学Task的时候上网搜索,看到很多文章的标题都是task取代thread等等相关,我也一直以为task和thread是一类,其实task是.net4.0提出的异步编程,在之前.net1.0有dele ...

  2. iOS开发之再探多线程编程:Grand Central Dispatch详解

    Swift3.0相关代码已在github上更新.之前关于iOS开发多线程的内容发布过一篇博客,其中介绍了NSThread.操作队列以及GCD,介绍的不够深入.今天就以GCD为主题来全面的总结一下GCD ...

  3. 《Python核心编程》18.多线程编程(三)

    18.6使用threading模块 #!/usr/bin/env python # -*- coding:utf-8 -*- """从Thread类中派生出一个子例,创建 ...

  4. 《Python核心编程》 18.多线程编程(一)

    一进程和线程 1参考链接: http://www.zhihu.com/question/25532384 中OF小工和zhonyong 的回答 总结他们两的回答: 引言: 1.电脑的运行,在硬件上是C ...

  5. 【C编程基础】多线程编程

    基础知识 1.基本概念 (1)线程,即轻量级进程(LWP:LightWeight Process),是程序执行流的最小单元. 线程是进程中的一个实体,是被系统独立调度和分派的基本单位. (2)线程同步 ...

  6. 《Python核心编程》18.多线程编程(二)

    18.1没有线程支持 #!/usr/bin/env python # -*- coding:utf-8 -*- from time import sleep, ctime def loop0(): p ...

  7. Java之旅_高级教程_多线程编程

    摘自:http://www.runoob.com/java/java-multithreading.html Java 多线程编程 Java 给多线程编程提供了内置的支持.一条线程指的是进程中的一条执 ...

  8. android: 多线程编程基础

    9.1   服务是什么 服务(Service)是 Android 中实现程序后台运行的解决方案,它非常适合用于去执行那 些不需要和用户交互而且还要求长期运行的任务.服务的运行不依赖于任何用户界面,即使 ...

  9. C语言多线程编程

    HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUT ...

  10. 初识Java多线程编程

    Java 多线程编程 Java给多线程编程提供了内置的支持.一个多线程程序包含两个或多个能并发运行的部分.程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径. 多线程是多任务的一种特别 ...

随机推荐

  1. 淘宝UWP桌面版公测-谁需要邀请码?

    今天taobaoUWP桌面版上线beta测试了.哪位朋友需要邀请码的,请与我联系. 前提是,您的PC已经升级到Windows 10 10586版本了,否则无法使用. 邀请码数量有限,一人一枚,共20枚 ...

  2. NoSQL:从关系型数据库到非关系型数据库

    关系型数据库 所谓关系型数据库,,就是指采用了关系模型来组织数据的数据库. 什么是关系模型,简单说,关系模型就是二维表格模型,而一个关系型数据库就是由二维表及其之间的联系所组成的一个数据组织. 关系模 ...

  3. 团队项目——站立会议DAY7

    第七次站立会议记录: 参会人员:张靖颜,钟灵毓秀,何玥,赵莹,王梓萱 项目进展: 1.张靖颜:对功能模块代码进行近一步的审查和辅助,并对出错处进行修改和完善. 2.钟灵毓秀:对代码近一步的修改,将各个 ...

  4. Gradle与Gatling脚本集成

    Gatling作为次时代的性能测试工具,由于其API简洁明了.性能出众,越来越受欢迎.但是运行Gatling脚本却有诸多不便,其提供的默认方式不是很方便.考虑到Gatling脚本本质上是Scala类, ...

  5. 微软专家推荐11个Chrome 插件

    Web开发人员,需要长时间使用浏览器,尽管Windows10 Edge浏览器启动非常快速,且支持110多种设备,Edge支持基于JS 扩展,但也删除了很多旧功能像Active-X等插件.多数情况下,插 ...

  6. Java使用snakeyaml解析yaml

    YAML Yaml是一种"是一个可读性高并且容易被人类阅读,容易和脚本语言交互,用来表达资料序列的编程语言."类似于XML但比XML更简洁,语法详见 http://www.ruan ...

  7. Android Activity的生命周期简单总结

    Android Activity的生命周期简单总结 这里的内容参考官方的文档,这篇文章的目的不是去总结Activity是如何启动,如何创造,以及暂停和销毁的,而是从实际开发中分析在Activity各个 ...

  8. Atitit 图片 验证码生成attilax总结

    Atitit 图片 验证码生成attilax总结 1.1. 图片验证码总结1 1.2. 镂空文字  打散 干扰线 文字扭曲 粘连2 1.1. 图片验证码总结 因此,CAPTCHA在图片验证码这一应用点 ...

  9. atitit 研发管理 要不要自己做引擎自己实现架构?.docx

    atitit 研发管理 要不要自己做引擎自己实现架构?.docx 1.1. 目前已经有很多引擎了,还要自己做吗??1 1.2. 答案是自己做更好,利大于弊1 2. 为什么要自己做??1 2.1. 从历 ...

  10. iOS---用LLDB调试,让移动开发更简单(一)

    因文章字数超过限制,所以拆分成了上下篇 LLDB的Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能.平时用Xcode运行程序,实际走的都是LLDB.熟练使用 ...