iOS開發(fā)性能優(yōu)化(一)

tableview優(yōu)化

  • Cell重用
  • 提前計(jì)算并緩存Cell的高度
  • 異步繪制cell
  • 滑動(dòng)時(shí),按需加載
  • 渲染

網(wǎng)絡(luò)優(yōu)化

  • 第三方AFN時(shí)做網(wǎng)絡(luò)隔離,減少對(duì)第三方的依賴性
  • 功能設(shè)計(jì)時(shí),減少同一時(shí)間多次網(wǎng)絡(luò)請(qǐng)求的可能性
  • 請(qǐng)求數(shù)據(jù)用json
  • 圖片加載優(yōu)化

tableview優(yōu)化

cell重用

數(shù)據(jù)源方法優(yōu)化,在可見的頁面會(huì)重復(fù)繪制頁面,每次刷新顯示都回去創(chuàng)建新的cell,非常耗費(fèi)性能。
解決方案,首先創(chuàng)建一個(gè)靜態(tài)變量reuseID(代理方法返回cell回調(diào)用很多次,防止重復(fù)創(chuàng)建,static保證會(huì)被創(chuàng)建一次,提高性能),然后,從緩存池中取相應(yīng)identifier的cell并更新數(shù)據(jù),如果沒有,才開始創(chuàng)建新的cell,并用identified鏢師cell。每一個(gè)cell都會(huì)注冊(cè)一個(gè)identified(重用標(biāo)識(shí)符)放入緩存池,當(dāng)需要調(diào)用的時(shí)候就直接從緩存池中找對(duì)應(yīng)的ID,當(dāng)不需要時(shí)就放入緩存池中等待調(diào)用。

緩存池的實(shí)現(xiàn)

當(dāng)cell要alloc時(shí),UITableView會(huì)在堆中開辟一段內(nèi)存以供cell緩存之用。cell的重用通過identifier鏢師不同類型的cell,由此可以推斷出,緩存池外層可能是一個(gè)可變字典,通過key來取出內(nèi)部的cell,而緩存池為存儲(chǔ)不同高度、不同類型的cell,可以推斷出緩存池的字典內(nèi)部可能時(shí)一個(gè)可變數(shù)組,用來存放不同類型的cell,緩存池中只會(huì)保存已經(jīng)被移除屏幕的不同類型的cell。緩存池獲取celltableview提供了兩個(gè)方法:

//如果注冊(cè)了cell,能夠查詢到返回cell,查詢不到就返回nil
-(nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier; 
//使用這個(gè)方法之前,必須通過xib(storyboard)或是Class(純代碼)注冊(cè)可重用Cell,而且這個(gè)方法一定會(huì)返回一個(gè)Cell
- (void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier;
- (void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier;

用法:

- (UITableView *)tableView {
    if(_tableView){
        _tableView = [[UItableView alloc]initWithFrame:self.view.frame];
        _tableView.delagate = self;
        _tableView.dataSource = self;
        [self.view addSubView:_tableView];
        [_tableView registerClass:[MXCell class] forCellreuseIdentifier:@"MXCellID"];
    }
    return _tableView;
}
- (UITableViewCell *)tableView:(UITableView *)tableView CellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MXCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MXCellID"];
    cell.textLabel.text = @"傻逼就是你";
    return cell;
}

如果緩沖區(qū) Cell 不存在,會(huì)使用原型 Cell 實(shí)例化一個(gè)新的 Cell,不需要再判斷,同時(shí)代碼結(jié)構(gòu)更清晰。

提前計(jì)算并緩存Cell的高度

rowHeight
tableview詢問cell高度的方式有兩種
1、

self.tableView.rowHeight = 88;

2、

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
     return 88;
}

強(qiáng)烈建議使用第一種方式,保證不必要的高度計(jì)算方法調(diào)用,rowHeight屬性的默認(rèn)值是44。第二種是代理回調(diào),需要注意的是,實(shí)現(xiàn)第二個(gè)方法后,rowHeight設(shè)置獎(jiǎng)無效。所以,這個(gè)方法用于具有多種cell高度的UITableView。
具有動(dòng)態(tài)高度內(nèi)容的cell一直是個(gè)頭疼的問題,比如聊天氣泡的cell,frame布局時(shí)代通常是用數(shù)據(jù)內(nèi)容反算高度:

CGFloat height = textHeightWithFont() + imageHeight + topMargin + bottomMargin + ...;
@interface BubbleCell : UITableViewCell
+ (CGFloat)heightWithEntity:(id)entity;
@end

各種魔法margin加上耦合了屏幕寬度。
AutoLayout時(shí)代好了不少,提供了-systemLayoutSizeFittingSize:的API,在contentView中設(shè)置約束后,就能計(jì)算出準(zhǔn)確的值;缺點(diǎn)是計(jì)算速度肯定沒有手算快,而且這是個(gè)實(shí)例方法,需要維護(hù)專門為計(jì)算高度而生的template layout cell,它還要求使用者對(duì)約束設(shè)置的比較熟練,要保證contentView內(nèi)部上下左右所有方向都有約束支撐,設(shè)置不合理的話計(jì)算的高度就成了0。

UITableView+FDTemplateLayoutCell
使用UITableView+FDTemplateLayoutCell無疑是解決算高問題的最佳實(shí)踐之一,既有iOS 8 self-sizing功能簡(jiǎn)單的API,又可以達(dá)到iOS7流暢的滑動(dòng)效果,還保持了最低支持iOS6。

使用起來大概是這樣:

#import <UITableView+FDTemplateLayoutCell.h>
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return [tableView fd_heightForCellWithIdentifier:@"identifer" cacheByIndexPath:indexPath configuration:^(id cell) {
 
        //cell.entity = self.feedEntities[indexPath.row];
    }];
}

寫完上面的代碼后,你就已經(jīng)使用到了:

和每個(gè)UITableViewCell ReuseID一一對(duì)應(yīng)的template layout cell
這個(gè)cell只為了參加高度計(jì)算,不會(huì)真的顯示到屏幕上;它通過UITableView的-dequeueCellForReuseIdentifier:方法lazy創(chuàng)建并保存,所以要求這個(gè)ReuseID必須已經(jīng)被注冊(cè)到了UITableView中,也就是說,要么是Storyboard中的原型cell,要么就是使用了UITableView的-registerClass:forCellReuseIdentifier:或-registerNib:forCellReuseIdentifier:其中之一的注冊(cè)方法。

根據(jù)autolayout約束自動(dòng)計(jì)算高度
使用了系統(tǒng)在iOS 6就提供的API:-systemLayoutSizeFittingSize:

根據(jù)index path的一套高度緩存機(jī)制
計(jì)算出的高度會(huì)自動(dòng)進(jìn)行緩存,所以滑動(dòng)時(shí)每個(gè)cell真正的高度計(jì)算只會(huì)發(fā)生一次,后面的高度詢問都會(huì)命中緩存,減少了非??捎^的多余計(jì)算。

自動(dòng)的緩存失效機(jī)制
無須擔(dān)心你數(shù)據(jù)源的變化引起的緩存失效,當(dāng)調(diào)用如-reloadData,-deleteRowsAtIndexPaths:withRowAnimation:等任何一個(gè)觸發(fā) UITableView 刷新機(jī)制的方法時(shí),已有的高度緩存將以最小的代價(jià)執(zhí)行失效。如刪除一個(gè)indexPath為[0:5]的cell時(shí),[0:0] ~ [0:4]的高度緩存不受影響,而[0:5]后面所有的緩存值都向前移動(dòng)一個(gè)位置。自動(dòng)緩存失效機(jī)制對(duì)UITableView的9個(gè)公有API都進(jìn)行了分別的處理,以保證沒有一次多余的高度計(jì)算。

預(yù)緩存機(jī)制。就不展開說了,都抄得不好意思了??。有興趣可以去看看這篇文章《優(yōu)化UITableViewCell高度計(jì)算的那些事》,寫得狠屌

異步繪制cell

這里我就不逼逼了,逼逼也是抄人家的,大神寫的《UITableView優(yōu)化技巧》

滑動(dòng)時(shí),按需加載

開發(fā)的過程中,自定義Cell的種類千奇百怪,但Cell本來就是用來顯示數(shù)據(jù)的,不說100%帶有圖片,也差不多,這個(gè)時(shí)候就要考慮,下滑的過程中可能會(huì)有點(diǎn)卡頓,尤其網(wǎng)絡(luò)不好的時(shí)候,異步加載圖片是個(gè)程序員都會(huì)想到,但是如果給每個(gè)循環(huán)對(duì)象都加上異步加載,開啟的線程太多,一樣會(huì)卡頓,我記得好像線程條數(shù)一般3-5條,最多也就6條吧。這個(gè)時(shí)候利用UIScrollViewDelegate兩個(gè)代理方法就能很好地解決這個(gè)問題。

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

思想就是識(shí)別UITableView禁止或者減速滑動(dòng)結(jié)束的時(shí)候,進(jìn)行異步加載圖片,快滑動(dòng)過程中,只加載目標(biāo)范圍內(nèi)的Cell,這樣按需加載,極大的提高流暢度。而SDWebImage可以實(shí)現(xiàn)異步加載,與這條性能配合就完美了,尤其是大量圖片展示的時(shí)候。而且也不用擔(dān)心圖片緩存會(huì)造成內(nèi)存警告的問題。

//獲取可見部分的Cell
NSArray *visiblePaths = [self.tableView indexPathsForVisibleRows];
        for (NSIndexPath *indexPath in visiblePaths)
        {
        //獲取的dataSource里面的對(duì)象,并且判斷加載完成的不需要再次異步加載
             <code>
        }

記得在記得在“tableView:cellForRowAtIndexPath:”方法中加入判斷:

// tableView 停止滑動(dòng)的時(shí)候異步加載圖片
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

         if (self.tableView.dragging == NO && self.tableView.decelerating == NO)
            {
               //開始異步加載圖片
            }
 
渲染
  • 減少cell內(nèi)部view的個(gè)數(shù)和層數(shù), 子控件的層級(jí)越深,渲染到屏幕上所需要的計(jì)算量就越大;
  • 盡量少用view的透明圖層,對(duì)于不透明的View,設(shè)置opaque為YES,這樣在繪制該View時(shí),就不需要考慮被View覆蓋的其他內(nèi)容;
  • 避免CALayer特效,給Cell中View加陰影會(huì)引起性能問題;

網(wǎng)絡(luò)優(yōu)化

第三方AFN時(shí)做網(wǎng)絡(luò)隔離

網(wǎng)絡(luò)隔離少不了對(duì)afn的二次封裝,前幾天突然去翻看了一下15年寫的項(xiàng)目,已經(jīng)滿目瘡痍,跑起來網(wǎng)絡(luò)都不通。繼我之后項(xiàng)目又經(jīng)歷3個(gè)人之手,每個(gè)人的編程習(xí)慣都不一樣,有一個(gè)跟我一樣把網(wǎng)絡(luò)請(qǐng)求的代碼寫在viewcontrller,有一個(gè)哥們用蘋果原生NSURLSession、NSURLRequest、NSURLResPonse、NSURLProcotol這幾個(gè)類自己封裝的,一個(gè)哥們對(duì)afn做了一些簡(jiǎn)單的封裝,但是給出的接口有點(diǎn)不好看??偨Y(jié)一下,就是項(xiàng)目代碼亂七八糟的。項(xiàng)目代碼是我一手帶大了,所以決定把它寫規(guī)范一點(diǎn)。閑話就不逼逼了。

afn網(wǎng)絡(luò)隔離封裝需要做到幾點(diǎn):

  1. 與項(xiàng)目代碼完全解耦,把請(qǐng)求代碼放在封裝體內(nèi)部。否則,如果以后換網(wǎng)絡(luò)請(qǐng)求框架,就需要到每一個(gè)控制器里面去改,要死人的。
  2. AFHTTPSessionManager寫成單例,AFNetworking作者也是這樣建議的。
  3. 封裝的接口統(tǒng)一,參數(shù)越少越好。

下面我就拿一下我滿目瘡痍的優(yōu)化后的網(wǎng)絡(luò)封裝來說明一下

.h文件

/**
 *可以設(shè)置一個(gè)基礎(chǔ)的url,比如:https://www.baidu.com/
 */
#define BASE_URL @""
/** 請(qǐng)求類型*/
typedef NS_ENUM(NSInteger, MXNet_YTPE)
{
    GET = 1,
    POST,
    DOWN_LOAD,//下載
    UPLOADING//上傳
};
/**
 *發(fā)起網(wǎng)絡(luò)請(qǐng)求
 *urlPath,url地址
 *type 請(qǐng)求類型
 *parameters 參數(shù)
 *returnBlock 成功/失敗 block塊
 *ProgressBlock 下載/上傳 進(jìn)度block塊
 */
void request(NSString *urlPath, MXNet_YTPE type,id parameters,void(^returnBlock)(id resuter,NSError *error),void(^ProgressBlock)(NSProgress *downloadProgress));
/** 取消網(wǎng)絡(luò)請(qǐng)求
 *urlPath nil取消所有的網(wǎng)絡(luò)請(qǐng)求,不為nil取消特定的url的網(wǎng)絡(luò)請(qǐng)求
 */
void cancel(NSString *urlPath);
/** 暫停請(qǐng)求
 *urlPath nil暫停所有的網(wǎng)絡(luò)請(qǐng)求,不為nil暫停特定的url的網(wǎng)絡(luò)請(qǐng)求
 */
void suspend(NSString *urlPath);
/** 繼續(xù)請(qǐng)求
 *urlPath nil繼續(xù)所有的網(wǎng)絡(luò)請(qǐng)求,不為nil繼續(xù)特定的url的網(wǎng)絡(luò)請(qǐng)求
 */
void resume(NSString *urlPath);

在.h里面就給粗來幾個(gè)簡(jiǎn)單的接口實(shí)現(xiàn)幾個(gè)功能,發(fā)起請(qǐng)求、暫停請(qǐng)求、繼續(xù)請(qǐng)求、取消請(qǐng)求。在發(fā)起請(qǐng)求函數(shù)里面需要完成的功能有請(qǐng)求成功的返回,請(qǐng)求的進(jìn)度,比如實(shí)時(shí)監(jiān)測(cè)下載或者上傳進(jìn)度,顯示進(jìn)度條。

.m文件

/** 網(wǎng)絡(luò)請(qǐng)求二次封裝*/
@interface MXNetTool : NSObject
@property (nonatomic, strong) AFHTTPSessionManager *netManager;
/** 記錄每次請(qǐng)求的標(biāo)記*/
@property (nonatomic, strong) NSMutableDictionary *taskDictionary;
/** 單列對(duì)象*/
+ (instancetype)netTool;
/**
 *對(duì)外的網(wǎng)請(qǐng)求接口
 *如果type為DOWN_LOAD或者UPLOADING,urlPath需要穿絕對(duì)路徑
 */
- (void)requestUrl:(NSString *)urlPath Moth:(MXNet_YTPE)type parameters:(id)parameters returnBlock:(void(^)(id resuter,NSError *error))returnBlock progressBlock:(void(^)(NSProgress *downloadProgress))progressBlock;
/**
 *urlKey 傳nil,取消所有網(wǎng)絡(luò)請(qǐng)求
 *urlKey 取消特定url請(qǐng)求
 */
- (void)cancel:(NSString *)urlKey;
/** 繼續(xù)*/
- (void)resume:(NSString *)urlKey;
/** 暫停*/
- (void)suspend:(NSString *)urlkey;
@end

@implementation MXNetTool

+ (instancetype)netTool {
    static MXNetTool *networking = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        networking = [[self alloc] init];
        networking.netManager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:BASE_URL]];
        [networking configNetManager];
    });
    return networking;
}
- (NSMutableDictionary *)taskDictionary {
    if (!_taskDictionary) {
        _taskDictionary = [NSMutableDictionary dictionary];
    }
    return _taskDictionary;
}
/** 配置網(wǎng)絡(luò)參數(shù)*/
- (void)configNetManager {
    self.netManager.responseSerializer = [AFHTTPResponseSerializer serializer];
    self.netManager.responseSerializer.acceptableContentTypes = [NSSet setWithArray:@[@"application/json",\
                                                                                      @"text/html",\
                                                                                      @"text/json",\
                                                                                      @"text/javascript",\
                                                                                      @"image/tiff",\
                                                                                      @"image/jpeg",\
                                                                                      @"image/png",\
                                                                                      @"image/x-icon",\
                                                                                      @"image/gif"]];
    //這里還可以配置請(qǐng)求頭以及其他...
    
    /** 超時(shí)時(shí)間*/
    self.netManager.requestSerializer.timeoutInterval = 15.0;
}
/** MXNetTool入口*/
- (void)requestUrl:(NSString *)urlPath Moth:(MXNet_YTPE)type parameters:(id)parameters returnBlock:(void(^)(id resuter,NSError *error))returnBlock progressBlock:(void(^)(NSProgress *downloadProgress))progressBlock {
    __weak __block typeof(self) weakSelf = self;
    /** 當(dāng)請(qǐng)求結(jié)束以后回調(diào)移除標(biāo)記,并返回?cái)?shù)據(jù)*/
    void (^block)(id data, NSError *error) = ^(id data,NSError *error){
        [weakSelf.taskDictionary removeObjectForKey:urlPath];
        returnBlock(data,error);
    };
    NSURLSessionTask *task = nil;
    switch (type) {
        case GET:
            task = [self GET_requestUrlPath:urlPath Parameters:parameters returnBlock:block];
            break;
        case POST:
            task = [self POST_requestUrlPath:urlPath Parameters:parameters returnBlock:block];
            break;
        case DOWN_LOAD:
            task = [self downLoadWithUrl:urlPath parameters:parameters returnBlock:block progressBlock:progressBlock];
            break;
        case UPLOADING:
            task = [self uploadingUrl:urlPath parameters:parameters returnBlock:block progressBlock:progressBlock];
            break;
        default:
            task = nil;
            returnBlock(nil,nil);
            break;
    }
    /** 記錄下每一次網(wǎng)絡(luò),并用url做標(biāo)記*/
    if (task && urlPath) {
        self.taskDictionary[urlPath] = task;
    }
}
/** GET*/
- (NSURLSessionTask *)GET_requestUrlPath:(NSString *)urlPath Parameters:(NSDictionary *)parameters returnBlock:(void(^)(id resuter,NSError *error))returnBlock {

    return [self.netManager GET:urlPath parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        if (returnBlock) {
            returnBlock(responseObject,nil);
        }
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        returnBlock(nil,error);
    }];
}
/** POST*/
- (NSURLSessionTask *)POST_requestUrlPath:(NSString *)urlPath Parameters:(NSDictionary *)parameters returnBlock:(void(^)(id resuter,NSError *error))returnBlock {
    
    return [self.netManager POST:urlPath parameters:parameters progress:^(NSProgress * _Nonnull uploadProgress) {
        
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        if (returnBlock) {
            returnBlock(responseObject,nil);
        }
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        returnBlock(nil,error);
    }];
}
/** 下載*/
- (NSURLSessionTask *)downLoadWithUrl:(NSString *)urlPath parameters:(NSDictionary *)parameters returnBlock:(void(^)(id resuter,NSError *error))returnBlock progressBlock:(void(^)(NSProgress *downloadProgress))progressBlock {
    NSURL *url = [NSURL URLWithString:[BASE_URL stringByAppendingString:urlPath]];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    return [self.netManager downloadTaskWithRequest:request progress:progressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
        NSString *path =  [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES) lastObject];
        NSString *filePath = [path stringByAppendingString:response.suggestedFilename];
        return [NSURL fileURLWithPath:filePath];
    } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
        if (error) {
            returnBlock(nil,error);
            return ;
        }
        NSString *path = [filePath path];
        NSData *data = [[NSData alloc]initWithContentsOfFile:path];
        returnBlock(data,nil);
    }];
}
/** 上傳*/
- (NSURLSessionTask *)uploadingUrl:(NSString *)urlPath parameters:(NSDictionary *)parameters returnBlock:(void(^)(id resuter,NSError *error))returnBlock progressBlock:(void(^)(NSProgress *downloadProgress))progressBlock {
    NSURL *url = [NSURL URLWithString:[BASE_URL stringByAppendingString:urlPath]];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSData *data = [NSJSONSerialization dataWithJSONObject:parameters options:NSJSONWritingPrettyPrinted error:nil];
    return [self.netManager uploadTaskWithRequest:request fromData:data progress:progressBlock completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
        if (error) {
            returnBlock(nil,error);
            return ;
        }
        returnBlock(responseObject,nil);
    }];
}
/** 取消*/
- (void)cancel:(NSString *)urlKey {
    if (urlKey == nil) {
        for (NSString *key in self.taskDictionary) {
            NSURLSessionDownloadTask *downloadTask = self.taskDictionary[key];
            [downloadTask cancel];
        }
        [self.taskDictionary removeAllObjects];
    }else{
         NSURLSessionDownloadTask *downloadTask = self.taskDictionary[urlKey];
        [downloadTask cancel];
        [self.taskDictionary removeObjectForKey:urlKey];
    }
}
/** 暫停*/
- (void)suspend:(NSString *)urlkey {
    if (urlkey == nil) {
        for (NSString *key in self.taskDictionary) {
            NSURLSessionDownloadTask *downloadTask = self.taskDictionary[key];
            [downloadTask suspend];
        }
    }else{
        NSURLSessionDownloadTask *downloadTask = self.taskDictionary[urlkey];
        [downloadTask suspend];
    }
}
/** 繼續(xù)*/
- (void)resume:(NSString *)urlkey {
    if (urlkey == nil) {
        for (NSString *key in self.taskDictionary) {
            NSURLSessionDownloadTask *downloadTask = self.taskDictionary[key];
            [downloadTask resume];
        }
    }else{
        NSURLSessionDownloadTask *downloadTask = self.taskDictionary[urlkey];
        [downloadTask resume];
    }
}
@end

#pragma mark <--公布的接口-->
/** 發(fā)起請(qǐng)求*/
void request(NSString *urlPath, MXNet_YTPE type,id parameters,void(^returnBlock)(id resuter,NSError *error),void(^progressBlock)(NSProgress *downloadProgress)) {
    [NetTool requestUrl:urlPath Moth:type parameters:parameters returnBlock:returnBlock progressBlock:progressBlock];
}
/** 取消請(qǐng)求*/
void cancel(NSString *urlPath) {
    [NetTool cancel:urlPath];
}
/** 暫停請(qǐng)求*/
void suspend(NSString *urlPath) {
    [NetTool suspend:urlPath];
}
/** 繼續(xù)請(qǐng)求*/
void resume(NSString *urlPath) {
    [NetTool resume:urlPath];
}

這里原本是想把MXNetTool類公布在.h的,通過MXNetTool類單例對(duì)象來進(jìn)行網(wǎng)絡(luò)操作,但是想到多人開發(fā),難免有人會(huì)寫錯(cuò)不用單例對(duì)象,自己要去創(chuàng)建重新創(chuàng)建,然而機(jī)智的我就想到了,把類的聲明放在.m文件里,.h文件用聲明幾個(gè)c語言函數(shù)接口,在.m里實(shí)現(xiàn)函數(shù),通過函數(shù)來調(diào)用MXNetTool的接口。??,這也許是多此一舉,歡迎來噴。

請(qǐng)求中的繼續(xù)/暫停/取消的實(shí)現(xiàn),這個(gè)很簡(jiǎn)單,就一次發(fā)起網(wǎng)絡(luò)請(qǐng)求,不管是用蘋果原生API方法,還是afn接口方法都會(huì)返回一個(gè)NSURLSessionTask類或者NSURLSessionTask子類對(duì)象,把網(wǎng)絡(luò)請(qǐng)求地址跟這個(gè)對(duì)象通過一個(gè)可變字典存起來,下次想操作哪個(gè)網(wǎng)絡(luò)請(qǐng)求就操作哪個(gè)網(wǎng)絡(luò)請(qǐng)求,NSURLSessionTask類提供類三個(gè)對(duì)象方法,suspend、resume、cancel,分別對(duì)應(yīng)暫停、繼續(xù)、取消。

功能設(shè)計(jì)時(shí),減少同一時(shí)間多次網(wǎng)絡(luò)請(qǐng)求的可能性

這也是設(shè)計(jì)網(wǎng)絡(luò)要用單例的原因,在上面我還沒有實(shí)現(xiàn),但很快會(huì)補(bǔ)上。具體實(shí)現(xiàn)可以去看一下串行隊(duì)列,任務(wù)之間的依賴關(guān)系。

請(qǐng)求數(shù)據(jù)用json

json數(shù)據(jù)是大多數(shù)公司用的數(shù)據(jù),方便解析。就說這么多,其他的就不做粘貼拷貝了。

圖片加載優(yōu)化

這個(gè)可以去仔細(xì)研究一下SDWebImage的內(nèi)部實(shí)現(xiàn),這里有一篇寫得不錯(cuò)的文章《SDWebImage 源碼閱讀筆記》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容