一、NSThread
1.1 基本使用
-(void) createThread{
//NSThread
//1.创建线程
/*
第一个参数:目标对象 self
第二个参数:方法选择器调用的方法
第三个参数:前面方法需要传递的参数
*/
NSThread *thread = [[NSThreadalloc]initWithTarget:selfselector:@selector(run:)object:@"abc"];
//2.启动线程
[thread start];
}
-(void)run:(NSString *)param{
NSLog(@"---run---%@",[NSThreadcurrentThread]);
}
二、GCD简介
2.1 什么是 GCD
全称是 Grand Central Dispatch,可以译为“牛逼的中枢调度起“。
纯C语言,提供了非常多强大的函数
2.2 GCD的优势
GCD是苹果公司为多核的并行运算提出的解决方案。
GCD会自动利用更多的CPU内核(比如双核、四核)
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
三、任务和队列
3.1 GCD中有2个核心概念
> 任务: 执行什么操作
> 队列: 用来存放任务
3.2 GCD的使用就2个步骤
> 定制任务
确定想做的事情
> 将任务添加到队列中
GCD会自动将队列中的任务取出,放到对应的线程中执行
任务的取出遵循队列的FIFO原则:先进先出,后进后出
3.3 执行任务
> GCD中有2个用来执行任务的常用函数
>用同步的方式执行任务
dispatch_sync(dispatch_queue_t queue,dispatch_block_t block);
queue - 队列
block - 任务
>用异步的方式执行任务
dispatch_async(dispatch_queue_t queue,dispatch_block_t block);
>同步和异步的区别
同步:只能在当前线程中执行任务,不具备开启新线程的能力
异步:可以在新的线程中执行任务,具备开启新线程的能力
3.4 队列的类型
GCD的队列可以分为2大类型
并发队列 (Concurrent Dispatch Queue)
可以让多个任务并发执行,自动开启多个线程同时执行任务
并发功能只有在异步(dispatch_async)函数下才有效
串行队列 (Serial Dispatch Queue)
让任务一个接着一个地执行,一个执行完毕后,再执行下一个任务
//异步函数+并发队列
-(void)asyncConcurrent{
//1.创建队列
/*
第一个参数:标签,用来区分队列
第二个参数:队列的类型,并发/串行
*/
dispatch_queue_t queue =dispatch_queue_create("first",DISPATCH_QUEUE_CONCURRENT);
//2.封装任务
/*
第一个参数:队列
第二个参数:要执行的任务
*/
dispatch_async(queue,^{
NSLog(@"download1---%@",[NSThreadcurrentThread]);
});
}
四、RunLoop
4.1 什么是RunLoop
从字面意思看 是 运行循环 跑圈
基本作用:
保持程序运行
处理app中的各种事件(比如触摸事件、定时器事件、Selector事件)
节省CPU资源,提高程序性能:该做事时做事,该休息时休息
BOOL running=YES;
do{
//...
}while(running);
有RunLoop的情况下,由于main函数里面启动了RunLoop
所以程序并不会马上退出,保持运行状态
int main(int argc,char * argv[]) {
@autoreleasepool {
returnUIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegateclass]));
}
}
UIApplicationMain函数内部启动了一个RunLoop
所以UIApplicationMain一直没有返回,保持了程序的持续运行
这个默认启动的RunLoop是跟主线程关联的
4.2 RunLoop对象
iOS中有2套API来访问和使用RunLoop
1> Foundation
NSRunLoop
2> CoreFoundation
CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表着RunLoop对象
NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,
需要多研究CFRunLoopRef层面的API(Core Foundation层面)
4.3 RunLoop官方资料
4.4 RunLoop与线程
每条线程都有唯一的一个与之对应的RunLoop对象
主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
RunLoop在第一次获取时创建,在线程结束时销毁
1> 获得RunLoop对象
>Foundation
[NSRunLoop currentRunLoop]; //获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; //获得主线程的RunLoop对象
>Core Foundation
CFRunLoopGetCurrent(); //获得当前线程的RunLoop对象
CFRunLoopGetMain();//获得主线程的RunLoop对象
2> 子线程RunLoop创建
NSThread *thread = [[NSThreadalloc]initWithTarget:selfselector:@selector(run:)object:@"abc"];
[thread start];
-(void)run:(NSString *)param{
NSLog(@"---run---%@",[NSThreadcurrentThread]);
//创建子线程对应的RunLoop
NSLog(@"%@",[NSRunLoopcurrentRunLoop]);
}
4.5 RunLoop相关类
Core Foundation中关于RunLoop的5个类
1> CFRunLoopRef
2> CFRunLoopModeRef
3> CFRunLoopSourceRef
4> CFRunLoopTimerRef
5> CFRunLoopObserverRef
一个 RunLoop里可以有多个Mode
一个Mode里至少要有一个Source或者一个Timer
RunLoop只能选择一种Mode运行
如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入
这样做的好处是为了分隔开不同组的Source/Timer/Observer,让其互不影响
系统默认注册了5个Mode:
kCFRunLoopDefaultMode: App的默认Mode,通常主线程在这个模式下运行
UITrackingRunLoopMode: 界面追踪Mode,用于ScrollView追踪触摸滑动,
保证界面滑动时不受其他Mode影响
UIInitializationRunLoopMode: 在刚启动 App时进入的第一个Mode,启动完成后
就不再使用
GSEventReceiveRunLoopMode: 接受系统事件的内部Mode,通常用不到
kCFRunLoopCommonModes: 这一个占位用的Mode,不是一种真正的Mode
{
[selftimer];
}
-(void)timer{
//1.创建定时器
NSTimer* timer = [NSTimertimerWithTimeInterval:2.0target:selfselector:@selector(run)userInfo:nilrepeats:YES];
//2.添加定时器到RunLoop
/*
第一个参数:定时器
第二个参数:RunLoop的运行模式
*/
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
//凡是添加到CommonModes中的事件都会被同时打上common标签的运行模式上
[[NSRunLoopcurrentRunLoop]addTimer:timerforMode:NSRunLoopCommonModes];
}
-(void)run{
NSLog(@"---run---%@",[NSThreadcurrentThread]);
//创建子线程对应的RunLoop
NSLog(@"%@",[NSRunLoopcurrentRunLoop]);
//启动RunLoop
[[NSRunLoopcurrentRunLoop]run];
//当前模式
NSLog(@"Mode: %@",[NSRunLoopcurrentRunLoop].currentMode);
}
五、Socket数据传输
要实现基于socket的数据传输,需要实现两个类Network和NetworkThread
网络单独放进一个线程里
同时配合timer进行数据接收频率的调整
timer1默认是0.1秒间隔触发,空闲时刻激活
timer2则是在有网络数据到达,0秒间隔迅速读取数据
实现之后,在AppDelegate的application函数里面
调用即可开启网络收发数据功能
具体的socket/select相关原理部分,可以参考【Linux C/C++】TCP
//Network setup
[Network Init];
5.1 Network类
Network.h:
#ifndef Network_h
#define Network_h
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <unistd.h>
@interface Network : NSObject
+(void)Init;
+(void)Run:(void*) data;
+(bool)Create;
+(bool)Connect;
+(void)Select;
+(void)ReadStream;
+(void)SendMessage:(char*)data :(ssize_t)len;
+(void)Close;
@end
#endif /* Network_h */
Network.mm
#import <Foundation/Foundation.h>
#import "Network.h"
#import "NetworkThread.h"
#import "MessageQueue.h"
#import <fcntl.h>
@interface Network()
@end
static int sock = -1;
static bool connected = false;
static NetworkThread* thread = 0;
static struct fd_set fds,fdsErr;
static struct timeval timeout={0,0};
static int maxfdp = 1;
static bool Timer1Active = true;
char buffer[1024];
@implementation Network
+(void) Init{
thread = [[NetworkThread alloc] init];
[thread CreateThread];
}
+(void) Run:(void*) data{
if(false == connected)
{
// int flags = fcntl(sock, F_GETFL, 0);
// fcntl(sock, F_SETFL, flags & ~O_NONBLOCK);
[Network Connect];
}
if(connected)
{
[Network Select];
}
}
+(bool) Create{
sock = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sock)
{
NSLog(@"socket create err");
return false;
}
return true;
}
+(bool) Connect{
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("119.23.71.163");
serv_addr.sin_port = htons(18888);
int r = connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if (-1 == r)
{
printf("%m\n");
connected = false;
NSLog(@"network connect fail");
return false;
}
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
NSLog(@"network connected");
connected = true;
return true;
}
+(void)Select{
FD_ZERO(&fds); //每次循环都要清空集合,否则不能检测描述符变化
FD_ZERO(&fdsErr);
FD_SET(sock,&fds); //添加描述符
FD_SET(sock,&fdsErr);
maxfdp = sock+1;
switch(select(maxfdp,&fds,NULL,&fdsErr,&timeout)) //select使用
{
case -1:
exit(-1);
break; //select错误,退出程序
case 0:
{
if(!Timer1Active && send_mq.IsNull())
{
Timer1Active = true;
[thread ActiveTimer1];
}
}
break; //再次轮询
default:
{
if(FD_ISSET(sock,&fdsErr))
{
//close fd
[Network Close];
[Network Create];
return ;
}
else if(FD_ISSET(sock,&fds)) //测试sock是否可读,即是否网络上有数据
{
if(Timer1Active)
{
Timer1Active = false;
[thread ActiveTimer2];
}
//read fd
[Network ReadStream];
return ;
}
}
break;
}// end switch
if(!send_mq.IsNull())
{
struct Message* msg = send_mq.Pop();
[Network SendMessage:msg->data :msg->size];
free_mq.Push(msg);
}
}
+(void)ReadStream{
ssize_t len = read(sock,buffer,1024);
if(-1 != len)
{
//dispatch this buffer data
struct Message* msg=0;
if(free_mq.IsNull())
{
msg = new Message;
}
else
msg = free_mq.Pop();
msg->size = len;
memcpy(msg->data,buffer,len);
global_mq.Push(msg);
return ;
}
[Network Close];
[Network Create];
}
+(void)SendMessage:(char*)data :(ssize_t)len{
while(len > 0)
{
ssize_t ret_len = write(sock,data,len > 1024 ? 1024 : len);
if(-1 != ret_len)
{
//
len -= ret_len ;
data += ret_len ;
}
else
{
[self Close];
[self Create];
}
}
}
+(void)Close{
close(sock);
connected = false;
}
@end
5.2 NetworkThread类
NetworkThread.h:
#ifndef NetworkThread_h
#define NetworkThread_h
@interface NetworkThread : NSThread
-(void)CreateThread;
-(void)ActiveTimer1;
-(void)ActiveTimer2;
@end
#endif /* NetworkThread_h */
NetworkThread.mm
#import <Foundation/Foundation.h>
#import "NetworkThread.h"
#import "Network.h"
@interface NetworkThread()
@property NSThread* thread;
@property NSTimer* timer1;
@property NSTimer* timer2;
@end
@implementation NetworkThread
-(void) CreateThread{
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(Run:) object:nil];
[self.thread start];
}
-(void) Run:(void*) data{
if([Network Create] != true)
{
return ;
}
//创建子线程对应的RunLoop
[NSRunLoop currentRunLoop];
[self timer];
[[NSRunLoop currentRunLoop] run];
}
-(void)run{
[Network Run:nil];
NSLog(@"network loop run 1");
}
-(void)run2{
[Network Run:nil];
NSLog(@"network loop run 2");
}
-(void)timer{
//1.创建定时器
self.timer1 = [NSTimer timerWithTimeInterval:0.1 target:self selector:@selector(run) userInfo:nil repeats:YES];
self.timer2 = [NSTimer timerWithTimeInterval:0 target:self selector:@selector(run2) userInfo:nil repeats:YES];
//2.添加定时器到RunLoop
/*
第一个参数:定时器
第二个参数:RunLoop的运行模式
*/
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
//凡是添加到CommonModes中的事件都会被同时打上common标签的运行模式上
[[NSRunLoop currentRunLoop] addTimer:self.timer1 forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] addTimer:self.timer2 forMode:NSRunLoopCommonModes];
// [self.timer1 setFireDate:[NSDate distantFuture]];
[self.timer2 setFireDate:[NSDate distantFuture]];
// [self.timer1 setFireDate:[NSDate distantPast]];
}
-(void)ActiveTimer1{
// [self.timer1 setFireDate:[NSDate distantFuture]];
[self.timer2 setFireDate:[NSDate distantFuture]];
[self.timer1 setFireDate:[NSDate distantPast]];
}
-(void)ActiveTimer2{
[self.timer1 setFireDate:[NSDate distantFuture]];
// [self.timer2 setFireDate:[NSDate distantFuture]];
[self.timer2 setFireDate:[NSDate distantPast]];
}
@end