項目上線總結

always trust yourself

MyZone


題記

原定于雙休日打包上線的硬生生的拖到了今天上午,心里的石頭總算是落下了. 愛油我去是我的第五個獨立開發的app,自己也算得上是徹徹底底的一位老司機了.原以為隨著自己的經驗不斷豐富,開發起來的難度會隨之減少,但是結果卻是恰恰相反.因為我突然發現要考慮的事情太多了,無論是項目的框架構建,還是本地化的一些考量等都需要經過深思熟慮,并不是走來就開擼.本篇文章不會涉及到技術方面的問題,就當是一篇小日記吧,記錄下這個項目的開發流程以及不足之處和相應的解決方案.


notice

今天上午在打包提交審核的過程中遇到了一個問題,這里也一并記錄下.因為我使用的是xcode8,提交的版本在itunes connect中總是不顯示.后來在查看蘋果的郵件中找到了問題所在.因為訪問了用戶的隱私數據但是并沒有顯示的添加描述,所以構建版本失敗.

無法構建版本原因截圖.jpg

解決的方法也很簡單,只需要在info.plist添加相應的描述文件即可.如下所示,全部添加上即可

描述信息.png

項目的框架

在開始項目之前,首先需要對該款app的功能有一個大致的了解,最好列一個list,這樣成竹在胸,寫起代碼來自然會是得心應手.目錄如下

為了很好的解決傳統MVCcontroller代碼臃腫問題,我在每一個controller中引入了ViewModel的類,讓它全權負責網絡請求,以及相應的模型解析等事件,將控制器解放出來.每個控制器的代碼量控制在500行之內.當然,自我感覺該項目的模式并不是真正意義上的MVVM.因為它的體量并不算的上是一個很大的程序,功能并非很復雜.如果要是全部采用MVVM的形式,不免有點兒大材小用,得不償失的感覺.

-loveOil            //項目名稱
--AppManager       //manager
    --AppManager // 與app相關的類。首次登錄相關,設置window的根控制器
    --PermissonManager //授權相關 訪問相冊,定位
--AppEntry      //設置程序的入口
--Features         //模塊。包含各個模塊的Model,View,Controller,Manager
    --BaseViewController //基ViewController
    --IntroModule   //引導頁模塊
    --EntryNavModule //主程序入口
    --GasStationPort //加油站
    --UserPort      //用戶端
        --MainContainer //主要的容器
        --MainVC // 基控制器  MainTableVIewController
        --SubVIewController //用戶端下的分控制器 包括 買油 賣油 以及我

            --BaseModule //基模塊 用于其他幾個控制器繼承
            --LoginAndRegisterAbout(注冊登錄相關)
            --BuyOil //買油模塊

                    --SubVC // 訂單模塊

            --Profile //我的模塊

                    --VC // 主控制器

                            -- RemainingBalance 現金余額
                            -- MyOilCard 我的油卡
                            -- AddOil 我要加油
                            -- ReapacketOilCard 紅包油卡
                            -- SignInRewardVC 簽到獎勵
                            -- UnfilledOrderVC 未完成訂單
            --SellOil //賣油模塊

--categories            //類目。包含各種類的分類
--Frameworks        //系統框架。包含導入的系統的框架
--Helpers            //幫助類。包含網絡,數據庫,歸檔,定位等操作類的封裝和實現
    --CacheManager //緩存管理類
    --BottomAlertView //彈框相關
    --NetworkTool // 網絡請求相關---包含整個項目的網絡請求

--Utilites       //工具類,一些非對象的,而是類方法調用的類
--Vendors            //第三方庫。部分需要修改或者不支持cocoapod的第三方的框架引入
--Config                //配置。包含宏定義文件,全局配置文件,全局常量文件,顏色配置文件

    --PrefixHeader //pch文件
    --macro //常用的宏定義
    --firstLaunchAbout //首次登錄相關
    --category  //分類相關

--Resources            // 資源。包含plist,image,html,bundle,Localizable.strings等
--AppEntry            // 程序入口。包含AppDelegate,main.c,info.plist
--RemoteNotificationAbout // 通知相關(視圖控制器)
-Products           // 系統自動生成的.app所在文件夾
-Pods                    // 采用 CocoaPods 管理的第三方庫。

關于網絡請求

該項目采用的網絡請求是基于猿題庫YTKNetwork,它是對AFNetWorking的再次封裝,功能更加強大,使用起來也更加方便.這里不討論YTKNetwork這個網絡庫,如果有需要,可以去這里下載 YTKNetworkg.

  • 這里要記錄下我在使用這個網絡框架時所遇到的一個坑.

需要修改下AFNetWorking的配置信息,在AFURLResponseSerialization.m中,替換成如下即可

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }
    self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript",@"text/json",@"text/html",@"text/css", nil]; 
   //修改下可以接受的類型,這樣就不會報解析的錯誤.
    return self;
}

我新建了一個網絡請求類,命名為BaseYtkRequest,讓它繼承YTKRequest,然后改項目的所有網絡請求都繼承自BaseYtkRequest,這樣做的好處是將第三方庫對項目的影響降低到最小,低侵入,低耦合,這樣,即使在那一天需要替換網絡請求的庫,也只需要修改BaseYtkRequest接口,其他的網絡請求接口都不需要修改.

  • 請求所帶的參數大致如下
#import "YTKRequest.h"

@interface BaseYtkRequest : YTKRequest

/**
 *  當前版本號----每一個請求都有版本號
 */

@property (nonatomic, strong) NSString * ver;

/**
 *  當前請求時間戳
 */
@property (nonatomic, assign) NSUInteger  timestamp;

/**
 *  簽名
 */
@property (nonatomic, strong) NSString * sign;

/**
 *  隨機數
 */
@property (nonatomic, strong) NSString * random;

/**
 *  簽名
 */
@property (nonatomic, strong) NSString * token;

/**
 *  用戶id
 */
@property (nonatomic, assign) NSInteger users_id;

/**
 * 
 */
-(NSString *)staffRequestUrl;

關于應用內支付(該項目使用的有微信支付,以及支付寶支付)

這里不詳細討論如何接入上述的兩種支付方式,僅僅說說一些注意點(微信為例).

支付流程.png

總之,生成簽名等關鍵步驟,由服務器端來做,app端僅僅負責拿到數據并且吊起支付.代碼大致如下

 //1.吊起微信支付
          
 [[OpenShareManager manager] wechatpay:[[[request.responseJSONObject valueForKey:kData] valueForKey:kResult] valueForKey:kToken]];

  //方法實現:
 
#pragma mark - 微信支付

-(void)wechatpay:(NSDictionary *)orderDetail
{
    
    wechatPayResultModel * model = [wechatPayResultModel modelWithDictionary:orderDetail];

    PayReq *request = [[PayReq alloc] init];

    request.partnerId = model.partnerid;
    
    request.prepayId =  model.prepayid;
    
    request.package = model.package;
    
    request.nonceStr = model.noncestr;
    
    request.timeStamp = model.timestamp;
    
    request.sign = model.sign;

    [WXApi sendReq:request];
    
}
          
    

關于即時通訊(騰訊IM)

因為用戶需要實時的接收到訂單信息,例如掃描二維碼輸入金額后,需要立即接收到確認訂單的信息.那么只能使用長連接來完成上述功能.這里使用的是騰訊IM. 其中最主要的兩個功能是單點登錄以及新信息的接收.

  • 如何接入

在app啟動時,初始化騰訊IM.注意在初始化之前,必須首先設置在線狀態監聽者,以及新消息通知監聽者,這樣才能接收到用戶狀態變更以及新消息通知

/**
 *  初始化IMSdk
 */
-(void)initIMSDKConfig
{
    
    //0.禁用日志打印
    
    [[TIMManager sharedInstance] setLogLevel:TIM_LOG_NONE];
    

    //1.設置用戶在線狀態通知
    
    [self setUserStatusListener];
    
    
    //2.設置新消息通知
    
    [self setMessageListener];
   
    //3.初始化sdk
    
    [[TIMManager sharedInstance] initSdk:[appidAt3rdFormal intValue] accountType:accountType];
    
}
/**
 *  用戶在線狀態通知
 */

-(void)setUserStatusListener
{
    
       TIMUserStatusListenerVC * listener = [[TIMUserStatusListenerVC alloc] init];
    
    
       [[TIMManager sharedInstance] setUserStatusListener:listener];
}
  • 用戶狀態變更

需要遵循TIMUserStatusListener協議,并且實現協議的方法

/**
 *  被踢下線
 */

-(void)onForceOffline
{
    
    //1.彈框提示用戶
        
    kDISPATCH_MAIN_THREAD(^{
       
        DQAlertView * alertView = [[DQAlertView alloc] initWithTitle:@"下線通知" message:@"您的賬號在另一臺設備登錄,如非本人操作,則密碼可能已經泄露,建議前往修改密碼" cancelButtonTitle:@"取消" otherButtonTitle:@"重新登錄"];
        
        alertView.otherButtonAction = ^{
            
            
            //2.此處讓用戶重新登錄,對用戶透明
            
            __block MBProgressHUD * hud = nil;
            
            kDISPATCH_MAIN_THREAD(^{
               
               hud =  [MBProgressHUD showHudAddedTo:KEY_WINDOW mode:MBProgressHUDModeText text:WHENWAIT];
                
            });
            
                     
            UserLogInRequest * request = [[UserLogInRequest alloc] initWithUserAccount:valueForKey(kUserAccount) password:valueForKey(kUserPwd)];
            
            //2.1 登錄類型
            request.login_type = [valueForKey(kUserLoginType) integerValue];
            
            [request startWithCompletionBlockWithSuccess:^(__kindof YTKBaseRequest *request) {
                
                
                kDISPATCH_MAIN_THREAD(^{
                   
                    [hud hideAnimated:YES];
                    
                });
                
                //3.登錄成功
                
                if (ValueForRespones(request.responseJSONObject) == StatusCodeSuccess) {
                
                    
                    //4.緩存數據
                    
                    [UserModelManager saveProfileModelToLocal:[[request.responseJSONObject valueForKey:kData] valueForKey:kResult]];
                    
                    //5.IM重新登錄
                    
                    [IMManager userLogin];
                    
                    
                    //6.播放音效
                    
                    [SoundManager playSystemSound];
                }
               
                
            } failure:^(__kindof YTKBaseRequest *request) {
                
                
                kDISPATCH_MAIN_THREAD(^{
                    
                    [hud hideAnimated:YES];
                    
                });
                
            }];
            
            
        };
        
        
        alertView.cancelButtonAction = ^{
          
            
            //3.讓用戶重新登錄
            
            app_window.rootViewController = [[BaseNavigationController alloc] initWithRootViewController:[[MainInterfaceEntryViewController alloc] init]];
            
        };
        
    
        
        [alertView show];
        
    });
   
    
}

/**
 *  票據過期
 */
-(void)onUserSigExpired
{
    
     //1.發送請求
    UserSignHasExpiredRequestTool * requestTool = [[UserSignHasExpiredRequestTool alloc] init];
    
    [requestTool startWithCompletionBlockWithSuccess:^(__kindof YTKBaseRequest *request) {
        
        if (ValueForRespones(request.responseJSONObject) == StatusCodeSuccess) {
            
            //2.用戶重新登錄im
            
            //2.1防止異常奔潰
            if ([[[requestTool.responseJSONObject valueForKey:kData] valueForKey:kResult] valueForKey:KIMToken]) {
                
                [IMManager userLoginWithIMToken:[[[requestTool.responseJSONObject valueForKey:kData] valueForKey:kResult] valueForKey:KIMToken]];
            }
            
            
        }
        
    } failure:^(__kindof YTKBaseRequest *request) {
        
    }];
    
    
}


  • 設置新消息通知,并且遵循TIMMessageListener協議
/**
 *  設置新消息通知
 */

-(void)setMessageListener
{
    
    TIMUserMessangeListenerVC * messangeListener = [[TIMUserMessangeListenerVC alloc] init];
    
    [[TIMManager sharedInstance] setMessageListener:messangeListener];
}

大致總結如上,持續完善中.....

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,264評論 25 708
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,229評論 4 61
  • 為什么經常會出現這樣的情況,你明明是已經想通了的事情,結果一會功夫就又反悔了?你明明已經決定了的事情,事到臨頭就又...
    LoveYourself閱讀 9,654評論 5 50
  • 文l張西影 生活在這個世界上,萬事如意,事事順心,一帆風順,那是不可能的。很多的人都是在逆境中實現了人生的超越。既...
    豫視西影閱讀 761評論 5 14
  • 在得到APP里李笑來老師提一個概念,元認知,讓我大開眼界,居然還可以這樣? 其實我也是一個初學者,這也是我第一天寫...
    采蘑菇的帥哥閱讀 390評論 0 1