1.pthread
#import "ViewController.h" #import <pthread.h> @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self pthreadDemo]; } // MARK: pthread 演练 - (void)pthreadDemo { /** 可以访问 baike.baidu.com,查询所有 POSIX 框架,精通 socket 返回值 - 若线程创建成功,则返回0。若线程创建失败,则返回出错编号 参数 - 第一个参数为指向线程标识符的指针 - 第二个参数用来设置线程属性 - 第三个参数是线程运行函数的起始地址 void *(*)(void *) 返回值(函数指针)(参数) OC对应的格式 => id (*)(id) block 定义 void (^)(参数) - 最后一个参数是运行函数的参数 * void * 是 C 语言的,等同于 OC 中的 id ARC(自动引用计数),是针对 OC 的代码管理内存的,本质上是在"编译"的时候, 根据代码的结构“自动”添加 retain/release/autorealse C语言也会涉及到内存分配的问题! 在 C 语言框架中,如果函数出现了 create/retain/copy 等字样,通常需要程序员自己 release! 在日常开发中,如果碰到 c 和 oc 混编的时候,如果碰到相同类型的数据转换,需要使用 __bridge “桥接” 告诉编译器如何管理内存! 提示:__bridge 的添加,可以利用 Xcode 辅助添加即可! * 在 MRC 开发中,不需要桥接,因为所有的内存管理,都是程序员负责的! */ // 在 C 语言框架中,定义类型的时候,通常选择结尾是 Ref/_t pthread_t threadId; NSString *str = @"hello"; int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(str)); if (result == 0) { NSLog(@"OK"); } else { NSLog(@"失败"); } } // MARK: pthread 调用的函数 void *demo(void *params) { // 将参数转换成了字符串 NSString *s = (__bridge NSString *)(params); NSLog(@"%@ %@", [NSThread currentThread], s); return NULL; } @end
2.NSThread
#import "ViewController.h" #import "Person.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self threadDemo4]; } #pragma mark - NSThread 演练 - (void)threadDemo4 { Person *p = [[Person alloc] init]; // 使用分类方法,可以让任何一个 NSObject 都可以在后台执行自己的某个方法! [p performSelectorInBackground:@selector(run) withObject:nil]; } - (void)threadDemo3 { // "隐式"的多线程方法 - NSObject 的分类方法! [self performSelectorInBackground:@selector(demo:) withObject:@"background"]; // 1 / 2? 1 NSLog(@"%@", [NSThread currentThread]); } - (void)threadDemo2 { // 1 / 2? 1 NSLog(@"%@", [NSThread currentThread]); // detach -> 派遣 | "分离",直接就会在后台线程执行 demo 方法 [NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"detach"]; // 1 / 2? “1” / 2 NSLog(@"%@", [NSThread currentThread]); } - (void)threadDemo1 { // 1 / 2? 1 NSLog(@"%@", [NSThread currentThread]); // 实例化 alloc / init =》 内存中有了一个对象 NSThread *thead = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"alloc / init"]; // 需要使用 start 启动线程 [thead start]; // 1 / 2? 1! NSLog(@"%@", [NSThread currentThread]); } - (void)demo:(id)obj { for (int i = 0; i < 10; ++i) { NSLog(@"%@ %@", [NSThread currentThread], obj); } } @end
3.线程状态
#import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self threadDemo]; } #pragma mark - 线程状态演练 - (void)threadDemo { // 1. 实例化线程对象(新建了一个线程对象) NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil]; // 2. 启动(将线程对象加入CPU的可调度线程池,等待 CPU 的调度) [t start]; } - (void)demo { // 阻塞-当满足某个条件的时候,让线程睡眠一会 NSLog(@"睡"); [NSThread sleepForTimeInterval:2.0]; for (int i = 0; i < 20; ++i) { // if (i == 9) { NSLog(@"再睡"); // 满足某一个条件,可以继续睡眠/休眠 [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; } NSLog(@"%@ %@", [NSThread currentThread], @(i)); // 当满足某一个条件的时候,可以让当前线程强行退出 if (i == 15) { // 一旦线程被强行终止,后续所有的代码都不会再继续执行!线程对象也会被销毁 // Invoking this method should be avoided as it does not give your thread a chance to clean up any resources it allocated during its execution. // 调用此方法需要注意:没有给线程机会释放执行过程中申请的资源! // exit 方法会直接终止线程,要调用之前,需要考虑释放对象! // ARC 中通常不用考虑,如果涉及到 C 语言的混编,如果分配了 C 语言的对象,在 exit 之前,一定记住 release! NSLog(@"88"); [NSThread exit]; } } // 不能 NSLog(@"能执行吗"); } @end
4.NSThread的常用属性
#import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self demo]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self threadDemo]; } #pragma mark - 线程属性 /** 线程的执行是由 CPU 来调度的,程序员不能参与 提示:在开发多线程程序的时候,不要相信一次执行的结果! 线程的优先级:优先级高的任务只是表示 CPU 调度的频率高! 误区:优先级高的任务会先执行完! 不要修改线程的优先级,因为做多线程开发最主要的目的,将耗时的操作放在后台! 修改优先级有一个隐患->优先级翻转 开发多线程程序有一个宝典:尽量的简单! */ - (void)threadDemo { NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil]; // name -> 在商业级应用程序中,通常希望程序崩溃的时候,能够知道准确执行的线程!更容易排错! t1.name = @"thread A"; // 设置堆栈大小(只要知道就行) t1.stackSize = 1024 * 1024; // 1M // 优先级,浮点数,范围从 0~1,1的优先级最高!默认是0.5 t1.threadPriority = 0.1; [t1 start]; // ------ NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil]; t2.threadPriority = 1; t2.name = @"thread B"; [t2 start]; } /** 提示,在实际开发中,不同线程调用同一个方法,有可能会产生不同的效果! */ - (void)demo { for (int i = 0; i < 10; ++i) { NSLog(@"%@ %@", [NSThread currentThread], @(i)); } NSLog(@"堆栈大小 %lu", [NSThread currentThread].stackSize / 1024); // // 判断是否是主线程,如果不是,故意弄一个 bug // if (![NSThread isMainThread]) { // NSMutableArray *array = [NSMutableArray array]; // // -[__NSArrayM insertObject:atIndex:]: object cannot be nil‘ // // 在 OC 中不能向数组或者字典插入 nil // [array addObject:nil]; // } } @end
5.资源共享(卖票)
#import "ViewController.h" @interface ViewController () /** 总票数 */ @property (nonatomic, assign) int tickets; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { self.tickets = 20; // [self saleTickets]; NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil]; thread1.name = @"售票员 A"; [thread1 start]; NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil]; thread2.name = @"售票员 B"; [thread2 start]; } #pragma mark - 卖票逻辑 /** 1. 先保证单个线程执行是正确的! 2. 要保证一个线程(窗口)能够将所有的票卖完 3. 后台执行卖票流程 4. 增加线程,继续测试 目前为止,是最典型的资源抢夺问题! */ - (void)saleTickets { while (YES) { // 0. 模拟一个休眠 [NSThread sleepForTimeInterval:1.0]; // 1. 判断是否还有票 if (self.tickets > 0) { // 2. 如果有票,卖一张 self.tickets--; NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]); } else { // 3. 如果没有,什么也不做 NSLog(@"没票了 %@", [NSThread currentThread]); break; } } } @end
6.资源共享(加锁)
#import "ViewController.h" @interface ViewController () /** 总票数 */ @property (nonatomic, assign) int tickets; @property (nonatomic, strong) NSObject *lockObj; /** 非原子属性 nonatomic 原子属性->默认属性 atomic 原子属性是能够保证"线程安全"的属性,原子属性内部也有一把锁 互斥锁 自旋锁 共同点: 都能够保证同一时间只有一条线程执行锁定范围的代码 不同点: 互斥锁:当发现要执行的代码被其他线程锁定后,线程会进入休眠状态,等待解锁之后,线程会被再次唤醒 性能不高! 自旋锁:当发现要执行的代码被其他线程锁定后,会以死循环的方式,监听是否解锁,一旦解锁,立刻执行! 执行性能会高,适合与锁定非常短的代码! * 线程安全 就是在多个线程同时对一个资源进行读写操作时,同样能够保证结果是正确的! 要实现线程安全,必须要使用锁! 只要使用锁,就会降低性能! * UI线程 - 主线程 所有 UI 的更新,都应该在主线程上进行! 原因:几乎所有的 UIKit 类,都不是线程安全的! 取舍!苹果公司共同约定,所有的程序员更新UI同一在主线程进行! */ @property (atomic, strong) NSObject *demoAtomicObject; @end @implementation ViewController // @synthesize 合成指令,作用就是指定属性的 _成员变量 // 在 Xcode 4.3 版本以后,定义属性的同时,就能够自动生成带 _成员变量 // 提示:如果看到大量使用 合成指令的代码,就不要研究了! @synthesize demoAtomicObject = _demoAtomicObject; /** 在 iOS 开发中,如果同时实现了 setter & getter 方法,_成员变量,就不会自动生成 */ - (NSObject *)demoAtomicObject { return _demoAtomicObject; } - (void)setDemoAtomicObject:(NSObject *)demoAtomicObject { // 在原子属性内部也有一把锁,以下是类似的代码 @synchronized (self) { // 可以保证同一时间只有一条线程执行写入操作 _demoAtomicObject = demoAtomicObject; } } - (void)viewDidLoad { [super viewDidLoad]; // 实例化全局的锁对象 self.lockObj = [[NSObject alloc] init]; NSData *data = nil; // 原子性,在写入数据时,先将数据写入一个临时文件,等所有完全写入后,再复制到目标文件! // 防止在写入过程中,被其他同样写入文件所覆盖或者修改! [data writeToFile:@"/Users/apple/Desktop/123.txt" atomically:YES]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { self.tickets = 20; // [self saleTickets]; NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil]; thread1.name = @"售票员 A"; [thread1 start]; NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil]; thread2.name = @"售票员 B"; [thread2 start]; } #pragma mark - 卖票逻辑 /** 1. 先保证单个线程执行是正确的! 2. 要保证一个线程(窗口)能够将所有的票卖完 3. 后台执行卖票流程 4. 增加线程,继续测试 目前为止,是最典型的资源抢夺问题! */ - (void)saleTickets { // @synchronized(self) { while (YES) { // @synchronized(self) { // 0. 模拟一个休眠 [NSThread sleepForTimeInterval:0.5]; // 互斥锁/同步锁 -> 使用互斥锁锁定的代码能够保证同一时间内,只有一条线程执行! /** 1. 使用互斥锁,会降低性能,锁定范围只允许一条线程执行,就无法并发!效率下降! 2. 互斥锁锁定代码的范围,应该尽量的小! 3. 之所以没有只能提示,就是因为互斥锁的性能不好,苹果不建议使用! 4. 技巧 // 用户偏好数据及时写入磁盘 [[NSUserDefaults standardUserDefaults] synchronize]; @ synchronize d 参数:self -> 是一个能够加锁的 NSObject 对象,而且一定要保证所有线程都能够共同访问到该对象 一般情况而言,self 最方便使用的加锁对象,如果程序中只有一个地方涉及到加锁,绝大多数可以考虑使用 self。 */ NSObject *lockObj = [[NSObject alloc] init]; // @synchronized(self.lockObj) { @synchronized(self) { // 1. 判断是否还有票 if (self.tickets > 0) { // 2. 如果有票,卖一张 self.tickets--; NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]); } else { // 3. 如果没有,什么也不做 NSLog(@"没票了 %@", [NSThread currentThread]); break; } } } } @end
7.加载网络图片
#import "ViewController.h" @interface ViewController () <UIScrollViewDelegate> @property (nonatomic, strong) UIScrollView *scrollView; @property (nonatomic, weak) UIImageView *imageView; @property (nonatomic, weak) UIImage *image; @end @implementation ViewController #pragma mark - UIScrollViewDelegate // "告诉" scrollView 缩放哪一个视图,就应该有"返回值" - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { return self.imageView; } /** 加载视图,和 storyboard & xib 等价的,一旦写了这个方法 storyboard & xib 会实效 创建界面上所有子视图的层次结构! */ - (void)loadView { _scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds]; // 设置缩放比例 _scrollView.minimumZoomScale = 0.5; _scrollView.maximumZoomScale = 2; // 设置代理 _scrollView.delegate = self; self.view = _scrollView; UIImageView *iv = [[UIImageView alloc] init]; _imageView = iv; [self.view addSubview:iv]; } /** 视图加载完毕后执行,加载数据 */ - (void)viewDidLoad { [super viewDidLoad]; [self downloadWebImage]; } #pragma mark - 下载网络图片 - (void)downloadWebImage { // 确定网络上唯一的资源 NSURL *url = [NSURL URLWithString:@"http://d.hiphotos.baidu.com/image/pic/item/0df3d7ca7bcb0a46e18d93706863f6246a60afcf.jpg"]; // 加载图片数据->所有网络上传输的都是"二进制数据" NSData *data = [NSData dataWithContentsOfURL:url]; // 将二进制数据转换成图像 UIImage *image = [UIImage imageWithData:data]; // 更新UI self.image = image; } - (void)setImage:(UIImage *)image { // 设置图像视图 self.imageView.image = image; // 调整图像视图大小和image一样 [self.imageView sizeToFit]; // 指定滚动视图的范围 self.scrollView.contentSize = image.size; } @end
8.加载网络图片(多线程)
#import "ViewController.h" @interface ViewController () <UIScrollViewDelegate> @property (nonatomic, strong) UIScrollView *scrollView; @property (nonatomic, weak) UIImageView *imageView; @property (nonatomic, weak) UIImage *image; @end @implementation ViewController #pragma mark - UIScrollViewDelegate // "告诉" scrollView 缩放哪一个视图,就应该有"返回值" - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { return self.imageView; } /** 加载视图,和 storyboard & xib 等价的,一旦写了这个方法 storyboard & xib 会实效 创建界面上所有子视图的层次结构! */ - (void)loadView { _scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds]; // 设置缩放比例 _scrollView.minimumZoomScale = 0.5; _scrollView.maximumZoomScale = 2; // 设置代理 _scrollView.delegate = self; self.view = _scrollView; UIImageView *iv = [[UIImageView alloc] init]; _imageView = iv; [self.view addSubview:iv]; } /** 视图加载完毕后执行,加载数据 */ - (void)viewDidLoad { [super viewDidLoad]; // [self downloadWebImage]; [[[NSThread alloc] initWithTarget:self selector:@selector(downloadWebImage) object:nil] start]; } #pragma mark - 下载网络图片 - (void)downloadWebImage { NSLog(@"===> %@", [NSThread currentThread]); // 确定网络上唯一的资源 NSURL *url = [NSURL URLWithString:@"http://d.hiphotos.baidu.com/image/pic/item/0df3d7ca7bcb0a46e18d93706863f6246a60afcf.jpg"]; // 加载图片数据->所有网络上传输的都是"二进制数据" NSData *data = [NSData dataWithContentsOfURL:url]; // 将二进制数据转换成图像 UIImage *image = [UIImage imageWithData:data]; // "线程间通讯" - 将数据从一个线程传递给另外一个线程,最常见的应用场景,就是后台下载数据,完成之后,在主线程更新UI // 更新UI - 提示:不是所有的在后台线程更新UI都会出问题! // 但是:一定记住在主线程更新UI,否则会有时候出现很奇怪的现象 // 典型的情况,本来很好的程序,不更新UI,“过了一会”,突然又好了! // 在主线程更新 UI /** 参数 waitUntilDone -》 是否等待在主线程执行的方法 setImage: 完成 NO 不等待,绝大多数会使用 NO,不需要等待! YES 等待 */ [self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES]; NSLog(@"来来"); // self.image = image; // [self setImage:image]; } - (void)setImage:(UIImage *)image { NSLog(@"更新UI%@", [NSThread currentThread]); // 设置图像视图 self.imageView.image = image; // 调整图像视图大小和image一样 [self.imageView sizeToFit]; // 指定滚动视图的范围 self.scrollView.contentSize = image.size; } @end
9.加载网络图片(细节)
#import "ViewController.h" @interface ViewController () <UIScrollViewDelegate> @property (nonatomic, strong) UIScrollView *scrollView; @property (nonatomic, weak) UIImageView *imageView; @property (nonatomic, weak) UIImage *image; @end @implementation ViewController /** 1. UIImage & UIImageView UIImage 数据 -> 相片 UIImageView 视图 -> 相框 2. 为什么 scrollView 是 strong 的? 能用 weak 吗? => 不能 如果用 weak 会怎样? => 会循环调用 loadView 造成死循环 3. 为什么 imageView 可以用 weak? 因为局部变量 iv 是强引用 4. 为什么 setImage 方法中没有使用 _image = image? 是因为没有使用 getter 方法 如果要用 getter 方法,可以直接返回 imageView 中保存的图像 5. 为什么 image 也是用的 weak? image 对象本身是被 imageView 强引用的! 通常:控件用 weak,模型对象用 strong! 6. scrollView 是如何对 imageView 进行缩放的? 修改控件的 transform 属性(形变属性/仿射矩阵) */ #pragma mark - UIScrollViewDelegate // "告诉" scrollView 缩放哪一个视图,就应该有"返回值" /** 代理方法的返回值有什么用处? -在需要的时候,“委托”方通知代理执行某些工作! -委托方,在需要的时候,询问代理一些信息 是通过代理方法的返回值来传递的! */ - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { return self.imageView; } // scrollView 在缩放视图的时候会调用 /** struct CGAffineTransform { CGFloat a(比例), b, c, d(比例); // a/b/c/d共同决定旋转 CGFloat tx(x位移), ty(y位移); }; */ - (void)scrollViewDidZoom:(UIScrollView *)scrollView { NSLog(@"%@", NSStringFromCGAffineTransform(self.imageView.transform)); } /** 加载视图,和 storyboard & xib 等价的,一旦写了这个方法 storyboard & xib 会实效 创建界面上所有子视图的层次结构! 如果self.view 不存在,会再次调用 loadView 创建根视图! */ - (void)loadView { // 如果是 weak 属性,会被立即释放 _scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds]; // 设置缩放比例 _scrollView.minimumZoomScale = 0.5; _scrollView.maximumZoomScale = 2; // 设置代理 _scrollView.delegate = self; // setter 方法 self.view = _scrollView; // [self setView:_scrollView]; // 在 OC 中,所有的对象默认都是强引用 UIImageView *iv = [[UIImageView alloc] init]; _imageView = iv; // getter 方法,addSubview 方法会把 iv 添加到 subViews 数组中,subViews 数组对 iv 进行强引用! [self.view addSubview:iv]; // [[self view] addSubview:iv]; } /** 视图加载完毕后执行,加载数据 */ - (void)viewDidLoad { [super viewDidLoad]; // [self downloadWebImage]; [[[NSThread alloc] initWithTarget:self selector:@selector(downloadWebImage) object:nil] start]; } #pragma mark - 下载网络图片 - (void)downloadWebImage { NSLog(@"===> %@", [NSThread currentThread]); // 确定网络上唯一的资源 NSURL *url = [NSURL URLWithString:@"http://d.hiphotos.baidu.com/image/pic/item/0df3d7ca7bcb0a46e18d93706863f6246a60afcf.jpg"]; // 加载图片数据->所有网络上传输的都是"二进制数据" NSData *data = [NSData dataWithContentsOfURL:url]; // 将二进制数据转换成图像 UIImage *image = [UIImage imageWithData:data]; // "线程间通讯" - 将数据从一个线程传递给另外一个线程,最常见的应用场景,就是后台下载数据,完成之后,在主线程更新UI // 更新UI - 提示:不是所有的在后台线程更新UI都会出问题! // 但是:一定记住在主线程更新UI,否则会有时候出现很奇怪的现象 // 典型的情况,本来很好的程序,不更新UI,“过了一会”,突然又好了! // 在主线程更新 UI /** 参数 waitUntilDone -》 是否等待在主线程执行的方法 setImage: 完成 NO 不等待,绝大多数会使用 NO,不需要等待! YES 等待 */ [self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES]; NSLog(@"来来 %@", self.image); // self.image = image; // [self setImage:image]; } - (UIImage *)image { return self.imageView.image; } - (void)setImage:(UIImage *)image { // 使用成员变量,记录住要设置的对象,可以保证 getter 方法能够获得正确的值。 NSLog(@"更新UI%@", [NSThread currentThread]); // 设置图像视图 self.imageView.image = image; // 调整图像视图大小和image一样 [self.imageView sizeToFit]; // 指定滚动视图的范围 self.scrollView.contentSize = image.size; } @end
注意:
如果我们在iOS9下直接进行HTTP请求是会收到如下错误提示:
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app‘s Info.plist file.
系统会告诉我们不能直接使用HTTP进行请求,需要在Info.plist新增一段用于控制ATS的配置:
原文:http://www.cnblogs.com/iosnds/p/4933435.html