IOS开发~GCD


















实现代码:

CGDHelper

/*
 *  Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。
 *  系统要求:iOS4.0以上。
 */

#import <Foundation/Foundation.h>
 
///     enum 声明     /

//队列优先级
typedef enum
{
    GlobalQueuePriorityDefault = 0,
    GlobalQueuePriorityHigh = 2,
    GlobalQueuePriorityLow = -2,
    GlobalQueuePriorityBackground = INT16_MIN
    
} GlobalQueuePriority;

//阻塞、非阻塞
typedef enum
{
    PerformBlockFeatureChoke,
    PerformBlockFeatureUnchoke
    
} PerformBlockFeature;

//网络请求方法
typedef enum GCDHelperHttpRequestMethod
{
    GCDHelperHttpRequestMethodGET = 0,
    GCDHelperHttpRequestMethodPOST
    
} GCDHelperHttpRequestMethod;

///     Block 声明     /

//返回值void
typedef     void (^GCDBlock)                         (void);
typedef     void (^GCDBlock1_Size_t)                 (size_t index);
typedef     void (^GCDBlock1_Int)                    (int index);
typedef     void (^GCDBlock1_Bool)                   (BOOL flag);
typedef     void (^GCDBlock1_Float)                  (float index);
typedef     void (^GCDBlock1_Obj)                    (id object);

//返回值void,两个形式参数
typedef     void (^GCDBlock2)                        (id object1, size_t index);
typedef     void (^GCDBlock2_Obj_Int)                (id object1, int index);
typedef     void (^GCDBlock2_Obj_Obj)                (id object1, id object2);

//有返回值
typedef     id   (^GCD_Obj_Block_Obj)                (id object);
typedef     id   (^GCD_Obj_Block_Void)               (void);

typedef     void (^GCDHttpRequestBlock)              (NSURLResponse *response, NSError *error, NSData *data);


///     GCDHelper 声明     /

@interface GCDHelper : NSObject

/* 获取3种队列 */
+ (dispatch_queue_t) gcdMainQueue;
+ (dispatch_queue_t) gcdGlobalQueue:(GlobalQueuePriority) priority;
+ (dispatch_queue_t) gcdCustomQueue:(NSString *) queueName;

//后台执行
+ (void) gcdPerformBlockAsynchronous:(GCDBlock) block;

//后台获取数据后,回到主线程
+ (void) gcdPerformBlockAsynchronous:(GCDBlock) blockAsyn
                         finishOnMainQueue:(GCDBlock) blockM;


/* 3种队列上执行Block 
 *
 * 是否阻塞执行:(PerformBlockFeature) feature
 * 全局队列优先级:(GlobalQueuePriority) priority
 */
+ (void) gcdPerformBlockOnMainQueue:(GCDBlock) block feature:(PerformBlockFeature) feature;

+ (void) gcdPerformBlockOnGlobalQueue:(GCDBlock) block
                              feature:(PerformBlockFeature) feature
                             priority:(GlobalQueuePriority) priority;

+ (void) gcdPerformBlockOnCustomQueue:(GCDBlock) block
                              feature:(PerformBlockFeature) feature
                                 name:(NSString *) queueName;


//延迟执行方法
+ (void) gcdPerformBlock:(GCDBlock) block
                 onQueue:(dispatch_queue_t) queue
             delaySecond:(int64_t) second;


//只执行一次
+ (void) gcdPerformBlockOnce:(GCDBlock) block;

//并发
+ (void) gcdBatchPerformBlocks:(NSArray *) blockArray finally:(GCDBlock) finallyBlock;

+ (void) gcdBatchPerformBlockWithData:(NSArray *) dataArray
          maxConcurrentOperationCount:(uint) count
                          handleBlock:(GCDBlock1_Obj) block
                              finally:(GCDBlock1_Obj) finallyBlock;



@end

///     图片下载     /

@interface GCDHelper (ImageDownload)

- (void) gcdImageWithURLString:(NSString *) URLString;
- (void) gcdImageWithURLString:(NSString *) URLString completion:(GCDBlock2_Obj_Obj) completion;

@end

///     网络请求     /

GCDBlock1_Bool _netWorkBlock;
@interface GCDHelper (NetworkConnect)

//网络连接判断、实时监控
- (void) gcdNetWorkGuarder:(NSString *) hostname withBlock:(GCDBlock1_Bool) block;

@end


@interface GCDHelper (HttpRequest)

//GCD请求网络(GET方式测试通过,POST方式测试未通过)
- (void) gcdHttpRequestWithURL:(NSString *) URLString
                    httpMethod:(GCDHelperHttpRequestMethod) method
                        params:(NSDictionary *) params
                       timeout:(NSTimeInterval) time
                       success:(GCDHttpRequestBlock) successBlock
                          fail:(GCDHttpRequestBlock) failBlock;

@end



#import "GCDHelper.h"

#import <SystemConfiguration/SystemConfiguration.h>

#import <sys/socket.h>
#import <netinet/in.h>
#import <netinet6/in6.h>
#import <arpa/inet.h>
#import <ifaddrs.h>
#import <netdb.h>

//Error
#define GCDHelperErrorURLISNULL   [NSError errorWithDomain:@"please setup GCDHelper‘s url or urlString" code:100 userInfo:nil]
#define GCDHelperErrorRequestISNULL  [NSError errorWithDomain:@"request can not be nil!" code:101 userInfo:nil]
#define GCDHelperErrorFileExist    [NSError errorWithDomain:@"File Exist!" code:102 userInfo:nil]
#define GCDHelperErrorCreateFail   [NSError errorWithDomain:@"Create File Fail!" code:103 userInfo:nil]

//下载的临时文件的后缀
#define kTHDownLoadTask_TempSuffix  @".TempDownload"
//计算下载速度的取样时间
#define kTHDownLoadTimerInterval  2.0
//THDispatchQueue默认的并发数
#define kTHDispatchQueueDefaultConcurrentCount 10

#define kDefaultTimeoutInterval  15


static NSString * const BOUNDRY        = @"--------------------------7d71a819230404";


@implementation GCDHelper

- (void) dealloc
{
    [super dealloc];
}

- (id) init
{
    if (self = [super init])
    {
    }
    
    return self;
}

#pragma mark -
#pragma mark 获取队列

+ (dispatch_queue_t) gcdMainQueue
{
    return dispatch_get_main_queue();
}

+ (dispatch_queue_t) gcdGlobalQueue:(GlobalQueuePriority) priority
{
    switch (priority)
    {
        case GlobalQueuePriorityDefault:
            return dispatch_get_global_queue(priority, 0);
            break;
        case GlobalQueuePriorityHigh:
            return dispatch_get_global_queue(priority, 0);
            break;
        case GlobalQueuePriorityLow:
            return dispatch_get_global_queue(priority, 0);
            break;
        case GlobalQueuePriorityBackground:
            return dispatch_get_global_queue(priority, 0);
            break;
            
        default:
            return dispatch_get_global_queue(GlobalQueuePriorityDefault, 0);
            break;
    }
}

+ (dispatch_queue_t) gcdCustomQueue:(NSString *) queueName;
{
    return dispatch_queue_create([queueName UTF8String], NULL);
}

#pragma mark -
#pragma mark 3种队列上执行Block

+ (void) gcdPerformBlockOnMainQueue:(GCDBlock) block feature:(PerformBlockFeature) feature
{
    switch (feature)
    {
        case PerformBlockFeatureChoke:
            dispatch_sync([GCDHelper gcdMainQueue], block);
            break;
            
        case PerformBlockFeatureUnchoke:
            dispatch_async([GCDHelper gcdMainQueue], block);
            break;
            
        default:
            dispatch_sync([GCDHelper gcdMainQueue], block);
            break;
    }
}

+ (void) gcdPerformBlockOnGlobalQueue:(GCDBlock) block feature:(PerformBlockFeature) feature priority:(GlobalQueuePriority) priority
{
    switch (feature)
    {
        case PerformBlockFeatureChoke:
            dispatch_sync([GCDHelper gcdGlobalQueue:priority], block);
            break;
            
        case PerformBlockFeatureUnchoke:
            dispatch_async([GCDHelper gcdGlobalQueue:priority], block);
            break;
            
        default:
            dispatch_sync([GCDHelper gcdGlobalQueue:GlobalQueuePriorityDefault], block);
            break;
    }
}

+ (void) gcdPerformBlockOnCustomQueue:(GCDBlock) block feature:(PerformBlockFeature) feature name:(NSString *) queueName
{
    switch (feature)
    {
        case PerformBlockFeatureChoke:
            dispatch_sync([GCDHelper gcdCustomQueue:queueName], block);
            break;
            
        case PerformBlockFeatureUnchoke:
            dispatch_async([GCDHelper gcdCustomQueue:queueName], block);
            break;
            
        default:
            dispatch_sync([GCDHelper gcdCustomQueue:@"com.GCDHelper.Queue"], block);
            break;
    }
}

//后台执行
+ (void) gcdPerformBlockAsynchronous:(GCDBlock) block
{
    [GCDHelper gcdPerformBlockOnGlobalQueue:block
                                    feature:PerformBlockFeatureUnchoke
                                   priority:GlobalQueuePriorityDefault];
}

//后台获取数据后,回到主线程
+ (void) gcdPerformBlockAsynchronous:(GCDBlock) blockAsyn
                   finishOnMainQueue:(GCDBlock) blockM
{
    dispatch_async([GCDHelper gcdGlobalQueue:GlobalQueuePriorityDefault], ^{
        blockAsyn();
        dispatch_async([GCDHelper gcdMainQueue], ^{
            blockM();
        });
    });
}

#pragma mark -
#pragma mark 队列延迟时间执行方法
+ (void) gcdPerformBlock:(GCDBlock) block onQueue:(dispatch_queue_t) queue delaySecond:(int64_t) second
{
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, second * NSEC_PER_SEC);
    dispatch_after(popTime, queue, block);
}

#pragma mark -
#pragma mark 只执行一次

+ (void) gcdPerformBlockOnce:(GCDBlock) block
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, block);
}

#pragma mark -
#pragma mark 无序并发

+ (void) gcdBatchPerformBlocks:(NSArray *) blockArray finally:(GCDBlock) finallyBlock
{
    [blockArray retain];
    
    dispatch_queue_t queue = [GCDHelper gcdGlobalQueue:GlobalQueuePriorityDefault];
    dispatch_group_t group = dispatch_group_create();
    
    for(GCDBlock block in blockArray)
    {
        dispatch_group_async(group, queue, ^{
            block();
        });
    }
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    dispatch_async([GCDHelper gcdGlobalQueue:GlobalQueuePriorityDefault], ^{
        finallyBlock();
    });
    
    dispatch_release(group);
    
    [blockArray release];
}

+ (void) gcdBatchPerformBlockWithData:(NSArray *) dataArray
          maxConcurrentOperationCount:(uint) count
                          handleBlock:(GCDBlock1_Obj) block
                              finally:(GCDBlock1_Obj) finallyBlock
{
    [dataArray retain];
    
    dispatch_queue_t queue = [GCDHelper gcdGlobalQueue:GlobalQueuePriorityDefault];
    dispatch_group_t group = dispatch_group_create();
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(count);
    for(id obj in dataArray)
    {
        NSLog(@"并发中");
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_group_async(group, queue, ^{
            block(obj);
            dispatch_semaphore_signal(semaphore);
        });
    }
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    dispatch_group_notify(group, queue, ^{
        finallyBlock(dataArray);
    });
    dispatch_release(group);
    
    [dataArray release];
}



#pragma mark -
#pragma mark 图片下载

- (void) gcdImageWithURLString:(NSString *) URLString
{
    [self gcdImageWithURLString:URLString completion:nil];
}

- (void) gcdImageWithURLString:(NSString *) URLString completion:(GCDBlock2_Obj_Obj) completion
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
        [request setURL:[NSURL URLWithString:URLString]];
        [request setHTTPMethod:@"GET"];
        NSData *returnData = [NSURLConnection sendSynchronousRequest:request
                                                   returningResponse:nil
                                                               error:nil];
        [request release];
        
        UIImage *image  = [UIImage imageWithData:returnData];
        
        if (image)
        {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                completion(image, URLString);
            });
        } else
        {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                completion(image, URLString);
            });
        }
    });
}

@end


#pragma mark -
#pragma mark 网络部分

@implementation GCDHelper (NetworkConnect)

- (BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags
{
    BOOL connectionUP = YES;
    
    if(!(flags & kSCNetworkReachabilityFlagsReachable))
        connectionUP = NO;
    
    if( (flags & (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection)) == (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection) )
        connectionUP = NO;
    
    return connectionUP;
}

-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags
{
    dispatch_async(dispatch_get_main_queue(), ^{
        _netWorkBlock([self isReachableWithFlags:flags]);
    });
}

static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info)
{
    @autoreleasepool
    {
        [(GCDHelper *)info reachabilityChanged:flags];
    }
}

- (void) gcdNetWorkGuarder:(NSString *) hostname withBlock:(GCDBlock1_Bool) block
{
    _netWorkBlock = block;
    
    SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]);
    SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL };
    dispatch_queue_t queue = dispatch_queue_create("com.myself.reachability", NULL);
    context.info = (void *)self;
    SCNetworkReachabilitySetCallback(ref, TMReachabilityCallback, &context);
    SCNetworkReachabilitySetDispatchQueue(ref, queue);
}

@end

@implementation GCDHelper(HttpRequest)

- (void) startPOSTHTTPRequest:(NSString *) URLString
                       params:(NSDictionary *) params
                      timeout:(NSTimeInterval) time
                      success:(GCDHttpRequestBlock) successBlock
                         fail:(GCDHttpRequestBlock) failBlock
{
    [params retain];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        __block NSURLResponse  *response = nil;
        __block NSError *error = nil;
        __block NSData *receiveData = nil;
        
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
        
        [request setURL:[NSURL URLWithString:[URLString lowercaseString]]];
        [request setHTTPMethod:@"POST"];
        [request setCachePolicy:NSURLRequestUseProtocolCachePolicy];
        [request setTimeoutInterval:time];
        
        if (!request)
        {
            NSDictionary *errorInfo = [NSDictionary dictionaryWithObjectsAndKeys:@"发送请求失败", @"errorKey", nil];
            error = [NSError errorWithDomain:@"www.myself.com" code:100 userInfo:errorInfo];
            
            dispatch_async(dispatch_get_main_queue(), ^{
                successBlock(response, error, receiveData);
            });
            
            return;
        }
        
        if (params != nil)
        {
            [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", BOUNDRY]
           forHTTPHeaderField:@"Content-Type"];
            
            int len=512;
            NSMutableData *postData =[NSMutableData dataWithCapacity:len];
            [postData appendData:[[NSString stringWithFormat:@"--%@/r/n", BOUNDRY]
                                  dataUsingEncoding:NSUTF8StringEncoding]];
            int i=0;
            int cnt = [params count];
            
            for (NSString *key in [params allKeys])
            {
                // NSString *str = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"/r/n/r/n", key];
                [postData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"/r/n/r/n", key] dataUsingEncoding:NSUTF8StringEncoding]];
                
                [postData  appendData: [[NSString stringWithFormat:@"%@",[params objectForKey:key]]
                                        dataUsingEncoding:NSUTF8StringEncoding]];
                if(i != cnt - 1)
                {
                    [postData appendData:[[NSString stringWithFormat:@"/r/n--%@/r/n", BOUNDRY]
                                          dataUsingEncoding:NSUTF8StringEncoding]];
                }
                i++ ;
            }
            [postData  appendData:[[NSString stringWithFormat:@"/r/n--%@--/r/n", BOUNDRY]
                                   dataUsingEncoding:NSUTF8StringEncoding]];
            
            [request  setHTTPBody:postData];
        }
        
        receiveData = [[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error] retain];
        if (!error)
        {
            dispatch_async(dispatch_get_main_queue(), ^{
                successBlock(response, nil, receiveData);
            });
        }
        else
        {
            dispatch_async(dispatch_get_main_queue(), ^{
                successBlock(response, error, receiveData);
            });
        }
        
        [request release];
    });
    
    [params release];
}

- (void) startGETHTTPRequest:(NSString *) URLString
                      params:(NSDictionary *) params
                     timeout:(NSTimeInterval) time
                     success:(GCDHttpRequestBlock) successBlock
                        fail:(GCDHttpRequestBlock) failBlock
{
    [params retain];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        __block NSURLResponse  *response = nil;
        __block NSError *error = nil;
        __block NSData *receiveData = nil;
        
        NSMutableString *paramsString = [[NSMutableString alloc] init];
        for(NSString *key in params)
        {
            [paramsString appendFormat:@"&%@=%@", key, [params objectForKey:key]];
        }
        NSString *requestString = [[NSString alloc] initWithFormat:@"%@%@", URLString, paramsString];
        NSURL *reqUrl = [[NSURL alloc] initWithString:requestString];
        
        [paramsString release];
        [requestString release];
        
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
        
        [request setURL:reqUrl];
        [request setHTTPMethod:@"GET"];
        [request setCachePolicy:NSURLRequestUseProtocolCachePolicy];
        [request setTimeoutInterval:time];
        
        [reqUrl release];
    
        if (request)
        {
            receiveData = [[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error] retain];
        }
        
        if (!error)
        {
            dispatch_async(dispatch_get_main_queue(), ^{
                successBlock(response, nil, receiveData);
            });
        }
        else
        {
            dispatch_async(dispatch_get_main_queue(), ^{
                successBlock(response, error, receiveData);
            });
        }
        
        [request release];
    });
    
    [params release];
}

- (void) gcdHttpRequestWithURL:(NSString *) URLString
                    httpMethod:(GCDHelperHttpRequestMethod) method
                        params:(NSDictionary *) params
                       timeout:(NSTimeInterval) time
                       success:(GCDHttpRequestBlock) successBlock
                          fail:(GCDHttpRequestBlock) failBlock
{
    switch (method)
    {
        case GCDHelperHttpRequestMethodGET:
        {
            [self startGETHTTPRequest:URLString params:params timeout:time success:successBlock fail:failBlock];
            break;
        }
        case GCDHelperHttpRequestMethodPOST:
        {
            [self startPOSTHTTPRequest:URLString params:params timeout:time success:successBlock fail:failBlock];
            break;
        }

        default:
            break;
    }
}

@end




用法举例:

一、基本概念举例:

#import <UIKit/UIKit.h>

@interface BaseViewController : UIViewController
{
    IBOutlet UITextField *field1;
    IBOutlet UITextField *field2;
    IBOutlet UITextField *field3;
    
    IBOutlet UITextField *textField;
    
    dispatch_queue_t queue;
}

- (IBAction) calculate:(id)sender;
- (IBAction) operationQueue:(id)sender;
- (IBAction) gcd:(id)sender;

- (IBAction) notchoke:(id)sender;
- (IBAction) choke:(id)sender;

- (IBAction) getUIData:(id)sender;


- (IBAction)startQueue:(id)sender;
- (IBAction)suspendQueue:(id)sender;
- (IBAction)resumeQueue:(id)sender;

@end

#import "BaseViewController.h"

@implementation BaseViewController

- (void) dealloc
{
    dispatch_release(queue);
    
    [super dealloc];
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        queue = dispatch_queue_create("sss", NULL);
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
}

- (void) longTask:(id) sender
{
    NSMutableArray *arr = [NSMutableArray array];
    for (int i = 0; i < 1000; i++) {
        
        [arr addObject:[NSMutableArray arrayWithObject:@(i)]];
        NSLog(@"longTask:%d", i);
    }
}

- (void) longTaskOther:(id) sender
{
    NSMutableArray *arr = [NSMutableArray array];
    for (int i = 0; i < 10000; i++) {
        
        [arr addObject:[NSMutableArray arrayWithObject:@(i)]];
        NSLog(@"longTaskOther:%d", i);
    }
}

- (IBAction) calculate:(id)sender
{
    field3.text = [NSString stringWithFormat:@"%f", [field1.text floatValue] - [field2.text floatValue]];
}
- (IBAction) operationQueue:(id)sender;
{
    NSOperationQueue *aqueue = [NSOperationQueue new];
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]
                                        initWithTarget:self
                                        selector:@selector(longTask:)
                                        object:nil];
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc]
                                        initWithTarget:self
                                        selector:@selector(longTaskOther:)
                                        object:nil];
    

    [aqueue addOperation:operation];
    [aqueue addOperation:operation1];
    
    [operation release];
    [operation1 release];
}
- (IBAction) gcd:(id)sender  //3.192999
{
    [GCDHelper gcdPerformBlockAsynchronous:^{
        NSMutableArray *arr = [NSMutableArray array];
        for (int i = 0; i < 1000; i++) {
            
            [arr addObject:[NSMutableArray arrayWithObject:@(i)]];
            NSLog(@"longTask:%d", i);
        }
    }];
    
    [GCDHelper gcdPerformBlockAsynchronous:^{
        NSMutableArray *arr = [NSMutableArray array];
        for (int i = 0; i < 10000; i++) {
            
            [arr addObject:[NSMutableArray arrayWithObject:@(i)]];
            NSLog(@"longTaskOther:%d", i);
        }
    }];
}

//

- (IBAction)notchoke:(id)sender
{
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"qqq");
    });

    NSLog(@"不阻塞");
}


//Calls to dispatch_sync() targeting the current queue will result
//* in dead-lock. Use of dispatch_sync() is also subject to the same
//* multi-party dead-lock problems that may result from the use of a mutex.
//* Use of dispatch_async() is preferred.
//在当前队列上调用dispatch_sync() 会导致死锁。调用dispatch_sync(),并使用mutex 经常会导致多方死锁问题。
- (IBAction) choke:(id)sender
{
    dispatch_queue_t exampleQueue;
    
    int i = 3;
    switch (i) {
        case 0:
            exampleQueue = dispatch_get_global_queue(0, 0);
            break;
        case 1:
            exampleQueue = dispatch_queue_create("com.abc.xxx", NULL);
            break;
        case 2:
            exampleQueue = dispatch_get_current_queue();
            break;
        case 3:
            exampleQueue = dispatch_get_main_queue();
            break;
            
        default:
            exampleQueue = dispatch_get_global_queue(0, 0);
            break;
    }
    
    dispatch_sync( exampleQueue,^{
        [self longTask:nil];
    });
    
    NSLog(@"task finish");
}

- (IBAction) getUIData:(id)sender
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        __block NSString *stringValue;
        dispatch_sync(dispatch_get_main_queue(), ^{
            stringValue = [textField.text copy];
        });
        
        [stringValue retain];
        
        NSLog(@"stringValue:%@", stringValue);
    });
}



//一个要注意的地方是,dispatch queue的挂起是block粒度的。换句话说,挂起一个queue并不会将当前正在执行的block挂起。它会允许当前执行的block执行完毕,然后后续的block不再会被执行,直至queue被恢复。
//还有一个注意点:从man页上得来的:如果你挂起了一个queue或者source,那么销毁它之前,必须先对其进行恢复。
- (IBAction)startQueue:(id)sender
{
    dispatch_async(queue, ^{
        for (int i = 0; i < 10000; i++) {
            NSLog(@"taskA");
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10000; i++) {
            NSLog(@"taskB");
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10000; i++) {
            NSLog(@"taskC");
        }
    });
}
- (IBAction)suspendQueue:(id)sender
{
    NSLog(@"Queue suspend");
    dispatch_suspend(queue);

}
- (IBAction)resumeQueue:(id)sender
{
    NSLog(@"Queue resume");
    dispatch_resume(queue);

}

二、基本用法举例

例子1:

#import <UIKit/UIKit.h>

@interface OneViewController : UIViewController


//无序并发
- (IBAction)selector0:(id)sender;

//无序并发处理数据
- (IBAction)selector100:(id)sender;

//执行一次
- (IBAction)selector1:(id)sender;

//异步/后台执行
- (IBAction)selector2:(id)sender;

//后台执行,然后返回主线程
- (IBAction)selector3:(id)sender;

//三种队列执行
- (IBAction)selector4:(UISegmentedControl *)sender;

//延迟执行
- (IBAction)selector5:(id)sender;

@end

#import "OneViewController.h"

@interface OneViewController ()

@end

@implementation OneViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
}

- (NSMutableArray *) getBlockArray
{
    NSMutableArray *arr = [[NSMutableArray array] retain];
    
    GCDBlock b0 = ^{ NSLog(@"无序并发: 0"); sleep(3); }; [arr addObject:b0];
    GCDBlock b1 = ^{ NSLog(@"无序并发: 1"); }; [arr addObject:b1];
    GCDBlock b2 = ^{ NSLog(@"无序并发: 2"); }; [arr addObject:b2];
    GCDBlock b3 = ^{ NSLog(@"无序并发: 3"); }; [arr addObject:b3];
    GCDBlock b4 = ^{ NSLog(@"无序并发: 4"); }; [arr addObject:b4];
    GCDBlock b5 = ^{ NSLog(@"无序并发: 5"); }; [arr addObject:b5];
    GCDBlock b6 = ^{ NSLog(@"无序并发: 6"); }; [arr addObject:b6];
    GCDBlock b7 = ^{ NSLog(@"无序并发: 7"); }; [arr addObject:b7];
    GCDBlock b8 = ^{ NSLog(@"无序并发: 8"); }; [arr addObject:b8];
    GCDBlock b9 = ^{ NSLog(@"无序并发: 9"); }; [arr addObject:b9];
    GCDBlock b10 = ^{ NSLog(@"无序并发: 10"); }; [arr addObject:b10];
    GCDBlock b11 = ^{ NSLog(@"无序并发: 11"); }; [arr addObject:b11];
    GCDBlock b12 = ^{ NSLog(@"无序并发: 12"); }; [arr addObject:b12];
    GCDBlock b13 = ^{ NSLog(@"无序并发: 13"); }; [arr addObject:b13];
    GCDBlock b14 = ^{ NSLog(@"无序并发: 14"); }; [arr addObject:b14];
    GCDBlock b15 = ^{ NSLog(@"无序并发: 15"); }; [arr addObject:b15];
    
    return arr;
}

//无序并发
- (IBAction)selector0:(id)sender
{
    [GCDHelper gcdBatchPerformBlocks:[self getBlockArray] finally:^{
        NSLog(@"一组有序并发完成");
    }];
    
//    NSLog(@"一组无序并发完成");
}


- (IBAction)selector100:(id)sender
{
    NSMutableArray *arr = [NSMutableArray array];
    for (int i = 0; i < 100; i++) {
        [arr addObject:[NSMutableArray array]];
    }
    
    __block int i = 0;
    [GCDHelper gcdBatchPerformBlockWithData:arr maxConcurrentOperationCount:10 handleBlock:^(id object) {
        
        sleep(1);
        NSMutableArray *arr = (NSMutableArray *)object;
        [arr addObject:@(i)];
        i++;
    } finally:^(id object) {
        NSLog(@"arr:%@", object);
    }];
}

- (IBAction)selector1:(id)sender
{
    [GCDHelper gcdPerformBlockOnce:^{
        NSLog(@"别想让我执行第二次");
    }];
    NSLog(@"不执行~");
}

//异步/后台执行
- (IBAction)selector2:(id)sender
{
    [GCDHelper gcdPerformBlockAsynchronous:^{
        sleep(3);
         NSLog(@"全局队列执行完成");
    }];
    NSLog(@"全局队列执行,不影响主队列");
}

//后台执行,然后返回主线程
- (IBAction)selector3:(id)sender
{
    [GCDHelper gcdPerformBlockAsynchronous:^{
       
        for (int i = 0; i< 10; i++)
        {
            NSLog(@"全局队列执行: %d", i);
        }
        
    } finishOnMainQueue:^{
        NSLog(@"回到主队列");
    }];
}

//三种队列执行
- (IBAction)selector4:(UISegmentedControl *)sender
{
    switch (sender.selectedSegmentIndex) {
        case 0:
        {
            [GCDHelper gcdPerformBlockOnMainQueue:^{
                NSLog(@"主队列执行");
            } feature:PerformBlockFeatureUnchoke];
            break;
        }
        case 1:
        {
            [GCDHelper gcdPerformBlockOnGlobalQueue:^{
                NSLog(@"全局队列执行");
            } feature:PerformBlockFeatureUnchoke priority:GlobalQueuePriorityDefault];
            break;
        }
        case 2:
        {
            [GCDHelper gcdPerformBlockOnCustomQueue:^{
                NSLog(@"自创建队列执行");
            } feature:PerformBlockFeatureUnchoke name:@"com.abc.bcd"];
            break;
        }
            
        default:
            break;
    }
}

//延迟执行
- (IBAction)selector5:(id)sender
{
    NSLog(@"延迟 2s 执行");
    [GCDHelper gcdPerformBlock:^{
        NSLog(@"执行完毕");
    } onQueue:[GCDHelper gcdMainQueue] delaySecond:2];
}

@end

例子2:

#import <UIKit/UIKit.h>

@interface MulthreadConcurrentVC : UIViewController


@end

#import "MulthreadConcurrentVC.h"

/*
 
 如何在GCD中快速的控制并发呢?答案就是
 dispatch_semaphore,对经常做unix开发的人来讲,我所介绍的内容可能就显得非常入门级了,信号量在他们的多线程开发中再平常不过了。
 在GCD中有三个函数是semaphore的操作,分别是:
 dispatch_semaphore_create          创建一个semaphore
 dispatch_semaphore_signal          发送一个信号
 dispatch_semaphore_wait              等待信号
 简单的介绍一下这三个函数,第一个函数有一个整形的参数,我们可以理解为信号的总量,dispatch_semaphore_signal是发送一个信号,自然会让信号总量加1,dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量减少1,根据这样的原理,我们便可以快速的创建一个并发控制。
 
 */


/*
 
简单的介绍一下这一段代码,创建了一个初使值为10的semaphore,每一次for循环都会创建一个新的线程,线程结束的时候会发送一个信号,线程创建之前会信号等待,所以当同时创建了10个线程之后,for循环就会阻塞,等待有线程结束之后会增加一个信号才继续执行,如此就形成了对并发的控制,如上就是一个并发数为10的一个线程队列。
 
*/

@implementation MulthreadConcurrentVC

- (void) loadView
{
    [super loadView];
}

- (void)aSelector:(id)sender
{
    dispatch_group_t group = dispatch_group_create();
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (int i = 0; i < 100; i++)
    {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_group_async(group, queue, ^{
            NSLog(@"%i",i);
            sleep(2);
            dispatch_semaphore_signal(semaphore);
        });
    }
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    dispatch_release(group);
    dispatch_release(semaphore);
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    UIButton *bt = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    bt.frame = CGRectMake(100, 100, 120, 120);
    [bt addTarget:self action:@selector(aSelector:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:bt];
}

三、GCD实际应用举例

#import <UIKit/UIKit.h>

#import "GCDHelper.h"

@interface TableViewController : UITableViewController

@end

#import "TableViewController.h"
#import "CustomCell.h"
#import <objc/runtime.h>

static char * const kIndexPathAssociationKey = "JK_indexPath";

@interface TableViewController ()

@end

@implementation TableViewController

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.clearsSelectionOnViewWillAppear = NO;
    self.navigationItem.rightBarButtonItem = self.editButtonItem;
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 100;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
        UIImageView *im = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 40)];
        im.tag = 10;
        [cell addSubview:im];
        [im release];
    }
    
    return cell;
}

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
//    http://localhost:8888/Imgs/img0.png
//    http://theme.blogcn.com/wp-content/themes/coffee-desk/images/rsscoffee.PNG
    
    NSString *imgURLStr = nil;
    if ((indexPath.row % 2) == 0)
    {
        imgURLStr = @"http://localhost:8888/Imgs/img0.png";
    } else
    {
        imgURLStr = @"http://localhost:8888/Imgs/img1.png";
    }
    
    GCDHelper *hp = [GCDHelper new];
    [hp gcdImageWithURLString:imgURLStr
                   completion:^(id object1, id object2) {
                    
                       dispatch_async(dispatch_get_main_queue(), ^{
                           UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
                           [(UIImageView *)[cell viewWithTag:10] setImage:(UIImage *)object1];
                       });
                   }];
}

#pragma mark - Table view delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
}

#pragma mark -
#pragma mark - cell重用

- (void)tableViewCellIsPreparingForReuse:(NSNotification *)notification
{
	if ([[notification object] isKindOfClass:[CustomCell class]]) {
		CustomCell *cell = (CustomCell *)[notification object];
		
		objc_setAssociatedObject(cell,
								 kIndexPathAssociationKey,
								 nil,
								 OBJC_ASSOCIATION_RETAIN);
		
		[[cell imageView] setImage:nil];
	}
}

@end

#import <UIKit/UIKit.h>

extern NSString * const kJKPrepareForReuseNotification;

@interface CustomCell : UITableViewCell

@end

#import "CustomCell.h"

NSString * const kJKPrepareForReuseNotification = @"JKCallbacksTableViewCell_PrepareForReuse";

@implementation CustomCell

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        //如果cell 的图片发生改变,当cell重用的时候,刷新图片
        
		[[self imageView] addObserver:self
						   forKeyPath:@"image"
							  options:NSKeyValueObservingOptionOld
							  context:NULL];
    }
    return self;
}

- (void)observeValueForKeyPath:(NSString *)keyPath
					  ofObject:(id)object
						change:(NSDictionary *)change
					   context:(void *)context
{
    NSLog(@"observeValueForKeyPath");
    
	if (object == [self imageView] &&
		[keyPath isEqualToString:@"image"] &&
		([change objectForKey:NSKeyValueChangeOldKey] == nil ||
		 [change objectForKey:NSKeyValueChangeOldKey] == [NSNull null]))
    {
		[self setNeedsLayout];
	}
}

- (void)prepareForReuse
{
	[[NSNotificationCenter defaultCenter] postNotificationName:kJKPrepareForReuseNotification
														object:self];
	
	[super prepareForReuse];
}

@end

----------------------------------------------

#import <Foundation/Foundation.h>

@interface NetGuarder : NSObject

+ (NetGuarder *) shareNetGuarder;

@end

#import "NetGuarder.h"

@implementation NetGuarder

static NetGuarder *guarder = nil;

+ (void) getNetConnectMsg
{
    GCDHelper *hp = [GCDHelper new];
    [hp gcdNetWorkGuarder:@"www.baidu.com" withBlock:^(BOOL flag) {
        if (flag)
        {
            NSLog(@"Net connect");
        } else
        {
            NSLog(@"Net not connect");
        }
    }];
}

+ (NetGuarder *) shareNetGuarder
{
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        
        NSLog(@"单例创建");
        guarder = [[self alloc] init];
        
        [NetGuarder getNetConnectMsg];
    });
    
    return guarder;
}

@end

-------------------------------------------

#import <UIKit/UIKit.h>

@interface URLConViewController : UIViewController <NSURLConnectionDataDelegate>
{
    IBOutlet UISegmentedControl *segment;
    IBOutlet UILabel *label;
}

@end

#import "URLConViewController.h"

typedef struct _INT
{
    int t1;
    
}INT_STRUCT;

@interface URLConViewController ()
{
    NSMutableData *receivedData;
    BOOL finished;
}

@end

@implementation URLConViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self)
    {
        receivedData = [[NSMutableData data] retain];
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}

- (void) cleanText
{
    label.text = @"";
}

- (IBAction)segmentAction:(UISegmentedControl *)sender
{
    switch (sender.selectedSegmentIndex) {
        case 0:
        {
            [self sendRequestSync];
            break;
        }
        case 1:
        {
            [self sendRequestAsync];
            break;
        }
        case 2:
        {
            [self sendRequestAsyncOther];
            break;
        }
        case 3:
        {
            [self gcdRequest];
            break;
        }
            
        default:
            break;
    }
}

#pragma mark -
#pragma mark  GCDRequest

- (void) gcdRequest
{
    GCDHelper *hp = [GCDHelper new];
    
    [hp gcdHttpRequestWithURL:@"http://localhost:8888/test.php"
                   httpMethod:GCDHelperHttpRequestMethodGET
                       params:[NSDictionary dictionary]
                      timeout:5.0f
                      success:^(NSURLResponse *response, NSError *error, NSData *data) {
                          if (data && (!error))
                          {
                              label.text = [[data objectFromJSONData] description];
                          }
                          
                      }
                         fail:^(NSURLResponse *response, NSError *error, NSData *data) {
                             if (error)
                             {
                                 label.text = [error description];
                             }
                         }];
}

#pragma mark -
#pragma mark  sendRequestSync

- (void) sendRequestSync
{
    [self cleanText];
    
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    
    [request setURL:[NSURL URLWithString:@"http://localhost:8888/test.php"]];
    [request setHTTPMethod:@"GET"];
    
    NSError *error = nil;
    NSData *data = [NSURLConnection sendSynchronousRequest:request
                                         returningResponse:nil
                                                     error:&error];
    
    if (data && (!error))
    {
        label.text = [[data objectFromJSONData] description];
    }
}

#pragma mark -
#pragma mark  sendRequestAsync

- (void) sendRequestAsync
{
    finished = NO;
    
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    [request setURL:[NSURL URLWithString:@"http://localhost:8888/test1.php"]];
    [request setHTTPMethod:@"GET"];
    [request setCachePolicy:NSURLRequestUseProtocolCachePolicy];
    [request setTimeoutInterval:5.0f];
    
    NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
                                                                  delegate:self
                                                          startImmediately:YES];
    
    [connection start];
    
//    但是异步模式下带来了一个新的问题,很多情况下,网络请求不在主线程,或者界面等待网络结果,不在主线程的时候,调用线程如果生命周期over,下面这些可能都没有调用到,导致得不到想要得效果,所以需要在NSURLConnection请求后面加点东西来阻塞
    while(!finished) {
        
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        
    }
}

// 收到回应
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    // 注意这里将NSURLResponse对象转换成NSHTTPURLResponse对象才能去
    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
    
    if ([response respondsToSelector:@selector(allHeaderFields)])
    {
        NSDictionary *dictionary = [httpResponse allHeaderFields];
        NSLog(@"allHeaderFields: %@",dictionary);
    }
    [receivedData setLength:0];
}

// 接收数据
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    NSLog(@"get some data");
    [receivedData appendData:data];
}

// 数据接收完毕
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSString *results = [[NSString alloc] initWithBytes:[receivedData bytes]
                                                 length:[receivedData length]
                                               encoding:NSUTF8StringEncoding];
    
    label.text = [[results objectFromJSONString] description];
    
    finished = YES;
}

// 返回错误
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    NSLog(@"Connection failed: %@", error);
}

#pragma mark -
#pragma mark  sendRequestAsyncOther

- (IBAction) sendRequestAsyncOther
{
    [self cleanText];
    
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    [request setURL:[NSURL URLWithString:@"http://localhost:8888/test2.php"]];
    [request setHTTPMethod:@"GET"];
    [request setCachePolicy:NSURLRequestUseProtocolCachePolicy];
    [request setTimeoutInterval:5.0f];
    
    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue new]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
                               
                               dispatch_async(dispatch_get_main_queue(), ^{
                                   label.text = [[data objectFromJSONData] description];
                               });
                               
                           }];
}

@end

----------------------------------------------

#import <Foundation/Foundation.h>

/** Simple GCD-based timer based on NSTimer.

 Starts immediately and stops when deallocated. This avoids many of the typical problems with NSTimer:

 * RNTimer runs in all modes (unlike NSTimer)
 * RNTimer runs when there is no runloop (unlike NSTimer)
 * Repeating RNTimers can easily avoid retain loops (unlike NSTimer)
*/

@interface RNTimer : NSObject

/**---------------------------------------------------------------------------------------
 @name Creating a Timer
 -----------------------------------------------------------------------------------------
*/

/** Creates and returns a new repeating RNTimer object and starts running it

 After `seconds` seconds have elapsed, the timer fires, executing the block.
 You will generally need to use a weakSelf pointer to avoid a retain loop.
 The timer is attached to the main GCD queue.

 @param seconds The number of seconds between firings of the timer. Must be greater than 0.
 @param block Block to execute. Must be non-nil

 @return A new RNTimer object, configured according to the specified parameters.
*/
+ (RNTimer *)repeatingTimerWithTimeInterval:(NSTimeInterval)seconds block:(dispatch_block_t)block;


/**---------------------------------------------------------------------------------------
 @name Firing a Timer
 -----------------------------------------------------------------------------------------
*/

/** Causes the block to be executed.

 This does not modify the timer. It will still fire on schedule.
*/
- (void)fire;


/**---------------------------------------------------------------------------------------
 @name Stopping a Timer
 -----------------------------------------------------------------------------------------
*/

/** Stops the receiver from ever firing again

 Once invalidated, a timer cannot be reused.

*/
- (void)invalidate;
@end

#import "RNTimer.h"

@interface RNTimer ()
@property (nonatomic, readwrite, copy) dispatch_block_t block;
@property (nonatomic, readwrite, assign) dispatch_source_t source;
@end

@implementation RNTimer
@synthesize block = _block;
@synthesize source = _source;

+ (RNTimer *)repeatingTimerWithTimeInterval:(NSTimeInterval)seconds
                                      block:(void (^)(void))block {
  NSParameterAssert(seconds);
  NSParameterAssert(block);

  RNTimer *timer = [[self alloc] init];
  timer.block = block;
  timer.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
                                        0, 0,
                                        dispatch_get_main_queue());
    
  uint64_t nsec = (uint64_t)(seconds * NSEC_PER_SEC);
  dispatch_source_set_timer(timer.source,
                            dispatch_time(DISPATCH_TIME_NOW, nsec),
                            nsec, 0);
  dispatch_source_set_event_handler(timer.source, block);
  dispatch_resume(timer.source);
  return timer;
}

- (void)invalidate {
  if (self.source) {
    dispatch_source_cancel(self.source);
    dispatch_release(self.source);
    self.source = nil;
  }
  self.block = nil;
}

- (void)dealloc {
  [self invalidate];
}

- (void)fire {
  self.block();
}


@end




完整的项目链接:http://pan.baidu.com/share/link?shareid=386371&uk=3674861929

转载请保留,原文链接:http://write.blog.csdn.net/postedit/8708667

若发现有不合适或错误之处,还请批评指正,不胜感激。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zfpp25_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值