Castie!

正态分布, 优劣伴生

北冥有鱼,其名为鲲(kūn)。鲲之大,不知其几千里也;化而为鸟,其名为鹏。鹏之背,不知其几千里也;怒而飞,其翼若垂天之云。是鸟也,海运则将徙于南冥。南冥者,天池也。


北海若曰:“井鼃不可以语于海者,拘于虚也;夏虫不可以语于冰者,笃于时也;曲士不可以语于道者,束于教也。今尔出于崖涘,观于大海,乃知尔丑,尔将可与语大理矣。

iOS 网络性能优化浅析

GitHub Repo:coderZsq.project.ios
Follow: coderZsq · GitHub
Resume: https://coderzsq.github.io/coderZsq.practice.web/

日常扯淡

emm~~ 最近有好多大厂疯狂招人, 也有很多朋友抛出了橄榄枝要来内推我, 在此我表示万分感谢, 但以小弟的简历… 真的连简历都过不了… 最近在学习Python, 真的学了Python才感受到了编程的乐趣啊, 爬虫, 自动化, 数据分析, 机器学习, 区块链 真的是感觉掌控了先进科技了… 我把所有学习Python的代码放在了这里–> 仓库, 会逐步更新的, 希望能够共同学习和进步!

诶… 一直在自学Python, iOS都不太会敲了, 想想之前看了函数式编程的书, 又想学着写写如何设计一个网络框架, 就有了今天这篇文章, 虽然也没啥技术含量….

对于iOS, Apple市值破万亿, 说明当初选择这个方向还是不错的, 但是现在的趋势真的是好多在职的iOS都在往大数据人工智能方向上转, 真的, 我感觉iOS各个都是全栈, 什么都会真的牛逼, 我也要跟随大佬的步伐, 继续我的学习之旅了~~

网络优化

说实话, 网络优化基本是根据你的项目来的, 不同的需求会有不同的方案, 但思想上是想通的, 大体就是, 缓存, 缓存, 缓存… 总结为如下几点:

  • 减少, 压缩网络数据
  • 如果多次请求结果是相同的, 经量使用缓存
  • 使用断点续传, 否则网络不稳定的时候可能多次传输相同的内容
  • 网络不可用的时候, 不要尝试执行网络请求
  • 让用户可以取消长时间运行或者速度很慢的网络操作, 设置合适的超时时间
  • 批量传输,比如,下载视频流时,不要传输很小的数据包,直接下载整个文件或者一大块一大块地下载。如果下载广告,一次性多下载一些,然后再慢慢展示。如果下载电子邮件,一次下载多封,不要一封一封地下载.

HTTP

说起网络, 肯定要说到HTTP了, 这个真的绕不过去, 对于HTTP可以看看我学习实践的代码–> 仓库

当然这个是用JS写的, 而且这个需要服务端的支持, 简单来说就是设置一些头信息, cache-control 这个字段就非常虫咬了, 是做一些缓存处理的, 设置max-age就可以设定超时时间. accept设置为gzip之类的就可以进行网络数据的压缩..

这种都是常规操作, 就不过多赘述了…

准备工作

这个在上一篇iOS 界面性能优化浅析的基础上进行, 在我们server.js中添加代码.

    if (decodeURI(req.url).indexOf("/get") != -1) {
        console.log(req.url);
        res.writeHead(200, {
            'Content-Type': 'application/json'
        });
        res.end(JSON.stringify({
            data: "GET Method",
            status: 'Success'
        }));
    }

    if (decodeURI(req.url).indexOf("/post") != -1) {
        console.log(req.url);
        let body = "";
        req.on('data', function (chunk) {
            body += chunk;  
        });
        req.on('end', function () {
            body = querystring.parse(body);
            console.log(body);
        });
        res.writeHead(200, {
            'Content-Type': 'application/json'
        });
        res.end(JSON.stringify({
            data: "POST Method",
            status: 'Success'
        }));
    }

    if (decodeURI(req.url).indexOf("/image") != -1) {
        let path = __dirname + '/contents/Castie!.jpg';
        fs.readFile(path, 'binary', (err, file) => {
            if (err) {
                console.log(err);
                return;
            } else {
                res.writeHead(200, {
                    'Content-Type': 'image/jpeg'
                });
                res.write(file, 'binary');
                res.end();
                return;
            }
        })
    }

我们就简单的开了三个接口来模拟get请求, post请求图片请求 这些基本上覆盖了我们80%的需求了吧.

如何设计

一个网络框架如何设计? 说实话, 我也不知道, 看了AFNetworkingSDWebImage的源码, 就会发现侧重点都是不同的. 做为开发者, 当然不能生搬硬套别人的思想, 所以我就按自己的理解来进行设计了.

我在写代码的时候, 有自顶向下设计的习惯, 首先把接口写出来, 关键是要让别人用的爽, 然后再进行具体的实现.

所以我们先看下之前AFN我们是怎么用的.

#import "Service.h"
#import <AFNetworking.h>

@implementation Service

- (void)fetchMockDataWithParam:(NSDictionary *)parameter completion:(RequestCompletionBlock)completion {
    
    AFHTTPSessionManager * manager = [AFHTTPSessionManager manager];
    [manager GET:@"http://localhost:8080/fetchMockData" parameters:parameter progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        if ([responseObject[@"status"] isEqualToString: @"success"]) {
            completion(responseObject[@"data"], nil);
        }
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        completion(nil, error);
    }];
}

@end

这个是我之前使用AFN的时候的常规操作, 再看我想要达到的效果:

#import "Service.h"
#import "SQFetcher.h"
#import <UIKit/UIKit.h>

@implementation Service

- (void)fetchMockDataWithParam:(NSDictionary *)parameter completion:(RequestCompletionBlock)completion {
    
    [SQFetchManager managerWithState:SQFetchSerialState]
    
    .GET(@"http://localhost:8090/fetchMockData", nil, ^(NSDictionary *responseObject){
        if ([responseObject[@"status"] isEqualToString: @"success"]) {
            completion(responseObject[@"data"], nil);
        }
        NSLog(@"EXCUTE: http://localhost:8090/fetchMockData");
    }, nil)
    
    .GET(@"http://localhost:8090/get", @{@"username": @"Castie!",
                                         @"passwd" : @"01234",
                                         @"param0" : @"0.0",
                                         @"param1" : @"1.1",
                                         @"param2" : @"2.2",
                                         @"param3" : @"3.3",
                                         @"param4" : @"4.4",
                                         }, ^(NSDictionary *responseObject){
             NSLog(@"EXCUTE: http://localhost:8090/get %@", responseObject);
    }, nil)
    
    .POST(@"http://localhost:8090/post", @{@"username": @"Castie!",
                                           @"passwd" : @"56789",
                                           @"param5" : @"5.5",
                                           @"param6" : @"6.6",
                                           @"param7" : @"7.7",
                                           @"param8" : @"8.8",
                                           @"param9" : @"9.9",
                                           }, ^(NSDictionary *responseObject){
              NSLog(@"EXCUTE: http://localhost:8090/post %@", responseObject);
    },nil)
    
    .GET(@"http://localhost:8090/image", nil, ^(UIImage *responseObject){
        NSLog(@"EXCUTE: http://localhost:8090/image %@", responseObject);
    },nil);
}

@end

这里的#import <UIKit/UIKit.h>是不必要的, 只是想表达请求图片也可以的意思.

可以看到, 这种调用, 我们从视觉的角度就非常清晰舒爽了~

刚才的代码, 细心的同学可以看到有一个状态的判断, 这个值可以操作下面链式调用是串行还是并行, 是不是很神奇~ 妈妈再也不用担心回调地狱的问题了~

如何实现

那如何实现这个链式调用及控制呢, 来看看我是怎么实现的.

#import <Foundation/Foundation.h>

typedef NS_ENUM(NSInteger, SQFetchState) {
    SQFetchSerialState,
    SQFetchConcurrentState
};

@interface SQFetchManager : NSObject

@property (nonatomic,strong) SQFetchManager * (^GET)(NSString * url, NSDictionary * parameters, void(^success)(id), void(^failure)(NSError *));

@property (nonatomic,strong) SQFetchManager * (^POST)(NSString * url, NSDictionary * parameters, void(^success)(id), void(^failure)(NSError *));

+ (SQFetchManager *)managerWithState:(SQFetchState)state;

@end

看看头文件, 就能够发现, 我这个属性是不是和你们用的都不太一样, 这是一个Block, 不要和我说Block要用copy修饰, 想想现在ARC已经多少时间了…. 当然copy是通用的…

#import "SQFetchManager.h"
#import "SQFetchSerialization.h"
#import "SQFetchReachability.h"
#import "SQFetchCache.h"

typedef NS_ENUM(NSInteger, SQHTTPMethod) {
    SQGETMethod,
    SQPOSTMethod
};

@interface SQFetchManager()

@property (nonatomic,strong) NSOperation * preOperation;
@property (nonatomic,strong) NSOperationQueue * operationQueue;
@property (nonatomic,assign) SQFetchState state;

@end

@implementation SQFetchManager

- (NSOperationQueue *)operationQueue {
    
    if (!_operationQueue) {
        _operationQueue = [NSOperationQueue new];
        _operationQueue.maxConcurrentOperationCount = 6;
    }
    return _operationQueue;
}

- (SQFetchManager *(^)(NSString *, NSDictionary *, void(^)(id), void(^)(NSError *)))GET {
    return ^(NSString * URLString, NSDictionary * parameters, void(^success)(NSDictionary *), void(^failure)(NSError *)){
        NSBlockOperation * operation = [NSBlockOperation blockOperationWithBlock:^{
            dispatch_semaphore_t semaphore = NULL;
            if (self.state == SQFetchSerialState)
                semaphore = dispatch_semaphore_create(0);
            [[self __dataTaskWithHTTPMethod:SQGETMethod
                                  URLString:URLString
                                 parameters:parameters
                                    success:success
                                    failure:failure
                                  semaphore:semaphore] resume];
            if (self.state == SQFetchSerialState)
                dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        }];
        if (self.preOperation) {
            [operation addDependency:self.preOperation];
        }
        [self.operationQueue addOperation:operation];
        self.preOperation = operation;
        
        return self;
    };
}

- (SQFetchManager *(^)(NSString *, NSDictionary *, void (^)(id), void (^)(NSError *)))POST {
    return ^(NSString * URLString, NSDictionary * parameters, void(^success)(NSDictionary *), void(^failure)(NSError *)){
        NSBlockOperation * operation = [NSBlockOperation blockOperationWithBlock:^{
            dispatch_semaphore_t semaphore = NULL;
            if (self.state == SQFetchSerialState)
                semaphore = dispatch_semaphore_create(0);
            [[self __dataTaskWithHTTPMethod:SQPOSTMethod
                                URLString:URLString
                               parameters:parameters
                                  success:success
                                  failure:failure
                                semaphore:semaphore] resume];
            if (self.state == SQFetchSerialState)
                dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        }];
        if (self.preOperation)
            [operation addDependency:self.preOperation];
        [self.operationQueue addOperation:operation];
        self.preOperation = operation;
        
        return self;
    };
}

- (NSURLSessionDataTask *)__dataTaskWithHTTPMethod:(SQHTTPMethod)method
                                       URLString:(NSString *)URLString
                                      parameters:(NSDictionary *)parameters
                                         success:(void (^)(id))success
                                         failure:(void (^)(NSError *))failure
                                       semaphore:(dispatch_semaphore_t)semaphore {
    SQFetchReachability * reachability = [SQFetchReachability sharedInstance];
    if (reachability.status == NotReachable) {
        return nil;
    }
    id cache = [SQFetchCache getCacheFromKey:URLString];
    if (cache) {
        success(cache);
        return nil;
    }

    if (method == SQGETMethod) {
        URLString = [URLString stringByAppendingString:
                     [SQFetchSerialization getMethodSerializationWithParameters:parameters]];
    }

    NSURL * url = [NSURL URLWithString:URLString];
    NSURLSession * session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
    
    if (method == SQPOSTMethod) {
        request.HTTPMethod = @"POST";
        request.HTTPBody = [SQFetchSerialization postMethodSerializationWithParameters:parameters];
    }
    
    NSURLSessionDataTask * task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSError * err;
        id responseObject = [SQFetchSerialization serializationWithContentType:response.MIMEType
                                                                          data:data error:err];
        [SQFetchCache setCache:responseObject key:URLString];
        if(err) {
            failure(err);
            if (self.state == SQFetchSerialState)
                dispatch_semaphore_signal(semaphore);
        } else {
            success(responseObject);
            if (self.state == SQFetchSerialState)
                dispatch_semaphore_signal(semaphore);
        }
        
    }];
    return task;
}

+ (SQFetchManager *)managerWithState:(SQFetchState)state {
    SQFetchManager * manager = [SQFetchManager new];
    manager.state = state;
    return manager;
}

@end

这个是整个实现文件, 可以看到, 这是如何使用函数式编程了, 用起来真是神清气爽, 但不知道如果别人用了会是什么感受…

如何实现同步依赖, 主要是使用了信号量, 配合队列依赖就可以实现啦~

序列化

序列化就是对不同的请求响应做不同的解析.

#import <Foundation/Foundation.h>

@interface SQFetchSerialization : NSObject

+ (NSString *)getMethodSerializationWithParameters:(NSDictionary *)parameters;

+ (NSData *)postMethodSerializationWithParameters:(NSDictionary *)parameters;

+ (id)serializationWithContentType:(NSString *)contentType data:(NSData *)data error:(NSError *)error;

@end

看头文件也是简洁明了吧…

#import "SQFetchSerialization.h"
#import <UIKit/UIKit.h>

@implementation SQFetchSerialization

+ (NSString *)getMethodSerializationWithParameters:(NSDictionary *)parameters {
    
    NSMutableString * parameterString = [NSMutableString string];
    [parameters enumerateKeysAndObjectsUsingBlock:^(NSString * key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        NSString * value = (NSString *)obj;
        [parameterString appendFormat:@"%@=%@&", key, value];
    }];
    if (parameterString.length) {
        [parameterString insertString:@"?" atIndex:0];
        [parameterString deleteCharactersInRange:NSMakeRange(parameterString.length - 1, 1)];
    }
    return parameterString;
}

+ (NSData *)postMethodSerializationWithParameters:(NSDictionary *)parameters {
    
    NSMutableString * parameterString = [NSMutableString string];
    [parameters enumerateKeysAndObjectsUsingBlock:^(NSString * key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        NSString * value = (NSString *)obj;
        [parameterString appendFormat:@"%@=%@&", key, value];
    }];
    if (parameterString.length) {
        [parameterString deleteCharactersInRange:NSMakeRange(parameterString.length - 1, 1)];
    }
    return [parameterString dataUsingEncoding:NSUTF8StringEncoding];
}

+ (id)serializationWithContentType:(NSString *)contentType data:(NSData *)data error:(NSError *)error {
    
    id responseObject;
    if ([contentType isEqualToString:@"application/json"]) {
        responseObject = [NSJSONSerialization JSONObjectWithData:data
                                                         options:NSJSONReadingMutableContainers error:&error];
    } else if ([contentType isEqualToString:@"image/jpeg"]) {
        CGImageRef cgImage = [UIImage imageWithData:data].CGImage;
        CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage) & kCGBitmapAlphaInfoMask;
        
        BOOL hasAlpha = NO;
        if (alphaInfo == kCGImageAlphaPremultipliedLast ||
            alphaInfo == kCGImageAlphaPremultipliedFirst ||
            alphaInfo == kCGImageAlphaLast ||
            alphaInfo == kCGImageAlphaFirst) {
            hasAlpha = YES;
        }
        
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
        bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
        
        size_t width = CGImageGetWidth(cgImage);
        size_t height = CGImageGetHeight(cgImage);
        
        CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, colorSpace, bitmapInfo);
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
        cgImage = CGBitmapContextCreateImage(context);
        
        UIImage * image = [UIImage imageWithCGImage:cgImage];
        CGColorSpaceRelease(colorSpace);
        CGContextRelease(context);
        CGImageRelease(cgImage);
        responseObject = image;
    }
    return responseObject;
}

@end

相关实现也是对请求参数进行拼接, 以及对响应的数据进行分类处理

网络监测

这个依赖了AppleReachability就是作用网络不可用的时候, 不要尝试执行网络请求.

#import "SQFetchReachability.h"

@interface SQFetchReachability()
@property (nonatomic, strong) Reachability * hostReachability;
@property (nonatomic, strong) Reachability * routerReachability;
@end

@implementation SQFetchReachability

+ (void)load {
    [SQFetchReachability sharedInstance];
}

+ (instancetype)sharedInstance {
    static SQFetchReachability * instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[super allocWithZone:NULL] init];
    });
    return instance;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [self sharedInstance];
}

- (instancetype)init {
    self = [super init];
    if (self) {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(appReachabilityChanged:)
                                                     name:kReachabilityChangedNotification
                                                   object:nil];
        NSString * remoteHostName = @"www.baidu.com";
        self.hostReachability = [Reachability reachabilityWithHostName:remoteHostName];
        [self.hostReachability startNotifier];
        self.routerReachability = [Reachability reachabilityForInternetConnection];
        [self.routerReachability startNotifier];
    }
    return self;
}

- (void)appReachabilityChanged:(NSNotification *)notification{
    Reachability * reach = [notification object];
    if([reach isKindOfClass:[Reachability class]]){
        NetworkStatus status = [reach currentReachabilityStatus];
        self.status = status;
        if (reach == self.routerReachability) {
            if (status == NotReachable) {
                NSLog(@"routerReachability NotReachable");
            } else if (status == ReachableViaWiFi) {
                NSLog(@"routerReachability ReachableViaWiFi");
            } else if (status == ReachableViaWWAN) {
                NSLog(@"routerReachability ReachableViaWWAN");
            }
        }
        if (reach == self.hostReachability) {
            NSLog(@"hostReachability");
            if ([reach currentReachabilityStatus] == NotReachable) {
                NSLog(@"hostReachability failed");
            } else if (status == ReachableViaWiFi) {
                NSLog(@"hostReachability ReachableViaWiFi");
            } else if (status == ReachableViaWWAN) {
                NSLog(@"hostReachability ReachableViaWWAN");
            }
        }
        
    }
}

@end

二级缓存

对于这块, 看过SD的同学肯定非常了解, 内存缓存磁盘缓存.

#import "SQFetchCache.h"
#import <CommonCrypto/CommonDigest.h>
#import <UIKit/UIKit.h>

@interface SQFetchCache()
@property (nonatomic, strong) NSMutableDictionary * memoryCache;
@end

@implementation SQFetchCache

- (NSMutableDictionary *)memoryCache {
    
    if (!_memoryCache) {
        _memoryCache = @{}.mutableCopy;
    }
    return _memoryCache;
}

+ (instancetype)sharedInstance {
    static SQFetchCache * instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[super allocWithZone:NULL] init];
    });
    return instance;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [self sharedInstance];
}

+ (void)setCache:(id)data key:(NSString *)key {
    if ([data isKindOfClass:[UIImage class]]) {
        [SQFetchCache sharedInstance].memoryCache[[self __sha1:key]] = data;
        [SQFetchCache __detectionDiskCache];
        NSString * homePath = NSHomeDirectory();
        NSString * path = [homePath stringByAppendingPathComponent:@"Library/Caches"];
        NSString * filePath = [path stringByAppendingPathComponent:[self __sha1:key]];
        [UIImagePNGRepresentation(data) writeToFile:filePath atomically:YES];
    }
}

+ (id)getCacheFromKey:(NSString *)key {
    id cache = [SQFetchCache sharedInstance].memoryCache[[self __sha1:key]];
    if (!cache) {
        NSString * cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        NSString * file = [NSString stringWithFormat:@"%@/%@", cachePath, [self __sha1:key]];
        cache = [[UIImage alloc]initWithContentsOfFile:file];
    }
    return cache;
}

+ (void)__detectionDiskCache {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSFileManager * fileManager = [NSFileManager defaultManager];
        NSString * cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        NSDirectoryEnumerator * direnum = [fileManager enumeratorAtPath:cachePath];
        NSError * error = nil;
        NSString * filename;
        while (filename = [direnum nextObject]) {
            NSDictionary * fileAttributes = [fileManager attributesOfItemAtPath:[NSString stringWithFormat:@"%@/%@",cachePath, filename] error:&error];
            if (fileAttributes != nil) {
                NSNumber * fileSize = [fileAttributes objectForKey:NSFileSize];
                NSString * fileOwner = [fileAttributes objectForKey:NSFileOwnerAccountName];
                NSDate * fileModDate = [fileAttributes objectForKey:NSFileModificationDate];
                NSDate * fileCreateDate = [fileAttributes objectForKey:NSFileCreationDate];
                if (fileSize) {
                    NSLog(@"File size: %qi\n", [fileSize unsignedLongLongValue]);
                }
                if (fileOwner) {
                    NSLog(@"Owner: %@\n", fileOwner);
                }
                if (fileModDate) {
                    NSLog(@"Modification date: %@\n", fileModDate);
                }
                if (fileCreateDate) {
                    NSLog(@"create date:%@\n", fileModDate);
                }
            }
            else {
                NSLog(@"Path (%@) is invalid.", cachePath);
            }
        }
    });
}

+ (void)clearDiskCache {
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString * cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    NSLog(@"%@",cachePath);
    NSDirectoryEnumerator * fileEnumerator = [fileManager enumeratorAtPath:cachePath];
    for (NSString * fileName in fileEnumerator) {
        NSString * filePath = [cachePath stringByAppendingPathComponent:fileName];
        [fileManager removeItemAtPath:filePath error:nil];
    }
}

+ (NSString *)__sha1:(NSString *)inputString {
    NSData * data = [inputString dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
    CC_SHA1(data.bytes,(unsigned int)data.length,digest);
    NSMutableString *outputString = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH];
    for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
        [outputString appendFormat:@"%02x",digest[i]];
    }
    return [outputString lowercaseString];
}

@end

如何优化就是对空间和时间两方面, 这里可以关注__detectionDiskCache这个方法中进行判断处理.. 可以策略性的进行删除..(内存缓存也是相同的思路)

#import "UIImageView+SQFetcher.h"
#import "SQFetcher.h"

@implementation UIImageView (SQFetcher)

- (void)sq_imageWithURLString:(NSString *)URLString {
    [SQFetchManager managerWithState:SQFetchConcurrentState]
    .GET(URLString, nil, ^(UIImage *responseObject){
        dispatch_async(dispatch_get_main_queue(), ^{
            self.image = responseObject;
        });
    },nil);
}

@end

分类使用的实现也是非常简单的, 我么使用起来也是非常方便.

最后

这个仅仅是一个最小可用的设计, 当然还有很多值得优化的点, 比如配置请求头, 请求复用, 哈希桶扩张, 管道, 对于需求, 还要安全验证等, 写这个东西其实就是了解下一个网络框架需要了解点什么, 平时使用的时候AFNSD肯定是你首选的方案!! 但有了这些思路, 你也可以实现一个自己定制的网络框架!

最后 本文中所有的源码都可以在github上找到 -> SQPerformance

GitHub Repo:coderZsq.project.ios
Follow: coderZsq · GitHub
Resume: https://coderzsq.github.io/coderZsq.practice.web/

最近的文章

iOS 开发者该认真思考的「三个问题」

GitHub Repo:coderZsq.project.iosFollow: coderZsq · GitHubResume: https://coderzsq.github.io/coderZsq.practice.web/日常扯淡大半年没有更新文章了, 可能是对自己写的内容有要求吧, 不想写一些如OC底层, 逆向入门这些像内容洗稿,东拼西凑的伪原创, 修修改改换换顺序就又是一篇完全没有意义的文章. 如果你获取技术大部分的手段是看技术博客的话, 是的, 是时候调整你的视角, 扩展你...…

移动开发继续阅读
更早的文章

iOS 界面性能优化浅析

GitHub Repo:coderZsq.project.iosFollow: coderZsq · GitHubResume: https://coderzsq.github.io/coderZsq.practice.web/日常扯淡emm~~~ 前段时间又出去看了看iOS市场的行情, 除非是BAT, TMD, 这类一线的知名企业, 其他的只要努努力还是可以通过的, 这里分享一些小技巧, 比如当HR,或者别人内推你的时候, 不管要求写的多么的天花乱坠, 千万不要被这些东西给吓到了, ...…

移动开发继续阅读