【完整APP】SpriteKit引擎開發iOS小遊戲之五(移動端網絡與優化)【完結】

【網絡動畫效果】

衆所周知,包括但不限於網絡處理,很多使用APP的時機都需要展示Loading或者Toast提示的形式來提升用戶的交互體驗。

  1. 自定義Loading類:是繼承UIActivityIndicatorView的子類。簡化創建與管理。指定了佈局與樣式等。對外暴露創建與消失方法。
#import "LJZLoading.h"

@implementation LJZLoading

- (instancetype)initWithActivityIndicatorStyle:(UIActivityIndicatorViewStyle)style
{
    self = [super initWithActivityIndicatorStyle:style];
    if(self){
        self.hidesWhenStopped = YES;
        self.frame = CGRectMake(0, 0, 150, 150);
        self.center = CGPointMake([UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.height/2);
        self.backgroundColor = [UIColor blackColor];
        self.alpha = 0.5;
        [[[UIApplication sharedApplication] keyWindow] addSubview:self];
        [self startAnimating];
    }
    return self;
}

- (void)StopShowLoading
{
    [self stopAnimating];
    [self removeFromSuperview];
}

@end
  1. 自定義Toast類”LJZToast“:設計爲以單例模式調用,控制內容與展示時長,在固定位置展示出提示信息。其頭文件如下:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface ToastLabel : UILabel
- (void)setMessageText:(NSString *)text;
@end

@interface LJZToast : NSObject
@property (nonatomic,strong)ToastLabel *toastLabel;

+ (instancetype)shareInstance;
- (void)ShowToast:(NSString *)message duration:(CGFloat)duration;

@end
  1. Toast的實現上,UILabel子類的樣式自定義,dispatch_once執行靜態單例。dispatch_after控制展示animateWithDuration實現消失動畫,具體實現如下:
#import "LJZToast.h"
@implementation ToastLabel

- (instancetype)init
{
    self = [super init];
    if(self){
        self.layer.cornerRadius = 8;
        self.layer.masksToBounds = YES;
        self.backgroundColor = [UIColor blackColor];
        self.numberOfLines = 0;
        self.textAlignment = NSTextAlignmentCenter;
        self.textColor = [UIColor whiteColor];
        self.font = [UIFont systemFontOfSize:15];
    }
    return self;
}

- (void)setMessageText:(NSString *)text
{
    [self setText:text];
    self.frame = CGRectMake(0, 0, 150, 50);
    self.center = CGPointMake([UIScreen mainScreen].bounds.size.width/2, 150);
}

@end


@implementation LJZToast

+ (instancetype)shareInstance
{
    static LJZToast *singleton = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        singleton = [[LJZToast alloc] init];
    });
    return singleton;
}

- (instancetype)init
{
    self = [super init];
    if(self){
        self.toastLabel = [[ToastLabel alloc] init];
    }
    return self;
}

- (void)ShowToast:(NSString *)message duration:(CGFloat)duration
{
    if([message length] == 0){
        return;
    }
    [_toastLabel setMessageText:message];
    _toastLabel.alpha = 0.8;
    [[[UIApplication sharedApplication] keyWindow] addSubview:_toastLabel];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self changeTime];
    });
}
- (void)changeTime
{
    [UIView animateWithDuration:0.2f animations:^{
        _toastLabel.alpha = 0;
    }completion:^(BOOL finished) {
        [_toastLabel removeFromSuperview];
    }];
}
@end

【移動端網絡請求】

  • NSURLSession
    對於iOS而言,網絡編程主要依賴兩種方式:NSURLSession以及NSURLConnection。在iOS9.0之後,NSURLConnection過期不再使用。本系統使用NSURLSession來完成客戶端的網絡模塊。NSURLSession具有以下優勢
    (1)NSURLSession支持http2.0協議。
    (2) NSURLSession在下載任務時會直接寫入沙盒和tem文件夾中,不會放入內存避免內存暴漲。
    (3)NSURLSession提供來全局的session並且可以統一配置,每一個實例也可單獨配置。
    (4)NSURLSession下載支持斷點續傳,可以取消暫停繼續,使用多線程異步處理下載。
  • 類NSURLSessionTask
    NSURLSessionTask是一個抽象類,如圖4-3展示,使用時根據具體需求使用子類。
    在這裏插入圖片描述
  • 請求過程與解析
    在遊戲中,用戶需要在登陸,註冊,上傳成績,以及查看排行等地方進行網絡請求,NSURLSession的使用非常簡便,根據會話對象創建一個請求Task然後執行,請求過程
    在這裏插入圖片描述

結合Loading與Toast的使用,APP中主要的網絡請求實現如下:

  1. 登錄請求與處理部分
- (void)HandleLogin
{
    [self.view endEditing:YES];
    NSString *username = self.account.text;
    NSString *password = self.password.text;
    //輸入合法性檢驗
    if (username.length == 0 || password.length == 0){
        [[LJZToast shareInstance] ShowToast:@"用戶名與密碼不能爲空!" duration:1];
        return;
    }
    
    LJZLoading *loadingView = [[LJZLoading alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://39.98.152.99:80/login?username=%@&password=%@",username,password]];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if(error == nil) {
            NSString *responseInfo = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];//接受字符串
            NSLog(@"reponse data:%@",responseInfo);
            if([responseInfo isEqualToString:@"enable"]){
                NSUserDefaults *userDefault= [NSUserDefaults standardUserDefaults];
                [userDefault setObject:username forKey:@"user_name"];
                dispatch_async(dispatch_get_main_queue(), ^{
                    [loadingView StopShowLoading];
                    [[LJZToast shareInstance] ShowToast:@"登錄成功!" duration:1];
                    [self dismissViewControllerAnimated:YES completion:nil];
                });
            } else{
                dispatch_async(dispatch_get_main_queue(), ^{
                    [loadingView StopShowLoading];
                    [[LJZToast shareInstance] ShowToast:@"登錄失敗,請檢查用戶名與密碼!" duration:1];
                });
            }
        }else{
            dispatch_async(dispatch_get_main_queue(), ^{
                [loadingView StopShowLoading];
                [[LJZToast shareInstance] ShowToast:@"請檢查網絡!" duration:1];
            });
            NSLog(@"server error:%@",error);
        }
    }];
    [dataTask resume];
}
  1. 註冊處理
- (void)HandleRegister
{
    NSString *username = self.account.text;
    NSString *password = self.password.text;
    if (username.length == 0 || password.length == 0){
        [[LJZToast shareInstance] ShowToast:@"用戶名與密碼不能爲空!" duration:1];
        return;
    }else if (username.length > 15 || password.length >15){
        [[LJZToast shareInstance] ShowToast:@"用戶名與密碼不超過15位!" duration:1];
        return;
    }
    //輸入合法性檢驗
    LJZLoading *loadingView = [[LJZLoading alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://39.98.152.99:80/register?username=%@&password=%@",username,password]];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if(error == nil) {
                NSString *responseInfo = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];//接受字符串
                NSLog(@"reponse data:%@",responseInfo);
                if([responseInfo isEqualToString:@"enable"]){
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [loadingView StopShowLoading];
                        [[LJZToast shareInstance] ShowToast:@"註冊成功!" duration:1];
                        [self dismissViewControllerAnimated:YES completion:nil];
                    });
                } else{
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [loadingView StopShowLoading];
                        [[LJZToast shareInstance] ShowToast:@"該名字已被註冊!" duration:1];
                    });
                }
            }else{
                dispatch_async(dispatch_get_main_queue(), ^{
                    [loadingView StopShowLoading];
                    [[LJZToast shareInstance] ShowToast:@"請檢查網絡!" duration:1];
                });
                
                NSLog(@"server error:%@",error);
            }
    }];
    [dataTask resume];
}
  1. 請求排行榜數據
- (void)GetPlayersInfo
{
    NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
    NSString *username = [userDefault objectForKey:@"user_name"];
    NSString *isLogin;
    if (username == nil){
        _playerAccoutRank.text = @"-";
        _playerAccoutScore.text = @"-";
        _playerAccoutName.text = @"未登陸";
        isLogin = @"no";
    } else{
        _playerAccoutName.text = [NSString stringWithFormat:@"%@",username];
        isLogin = @"yes";
    }

    //請求排行榜數據
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://39.98.152.99:80/rankInfo?username=%@&islogin=%@",username,isLogin]];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if(error == nil) {
                NSDictionary *responseInfo = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
                NSLog(@"reponse data:%@",responseInfo);
                if ([isLogin isEqualToString:@"yes"]){
                    _playerRank = responseInfo[@"self_rank"];
                    _playerScore = responseInfo[@"self_score"];
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [self ShowSelfInfo];
                    });
                }
                _firstPlayerName = responseInfo[@"p1_name"];
                _secondPlayerName = responseInfo[@"p2_name"];
                _thirdPlayerName = responseInfo[@"p3_name"];
                _fourthPlayerName = responseInfo[@"p4_name"];
                _fifthPlayerName = responseInfo[@"p5_name"];
                _sixthPlayerName = responseInfo[@"p6_name"];
                _firstPlayerScore = responseInfo[@"p1_score"];
                _secondPlayerScore = responseInfo[@"p2_score"];
                _thirdPlayerScore = responseInfo[@"p3_score"];
                _fourthPlayerScore = responseInfo[@"p4_score"];
                _fifthPlayerScore = responseInfo[@"p5_score"];
                _sixthPlayerScore = responseInfo[@"p6_score"];
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self ShowRankInfo];
                });
            }else{
                [[LJZToast shareInstance] ShowToast:@"請檢查網絡!" duration:1];
                NSLog(@"server error:%@",error);
            }
    }];
    [dataTask resume];
}
  1. 在遊戲結束界面上傳新成績
- (void)handleUpload
{
    NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
    NSString *username = [userDefault objectForKey:@"user_name"];
    if (username == nil){
        [[LJZToast shareInstance] ShowToast:@"未登陸不可上傳成績" duration:1];
    } else{
        LJZLoading *loadingView = [[LJZLoading alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://39.98.152.99:80/upLoadScore?username=%@&score=%ld",username,self.resultScore]];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSessionTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                if(error == nil) {
                    NSString *responseInfo = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];//接受字符串
                    NSLog(@"reponse data:%@",responseInfo);
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [loadingView StopShowLoading];
                        [[LJZToast shareInstance] ShowToast:[NSString stringWithFormat:@"%@",responseInfo] duration:1];
                    });
                }else{
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [loadingView StopShowLoading];
                        [[LJZToast shareInstance] ShowToast:@"上傳成績失敗,請檢查網絡!" duration:1];
                    });
                    NSLog(@"server error:%@",error);
                }
        }];
        [dataTask resume];
    }
}

【其他優化】

  1. 合理的地方加入主線程保護:主要爲UI改動。
dispatch_async(dispatch_get_main_queue(), ^{
                    //要做的事情
                    });
  1. 注意釋放內存
//移除通知
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

//場景中。。。
- (void)die
{
    [self.pipeTimer invalidate];
    [self.toolTimerOne invalidate];
    [self.toolTimerTwo invalidate];
    [self.toolTimerThree invalidate];
    
    [self.bgmPlayer stop];
    self.bgmPlayer = nil;
    [self changeToResultSceneWithScore:self.score];
}
  1. 耗時與持續工作放入後臺線程運行。
  2. 在關鍵的工作節點與事件觸發中打出Log信息,按一定規則打印,便於測試。

【未來展望】

作爲遊戲APP算是搭建比較完善了。遊戲組件的抽離也有利於後續的擴展遊戲功能。存在包括不限以下的ToDo:

  1. 遊戲角色單一,是否可添加遊戲角色的選擇。
  2. 遊戲賬號貨幣的積累,開放遊戲商城功能。
  3. 遊戲內容或者截圖分享?
  4. 道具與障礙的有待多元化?
  5. 支持多人運動飛行?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章