NSOperation并发实现

关于NSOperation的基本知识,点击查看我之前转发的博客: 猛戳这里  

NSOperation实现并发有两种方式:

①自定义NSOperation只需实现main方法,然后加入到NSOperationQueue

②自定义NSOperation实现start,isExecuting,isFinished,isConnurrent,,然后【operation start】

简单说下我自己的理解:NSOperation能够实现并发是因为start的时候需要分配了一个线程,如果加入到queue中,这些基本的配置queue全包了,只需要重载main方法实现任务即可;如果是手动实现就需要重载start,分配线程,同时需要广播operation的状态,所以需要重载isFinished等。


看到这里基本很明显了,NSOperation只能使用上述的其中一种,这样operation的状态就不会混乱。

如果我想实现NSOperation既能加入到queue又能手动start呢,就像ASI?

一、实现一个手动start的并发NSOperation

①新建一个NSOperation子类:

#import <Foundation/Foundation.h>


@interface ImageDownloadOperation :NSOperation


@end  

在.m中添加以下几个属性,用来发布NSOperation的状态

#import "ImageDownloadOperation.h"


@interface ImageDownloadOperation ()

@property(nonatomic ,assign , getter = isFinished)BOOL IOFinished;

@property(nonatomic ,assign , getter = isExecuting)BOOL IOExecuting;

@property(nonatomic ,assign , getter = isConcurrent)BOOL IOConcurrent;

@end


@implementation ImageDownloadOperation


-(void)setIOConcurrent:(BOOL)IOConcurrent{

   _IOConcurrent = IOConcurrent;

}


//发布NSOperation的状态


- (void)setIOFinished:(BOOL)IOFinished{

    [selfwillChangeValueForKey:@"isFinished"];

   _IOFinished = IOFinished;

    [selfdidChangeValueForKey:@"isFinished"];


}


//发布NSOperation的状态

- (void)setIOExecuting:(BOOL)IOExecuting{

    [selfwillChangeValueForKey:@"isExecuting"];

   _IOExecuting = IOExecuting;

    [selfdidChangeValueForKey:@"isExecuting"];

}

@end


这里直接设置IOFinished的getter为父类的isFinished,就省略了在重载isFinished的方法。重载IOFinished的set方法,告知外界NSOperation的状态,这是重中之重,尤其是在加入到queue的这种做法,会影响到queue释放NSOperation。当广播isFinished,NSOperation.completionBlock就会调用。


②配置一些基本的请求属性

通过类名就能看出这个NSOperation是用来下图片的,所以需要配置一些网络请求相关的属性;修改.m中的interface如下:

#import "ImageDownloadOperation.h"


@interface ImageDownloadOperation ()

@property(nonatomic ,assign , getter = isFinished)BOOL IOFinished;

@property(nonatomic ,assign , getter = isExecuting)BOOL IOExecuting;

@property(nonatomic ,assign , getter = isConcurrent)BOOL IOConcurrent;

@property(nonatomic ,strong) NSURLConnection* connection;

@property(nonatomic ,strong) void(^completion)(NSData* data);

@property(nonatomic ,strong) NSURLRequest* request;

@property(nonatomic ,strong) NSMutableData* buff;


@end

添加了一个connection,request,回调completion,和一个data容器。

现在再添加一个init方法,初始化这些属性,这里直接给出实现:

-(instancetype)initWithRequest:(NSURLRequest*)request compeletion:(void(^)(NSData* data))completion{

   self = [superinit];

   if (self) {

       self.request = request;

       self.buff = [NSMutableDatadata];

       self.completion = completion;

        _IOConcurrent =YES;

    }

    return self;

}


这里说明下IOConcurrent,如果说只需要NSOperation实现并发,只需要直接重载isConcurrent返回yes就可以了,不过博客开头,我说了要做到能同步,所以这个属性要写成活的。


③重载start方法

#pragma mark - 重载方法

- (void)start{

    

    self.connection = [[NSURLConnectionalloc] initWithRequest:self.requestdelegate:selfstartImmediately:NO];

    [self.connectionstart];

    

}

首先初始化好connection,并且添加代理

@interface ImageDownloadOperation ()<NSURLConnectionDataDelegate>



#pragma mark - NSURLConnection delegate


- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{

    

}


- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{

    

}


- (void)connectionDidFinishLoading:(NSURLConnection *)connection{

    

}


- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{

    

}


上边这些操作都是在.m文件中
这时重点就来了,并发需要一个新的线程,这个线程的配置要在start中,这时需要手动创建。
再添加一个NSThread属性:

@property(nonatomic ,strong) NSThread* thread;

然后修改start方法:

#pragma mark - 重载方法

- (void)start{


    self.thread = [[NSThreadalloc] initWithTarget:selfselector:@selector(main)object:nil];

    [self.threadstart];

    

}


- (void)main{

    self.connection = [[NSURLConnectionalloc] initWithRequest:self.requestdelegate:selfstartImmediately:NO];

    [self.connectionstart];

}


为了兼容NSoperation+NSoperationQueue这种方法,调整代码结构,重载main。

④修改协议中的方法

#pragma mark - NSURLConnection delegate


- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{

   if (self.completion) {

       self.completion(nil);

    }

    self.IOExecuting =NO;

    self.IOFinished =YES;

}


- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{

    [self.buffappendData:data];

}


- (void)connectionDidFinishLoading:(NSURLConnection *)connection{

   if (self.completion) {

        self.completion(self.buff);

    }

    self.IOExecuting =NO;

    self.IOFinished =YES;

}


- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{

    

}


在这里我只考虑了最理想的状态,简单处理下下载的数据。

修改start方法:

#pragma mark - 重载方法

- (void)start{


    self.IOFinished =NO;

    self.IOExecuting =YES;

    self.thread = [[NSThreadalloc] initWithTarget:selfselector:@selector(main)object:nil];

    [self.threadstart];

    

}


⑤测试NSOperation

找个VC添加代码测试:

- (IBAction)IOClick:(id)sender {

    NSLog(@"IO click");

   self.imageVIew.image =nil;

    

    NSURLRequest* request = [NSURLRequestrequestWithURL:[NSURLURLWithString:@"http://f.hiphotos.baidu.com/image/w%3D1920%3Bcrop%3D0%2C0%2C1920%2C1200/sign=8db50237259759ee4a5064c280cb7875/50da81cb39dbb6fdd729831c0a24ab18972b379d.jpg"]cachePolicy:NSURLRequestReloadIgnoringCacheDatatimeoutInterval:60.f];

    self.operationA = [[ImageDownloadOperationalloc] initWithRequest:request

                                                compeletion:^(NSData *data) {

                                                    NSLog(@"IO completion");

                                                    if (data) {

                                                        UIImage* img = [UIImageimageWithData:data];

                                                         [[NSOperationQueuemainQueue] addOperationWithBlock:^{

                                                             [self.imageVIewsetImage:img];

                                                         }];

                                                     }

                                                 }];

    [self.operationAsetCompletionBlock:^{

        NSLog(@"IO real completionBlock");

    }];

    [self.operationAstart];

    

    NSLog(@"2....");




}


测试下来发现问题了,当我点击按钮的事时候,我的网络请求居然没有发起。
仔细看了下,发现是thread没有启动runloop,走完main方法,thread就被挂起了,这个时候就需要唤醒runloop,
修改main方法:

- (void)main{

    NSLog(@"ID main");

    self.connection = [[NSURLConnectionalloc] initWithRequest:self.requestdelegate:selfstartImmediately:NO];

    [self.connectionstart];

    [[NSRunLoopcurrentRunLoop] run];

}

效果图:


模拟器截屏有点大, 大笑。截止到这里基本上手动并发已经实现了,关于isCancel等暂不添加,先实现功能再优化

二、实现一个手动start的同步NSOperation

①修改start方法,不手动生成NSThread

- (void)start{

    NSLog(@"ID start in isMain : %@",[NSThread isMainThread]?@"yes":@"no");

    self.IOFinished = NO;

    self.IOExecuting = YES;

    

    if (self.isConcurrent) {

        

        self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(main) object:nil];

        [self.thread start];

        

    }else{

        

        self.thread = [NSThread currentThread];

        [self main];

    }


    NSLog(@"IO start thread = %@ isMain = %@",self.thread,self.thread.isExecuting?@"yes":@"no");


}

这里用IOConcurrent的的getter也是需要重载的父类方法来做判断,如果是同步,就直接调用main,也可以使用perform

        [self performSelector:@selector(main) onThread:self.thread withObject:nil waitUntilDone:NO];

这里有一点需要注意的,因为实在当前i线程同步,所以外部提供的这个线程的生命周期就要外部控制。


②返回到.h中添加一个同步调用方法startSync

-(void)startSync;

在.m中添加实现

-(void)startSync{

    self.IOConcurrent = NO;

    [self start];

}


这样就实现了同步NSOperation,现在代码的隐患是同一个NSOperation对象仅能在一个线程中start,虽然如此,但是基本上不会有人在多个线程中共用一个NSOperation。因为一个NSOperation只代表了一个任务。


③开辟了线程测试下:

- (IBAction)IOClick:(id)sender {

    NSLog(@"IO click");

    self.imageVIew.image = nil;

    

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        NSLog(@"queue thread = %@",[NSThread currentThread]);

        NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/w%3D1920%3Bcrop%3D0%2C0%2C1920%2C1200/sign=8db50237259759ee4a5064c280cb7875/50da81cb39dbb6fdd729831c0a24ab18972b379d.jpg"] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.f];

        self.operationA = [[ImageDownloadOperation alloc] initWithRequest:request

                                                              compeletion:^(NSData *data) {

                                                                  NSLog(@"IO completion");

                                                                  if (data) {

                                                                      UIImage* img = [UIImage imageWithData:data];

                                                                      [[NSOperationQueue mainQueue] addOperationWithBlock:^{

                                                                          [self.imageVIew setImage:img];

                                                                      }];

                                                                  }

                                                              }];

        [self.operationA setCompletionBlock:^{

            NSLog(@"IO real completionBlock");

        }];

        [self.operationA startSync];

        

        NSLog(@"2....");

        [[NSRunLoop currentRunLoop] run];

    });




}

手比较懒就随便写了个dispatch,如果真实项目中最好不要这样写,四不像、代码混乱

效果图:

三、实现NSOperation加入到NSOperationQueue

①添加一个标志位isInQueue
如果NSOperation加入到queue中,发布NSOperation状态,线程配置,这些NSOperationQueue全包了,我们只需要乖乖的看好任务就好了。
现在修改手动发布状态,

//发布NSOperation的状态


- (void)setIOFinished:(BOOL)IOFinished{

    if (!self.isInQueue) [self willChangeValueForKey:@"isFinished"];

    _IOFinished = IOFinished;

    if (!self.isInQueue) [self didChangeValueForKey:@"isFinished"];


}


//发布NSOperation的状态

- (void)setIOExecuting:(BOOL)IOExecuting{

    if (!self.isInQueue) [self willChangeValueForKey:@"isExecuting"];

    _IOExecuting = IOExecuting;

    if (!self.isInQueue) [self didChangeValueForKey:@"isExecuting"];

}


开辟线程的代码

- (void)start{

    if (self.isInQueue) {

        [super start];

    }

    else{

        NSLog(@"ID start in isMain : %@",[NSThread isMainThread]? @"yes":@"no");

        self.IOFinished = NO;

        self.IOExecuting = YES;

        

        if (self.isConcurrent) {

            

            self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(main) object:nil];

            [self.thread start];

            

        }else{

            

            self.thread = [NSThread currentThread];

            //        [self main];

            [self performSelector:@selector(main) onThread:self.thread withObject:nil waitUntilDone:NO];

            

        }

        

        //    timer = [NSTimer scheduledTimerWithTimeInterval:1. target:self selector:@selector(logo) userInfo:nil repeats:YES];

        NSLog(@"IO start thread = %@ isExecuting = %@",self.thread,self.thread.isExecuting?@"yes":@"no");

    }


}


②赋值isInQueue

既然需要标记是否在queue中,就需要在添加到queue的时候给 isInQueue赋值。
容易想到的有两种方法:
第一,在加入到queue中的时候手动set。
第二,创建一个NSOperationQueue子类,重载addOperation方法(在同一个.m文件中实现 @implementation

对于第一种,因为属性私有,所以行不通,第二种倒是可以,只是交互不好,不能因为一个operation就让修改所有的queue

这时一种高端大气的方法就诞生了: Method Swizzling

了解了上边的博客,就可以实现自己的method swizzling了

在ImageDownloadOperation.m中添加

#import <objc/runtime.h>

@implementation NSOperationQueue (ImageDownloadOperationQueue)

+(void)load{

    Class class = [self class];

    

    SEL orinigalSEL = @selector(addOperation:);

    SEL swizzleSEL = @selector(addImageDownloadOperation:);

    

    Method orinigalMD = class_getClassMethod(class, orinigalSEL);

    Method swizzleMD = class_getClassMethod(class, swizzleSEL);

    

    BOOL didAddMethod = class_addMethod(class, orinigalSEL, method_getImplementation(swizzleMD), method_getTypeEncoding(swizzleMD));

    

    if (didAddMethod) {

        class_replaceMethod(class, swizzleSEL, method_getImplementation(orinigalMD), method_getTypeEncoding(orinigalMD));

    }

    else{

        method_exchangeImplementations(orinigalMD, swizzleMD);

    }

    

    

    

}


- (void)addImageDownloadOperation:(NSOperation *)op{

    if ([op isKindOfClass:[ImageDownloadOperation class]]) {

        __weak ImageDownloadOperation* weakOP = (ImageDownloadOperation*)op;

        weakOP.isInQueue = YES;

        weakOP.IOConcurrent = YES;

    }

    [self addImageDownloadOperation:op];

}




@end


如果你觉得上边的方法发生了死循环,就要再看看刚刚给出的链接了


③修改测试代码

- (IBAction)IOClick:(id)sender {

    NSLog(@"IO click");

    self.imageVIew.image = nil;

    

    NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/w%3D1920%3Bcrop%3D0%2C0%2C1920%2C1200/sign=8db50237259759ee4a5064c280cb7875/50da81cb39dbb6fdd729831c0a24ab18972b379d.jpg"] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.f];

    ImageDownloadOperation* op = [[ImageDownloadOperation alloc] initWithRequest:request

                                                          compeletion:^(NSData *data) {

                                                              NSLog(@"IO completion");

                                                              if (data) {

                                                                  UIImage* img = [UIImage imageWithData:data];

                                                                  [[NSOperationQueue mainQueue] addOperationWithBlock:^{

                                                                      [self.imageVIew setImage:img];

                                                                  }];

                                                              }

                                                          }];

    [op setCompletionBlock:^{

        NSLog(@"IO real completionBlock");

    }];


//    [self.operationA startSync];

    [self.operationQueue addOperation:op];

    

    NSLog(@"2....");



}


效果图: 



四、添加一个startAsync

在原有的基础上添加这个方法即可

-(void)startAsync{

    self.IOConcurrent = YES;

    [self start];

}


测试代码就不贴了,效果是可以的 大笑


如果有写错的地方,还望大家不吝赐教!!


































  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值