多线程网络(一)

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的配置:

 

转载于:https://www.cnblogs.com/iosnds/p/4933435.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值