├── ExceedMVC.xcodeproj └── project.pbxproj └── ExceedMVC ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj └── LaunchScreen.storyboard ├── Core ├── AppSetting │ ├── AppSetting+Server.h │ ├── AppSetting+Server.m │ ├── AppSetting.h │ └── AppSetting.m ├── DB │ ├── DBBase │ │ ├── DBConnection.h │ │ ├── DBConnection.m │ │ ├── DBStatement.h │ │ └── DBStatement.m │ ├── DBController+Message.h │ ├── DBController+Message.m │ ├── DBController+Update.h │ ├── DBController+Update.m │ ├── DBController+UserInfo.h │ ├── DBController+UserInfo.m │ ├── DBController+Version.h │ ├── DBController+Version.m │ ├── DBController.h │ └── DBController.m ├── DataItem │ ├── ChatsItem.h │ ├── ChatsItem.m │ ├── UserInfo.h │ └── UserInfo.m ├── Engine │ ├── CoreEngine+Receive.h │ ├── CoreEngine+Receive.m │ ├── CoreEngine.h │ └── CoreEngine.m ├── FileManager │ ├── FileManager+Picture.h │ ├── FileManager+Picture.m │ ├── FileManager.h │ └── FileManager.m └── Net │ ├── NetController.h │ ├── NetController.m │ ├── SimplifiedAFN+UIKit │ ├── UIImageView+NBL.h │ └── UIImageView+NBL.m │ └── SimplifiedAFN │ ├── NBLHTTPFileManager.h │ ├── NBLHTTPFileManager.m │ ├── NBLHTTPManager.h │ └── NBLHTTPManager.m ├── ExceedMVC.sqlite ├── Info.plist ├── UI ├── Engine │ ├── UIEngine+TabBar.h │ ├── UIEngine+TabBar.m │ ├── UIEngine.h │ └── UIEngine.m ├── OtherView │ ├── MBProgressHUD.h │ ├── MBProgressHUD.m │ ├── UIView+MBProgressHUD.h │ └── UIView+MBProgressHUD.m ├── RootVC.h ├── RootVC.m └── VCs │ ├── AboutVC.h │ ├── AboutVC.m │ ├── ChatVC.h │ ├── ChatVC.m │ ├── ChatsVC.h │ ├── ChatsVC.m │ ├── ContactInfoVC.h │ ├── ContactInfoVC.m │ ├── ContactsVC.h │ ├── ContactsVC.m │ ├── LoginVC.h │ ├── LoginVC.m │ ├── MainVC.h │ ├── MainVC.m │ ├── MoreVC.h │ └── MoreVC.m └── main.m /ExceedMVC/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /ExceedMVC/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | #import "UIEngine.h" 12 | #import "CoreEngine.h" 13 | 14 | @interface AppDelegate () 15 | @property (nonatomic, strong) UIEngine *uiEngine; 16 | @property (nonatomic, strong) CoreEngine *coreEngine; 17 | @end 18 | 19 | @implementation AppDelegate 20 | 21 | 22 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 23 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 24 | // Override point for customization after application launch. 25 | // CoreEngine 26 | self.coreEngine = [[CoreEngine alloc] init]; 27 | // UIEngine 28 | self.uiEngine = [[UIEngine alloc] init]; 29 | self.uiEngine.engineCore = self.coreEngine; 30 | // set root ViewController 31 | self.window.rootViewController = self.uiEngine.rootViewController; 32 | 33 | self.window.backgroundColor = [UIColor whiteColor]; 34 | [self.window makeKeyAndVisible]; 35 | return YES; 36 | } 37 | 38 | - (void)applicationWillResignActive:(UIApplication *)application { 39 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 40 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 41 | } 42 | 43 | - (void)applicationDidEnterBackground:(UIApplication *)application { 44 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 45 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 46 | [self.coreEngine applicationDidEnterBackground]; 47 | } 48 | 49 | - (void)applicationWillEnterForeground:(UIApplication *)application { 50 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 51 | [self.coreEngine applicationWillEnterForeground]; 52 | } 53 | 54 | - (void)applicationDidBecomeActive:(UIApplication *)application { 55 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 56 | } 57 | 58 | - (void)applicationWillTerminate:(UIApplication *)application { 59 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /ExceedMVC/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /ExceedMVC/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ExceedMVC/Core/AppSetting/AppSetting+Server.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppSetting+Server.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "AppSetting.h" 10 | 11 | @interface AppSetting (Server) 12 | 13 | //原版本 14 | + (void)oldVersion:(NSString *)oldVersion; 15 | + (NSString *)oldVersion; 16 | 17 | //新版本 18 | + (void)newVersion:(NSString *)newVersion; 19 | + (NSString *)newVersion; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /ExceedMVC/Core/AppSetting/AppSetting+Server.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppSetting+Server.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "AppSetting+Server.h" 10 | 11 | @implementation AppSetting (Server) 12 | 13 | //原版本 14 | + (void)oldVersion:(NSString *)oldVersion 15 | { 16 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 17 | [userDefaults setValue:oldVersion forKey:@"OldVersion"]; 18 | [userDefaults synchronize]; 19 | } 20 | + (NSString *)oldVersion 21 | { 22 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 23 | return [userDefaults objectForKey:@"OldVersion"]; 24 | } 25 | 26 | //新版本 27 | + (void)newVersion:(NSString *)newVersion 28 | { 29 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 30 | [userDefaults setValue:newVersion forKey:@"NewVersion"]; 31 | [userDefaults synchronize]; 32 | } 33 | + (NSString *)newVersion 34 | { 35 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 36 | return [userDefaults objectForKey:@"NewVersion"]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /ExceedMVC/Core/AppSetting/AppSetting.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppSetting.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppSetting : NSObject 12 | 13 | //当前版本号(用于升级) 14 | + (NSString *)userInfoVersion; 15 | + (void)userInfoVersion:(NSString *)strVersion; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /ExceedMVC/Core/AppSetting/AppSetting.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppSetting.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "AppSetting.h" 10 | 11 | @implementation AppSetting 12 | 13 | //当前版本号(用于升级) 14 | + (NSString *)userInfoVersion 15 | { 16 | NSString *strVersion = @"0.0"; 17 | //从配置文件中获取数据 18 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 19 | //获取版本 20 | NSString *version = [userDefaults objectForKey:@"version"]; 21 | // 22 | if (version.length > 0) { 23 | strVersion = [NSString stringWithString:version]; 24 | } 25 | return strVersion; 26 | } 27 | + (void)userInfoVersion:(NSString *)strVersion 28 | { 29 | if (strVersion) { 30 | //配置保存到文件 31 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 32 | //版本号 33 | [userDefaults setObject:strVersion forKey:@"version"]; 34 | //同步数据 35 | [userDefaults synchronize]; 36 | } 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DB/DBBase/DBConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBConnection.h 3 | // 4 | // 5 | // Created by yjh4866 on 11-9-11. 6 | // Copyright 2011 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | #define NAME_DB @"ExceedMVC.sqlite" 14 | 15 | @interface DBConnection : NSObject 16 | 17 | + (BOOL)createCopyOfDatabaseIfNeeded; 18 | + (sqlite3*)openDatabase:(NSString*)dbfilename; 19 | + (void)closeDatabase; 20 | 21 | // 22 | + (void)beginTransaction; 23 | + (void)commitTransaction; 24 | + (void)runSQLMore:(const char*)strsql; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DB/DBBase/DBConnection.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBConnection.m 3 | // 4 | // 5 | // Created by yjh4866 on 11-9-11. 6 | // Copyright 2011 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import "DBConnection.h" 10 | 11 | // 12 | static sqlite3* gTheDatabase = nil; 13 | 14 | 15 | @implementation DBConnection 16 | 17 | // Creates a writable copy of the bundled default database in the application Documents directory. 18 | + (BOOL)createCopyOfDatabaseIfNeeded { 19 | 20 | //获取数据库路径 21 | NSString *pathDB = [NSString stringWithFormat:@"%@/Documents/%@", NSHomeDirectory(), NAME_DB]; 22 | //查看数据库是否存在,存在则直接返回 23 | NSFileManager *fileManager = [NSFileManager defaultManager]; 24 | if ([fileManager fileExistsAtPath:pathDB]) { 25 | return YES; 26 | } 27 | //不存在则从资源中复制 28 | NSString *dbpathResource = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:NAME_DB]; 29 | NSError *error; 30 | BOOL success = [fileManager copyItemAtPath:dbpathResource toPath:pathDB error:&error]; 31 | if (!success) { 32 | NSAssert1(0, @"创建数据库失败(%@)", [error localizedDescription]); 33 | } 34 | return success; 35 | } 36 | 37 | + (sqlite3*)openDatabase:(NSString*)dbfilename { 38 | 39 | if (gTheDatabase) { 40 | return gTheDatabase; 41 | } 42 | 43 | //获取数据库路径 44 | NSString* strHomePath = NSHomeDirectory(); 45 | NSString *pathDB = [NSString stringWithFormat:@"%@/Documents/%@", strHomePath, dbfilename]; 46 | // Open the database. The database was prepared outside the application. 47 | if (sqlite3_open([pathDB UTF8String], &gTheDatabase) != SQLITE_OK) { 48 | // Even though the open failed, call close to properly clean up resources. 49 | sqlite3_close(gTheDatabase); 50 | gTheDatabase = nil; 51 | NSLog(@"数据库打开失败(%s)", sqlite3_errmsg(gTheDatabase)); 52 | } 53 | return gTheDatabase; 54 | } 55 | 56 | + (void)closeDatabase { 57 | 58 | if (gTheDatabase) { 59 | sqlite3_close(gTheDatabase); 60 | gTheDatabase = nil; 61 | } 62 | } 63 | 64 | + (void)beginTransaction { 65 | char *errmsg; 66 | sqlite3_exec(gTheDatabase, "BEGIN", NULL, NULL, &errmsg); 67 | } 68 | + (void)commitTransaction { 69 | char *errmsg; 70 | sqlite3_exec(gTheDatabase, "COMMIT", NULL, NULL, &errmsg); 71 | } 72 | + (void)runSQLMore:(const char*)strsql { 73 | char *errmsg; 74 | if (sqlite3_exec(gTheDatabase, strsql, NULL, NULL, &errmsg) != SQLITE_OK) { 75 | // ignore error 76 | NSLog(@"数据库执行错误(%s)", errmsg); 77 | } 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DB/DBBase/DBStatement.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBStatement.h 3 | // 4 | // 5 | // Created by yjh4866 on 11-9-11. 6 | // Copyright 2011 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | @interface DBStatement : NSObject { 14 | 15 | sqlite3_stmt *_stmt; 16 | } 17 | 18 | - (id)initWithSQL:(const char*)sql; 19 | 20 | // method 21 | - (int)step; 22 | - (void)reset; 23 | 24 | // Getter 25 | - (NSString*)getString:(int)index; 26 | - (int)getInt32:(int)index; 27 | - (double)getDouble:(int)index; 28 | - (long long int)getInt64:(int)index; 29 | - (BOOL)getBool:(int)index; 30 | - (NSData*)getData:(int)index; 31 | 32 | // Binder 33 | - (void)bindString:(NSString*)value forIndex:(int)index; 34 | - (void)bindInt32:(int)value forIndex:(int)index; 35 | - (void)bindDouble:(double)value forIndex:(int)index; 36 | - (void)bindInt64:(long long int)value forIndex:(int)index; 37 | - (void)bindBool:(BOOL)value forIndex:(int)index; 38 | - (void)bindData:(NSData*)data forIndex:(int)index; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DB/DBBase/DBStatement.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBStatement.m 3 | // 4 | // 5 | // Created by yjh4866 on 11-9-11. 6 | // Copyright 2011 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import "DBStatement.h" 10 | #import "DBConnection.h" 11 | 12 | 13 | @implementation DBStatement 14 | 15 | - (id)initWithSQL:(const char*)sql { 16 | self = [super init]; 17 | if (self) { 18 | // Custom initialization 19 | sqlite3 *db = [DBConnection openDatabase:NAME_DB]; 20 | if (sqlite3_prepare_v2(db, sql, -1, &_stmt, NULL) != SQLITE_OK) { 21 | NSLog(@"Failed to prepare statement '%s' (%s)", sql, sqlite3_errmsg(db)); 22 | //NSAssert2(0, @"Failed to prepare statement '%s' (%s)", sql, sqlite3_errmsg(db)); 23 | } 24 | } 25 | return self; 26 | } 27 | 28 | - (void)dealloc { 29 | 30 | sqlite3_finalize(_stmt); 31 | [DBConnection closeDatabase]; 32 | 33 | [super dealloc]; 34 | } 35 | 36 | - (int)step { 37 | return sqlite3_step(_stmt); 38 | } 39 | 40 | - (void)reset { 41 | sqlite3_reset(_stmt); 42 | } 43 | 44 | 45 | #pragma mark Get 46 | 47 | - (NSString*)getString:(int)index { 48 | char *value = (char*)sqlite3_column_text(_stmt, index); 49 | if (value) { 50 | return [NSString stringWithUTF8String:value]; 51 | } 52 | return @""; 53 | } 54 | - (int)getInt32:(int)index { 55 | return sqlite3_column_int(_stmt, index); 56 | } 57 | - (double)getDouble:(int)index { 58 | return sqlite3_column_double(_stmt, index); 59 | } 60 | - (long long)getInt64:(int)index { 61 | return sqlite3_column_int64(_stmt, index); 62 | } 63 | - (BOOL)getBool:(int)index { 64 | return sqlite3_column_int(_stmt, index) != 0; 65 | } 66 | - (NSData*)getData:(int)index { 67 | int length = sqlite3_column_bytes(_stmt, index); 68 | return [NSData dataWithBytes:sqlite3_column_blob(_stmt, index) length:length]; 69 | } 70 | 71 | 72 | #pragma mark Bind 73 | 74 | - (void)bindString:(NSString*)value forIndex:(int)index { 75 | sqlite3_bind_text(_stmt, index, [value UTF8String], -1, SQLITE_TRANSIENT); 76 | } 77 | - (void)bindInt32:(int)value forIndex:(int)index { 78 | sqlite3_bind_int(_stmt, index, value); 79 | } 80 | - (void)bindDouble:(double)value forIndex:(int)index { 81 | sqlite3_bind_double(_stmt, index, value); 82 | } 83 | - (void)bindInt64:(long long)value forIndex:(int)index { 84 | sqlite3_bind_int64(_stmt, index, value); 85 | } 86 | - (void)bindData:(NSData*)value forIndex:(int)index { 87 | sqlite3_bind_blob(_stmt, index, value.bytes, (int)value.length, SQLITE_TRANSIENT); 88 | } 89 | - (void)bindBool:(BOOL)value forIndex:(int)index { 90 | sqlite3_bind_int(_stmt, index, value?1:0); 91 | } 92 | 93 | @end 94 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DB/DBController+Message.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBController+Message.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "DBController.h" 10 | 11 | @interface DBController (Message) 12 | 13 | // 从数据库读取会话列表 14 | + (void)loadChats:(NSMutableArray *)marrChat; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DB/DBController+Message.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBController+Message.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "DBController+Message.h" 10 | #import "ChatsItem.h" 11 | 12 | @implementation DBController (Message) 13 | 14 | // 从数据库读取会话列表 15 | + (void)loadChats:(NSMutableArray *)marrChat 16 | { 17 | NSArray *arrAvatar = [NSArray arrayWithObjects: 18 | @"http://avatar.csdn.net/6/3/8/1_yjh4866.jpg", 19 | @"http://avatar.csdn.net/E/2/4/1_carefree31441.jpg", 20 | @"http://avatar.csdn.net/4/3/4/1_zhy_cheng.jpg", 21 | @"http://cc.cocimg.com/bbs/attachment/upload/71/1257711352020236.png", nil]; 22 | // 23 | for (int i = 0; i < 8; i++) { 24 | ChatsItem *chatsItem = [[ChatsItem alloc] init]; 25 | chatsItem.userID = 10+i; 26 | chatsItem.userName = [NSString stringWithFormat:@"好友%i", i+1]; 27 | chatsItem.latestMsg = [NSString stringWithFormat:@"最新消息 %i", 10*i+rand()%13]; 28 | chatsItem.avatarUrl = [arrAvatar objectAtIndex:rand()%arrAvatar.count]; 29 | [marrChat addObject:chatsItem]; 30 | } 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DB/DBController+Update.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBController+Update.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "DBController.h" 10 | 11 | @interface DBController (Update) 12 | 13 | // 升级 14 | + (void)update; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DB/DBController+Update.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBController+Update.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "DBController+Update.h" 10 | #import "DBController+Version.h" 11 | 12 | @implementation DBController (Update) 13 | 14 | // 升级 15 | + (void)update 16 | { 17 | NSMutableDictionary *mdicDBVersion = [NSMutableDictionary dictionary]; 18 | //获取数据库版本 19 | [DBController loadDBVersion:mdicDBVersion]; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DB/DBController+UserInfo.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBController+UserInfo.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "DBController.h" 10 | 11 | @interface DBController (UserInfo) 12 | 13 | // 从数据库加载好友列表 14 | + (void)loadContacts:(NSMutableArray *)marrContact; 15 | 16 | // 查询姓名 17 | + (NSString *)getUserNameOf:(UInt64)userID; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DB/DBController+UserInfo.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBController+UserInfo.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "DBController+UserInfo.h" 10 | #import "UserInfo.h" 11 | 12 | @implementation DBController (UserInfo) 13 | 14 | // 从数据库加载好友列表 15 | + (void)loadContacts:(NSMutableArray *)marrContact 16 | { 17 | //从数据库中读取好友列表 18 | 19 | NSArray *arrAvatar = [NSArray arrayWithObjects: 20 | @"http://avatar.csdn.net/6/3/8/1_yjh4866.jpg", 21 | @"http://avatar.csdn.net/E/2/4/1_carefree31441.jpg", 22 | @"http://avatar.csdn.net/4/3/4/1_zhy_cheng.jpg", 23 | @"http://cc.cocimg.com/bbs/attachment/upload/71/1257711352020236.png", nil]; 24 | //随便加些测试数据吧 25 | for (int i = 0; i < 5; i++) { 26 | UserInfo *friendInfo = [[UserInfo alloc] init]; 27 | friendInfo.userID = 10+i; 28 | friendInfo.userName = [NSString stringWithFormat:@"好友%i", 10+i]; 29 | friendInfo.avatarUrl = [arrAvatar objectAtIndex:i%arrAvatar.count]; 30 | [marrContact addObject:friendInfo]; 31 | } 32 | } 33 | 34 | // 查询姓名 35 | + (NSString *)getUserNameOf:(UInt64)userID 36 | { 37 | //跟数据库接上,就能读到最新的名字了 38 | return [NSString stringWithFormat:@"好友%lld", userID]; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DB/DBController+Version.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBController+Version.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "DBController.h" 10 | 11 | @interface DBController (Version) 12 | 13 | //从数据库中获取数据库版本 14 | + (NSString*)getDBVersion; 15 | 16 | //从数据库中获取数据库版本号放入可变字典中 17 | + (void)loadDBVersion:(NSMutableDictionary*)dicDBVersion; 18 | 19 | //修改数据库版本 20 | + (void)modifyDBVersionWithString:(NSString *)strDBVersion; 21 | 22 | //修改数据库版本 23 | + (void)modifyDBVersionWithDictionary:(NSDictionary *)dicDBVersion; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DB/DBController+Version.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBController+Version.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "DBController+Version.h" 10 | 11 | @implementation DBController (Version) 12 | 13 | //从数据库中获取数据库版 14 | + (NSString*)getDBVersion { 15 | 16 | NSString *strDBVersion = @""; 17 | 18 | const char* sql = "SELECT version0,version1,version2,version3 FROM version"; 19 | DBStatement *stmt = [[DBStatement alloc] initWithSQL:sql]; 20 | // 21 | if ([stmt step] == SQLITE_ROW) { 22 | int v0 = [stmt getInt32:0]; 23 | int v1 = [stmt getInt32:1]; 24 | int v2 = [stmt getInt32:2]; 25 | int v3 = [stmt getInt32:3]; 26 | // 27 | strDBVersion = [NSString stringWithFormat:@"%i.%i.%i.%i", v0, v1, v2, v3]; 28 | } 29 | return strDBVersion; 30 | } 31 | 32 | //从数据库中获取数据库版本号放入可变字典中 33 | + (void)loadDBVersion:(NSMutableDictionary*)dicDBVersion { 34 | 35 | const char* sql = "SELECT version0,version1,version2,version3 FROM version"; 36 | DBStatement *stmt = [[DBStatement alloc] initWithSQL:sql]; 37 | // 38 | if ([stmt step] == SQLITE_ROW) { 39 | int v0 = [stmt getInt32:0]; 40 | int v1 = [stmt getInt32:1]; 41 | int v2 = [stmt getInt32:2]; 42 | int v3 = [stmt getInt32:3]; 43 | // 44 | [dicDBVersion setObject:[NSNumber numberWithInt:v0] forKey:@"version0"]; 45 | [dicDBVersion setObject:[NSNumber numberWithInt:v1] forKey:@"version1"]; 46 | [dicDBVersion setObject:[NSNumber numberWithInt:v2] forKey:@"version2"]; 47 | [dicDBVersion setObject:[NSNumber numberWithInt:v3] forKey:@"version3"]; 48 | } 49 | } 50 | 51 | //修改数据库版本 52 | + (void)modifyDBVersionWithString:(NSString *)strDBVersion 53 | { 54 | NSArray *arrayVersion = [strDBVersion componentsSeparatedByString:@"."]; 55 | 56 | const char* sql = "UPDATE version SET version0=?,version1=?,version2=?,version3=? WHERE id=1"; 57 | DBStatement *stmt = [[DBStatement alloc] initWithSQL:sql]; 58 | //绑定各版本值 59 | for (int i = 0; i < 4; i++) { 60 | if (arrayVersion.count > i) { 61 | int v = [[arrayVersion objectAtIndex:i] intValue]; 62 | [stmt bindInt32:v forIndex:i+1]; 63 | } 64 | else { 65 | break; 66 | } 67 | } 68 | // 69 | if ([stmt step] == SQLITE_DONE) { 70 | } 71 | } 72 | 73 | //修改数据库版本 74 | + (void)modifyDBVersionWithDictionary:(NSDictionary *)dicDBVersion 75 | { 76 | const char* sql = "UPDATE version SET version0=?,version1=?,version2=?,version3=? WHERE id=1"; 77 | DBStatement *stmt = [[DBStatement alloc] initWithSQL:sql]; 78 | //绑定各版本值 79 | for (int i = 0; i < 4; i++) { 80 | NSString *str = [NSString stringWithFormat:@"version%i", i]; 81 | if ([dicDBVersion objectForKey:str]) { 82 | int v = [[dicDBVersion objectForKey:str] intValue]; 83 | [stmt bindInt32:v forIndex:i+1]; 84 | } 85 | else { 86 | break; 87 | } 88 | } 89 | // 90 | if ([stmt step] == SQLITE_DONE) { 91 | } 92 | } 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DB/DBController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBController.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "DBStatement.h" 11 | 12 | @interface DBController : NSObject 13 | 14 | //清空数据 15 | + (void)clearDB; 16 | 17 | @end 18 | 19 | 20 | #ifdef DEBUG 21 | 22 | #define DBLOG(fmt,...) NSLog((@"DB->%s(%d):"fmt),__PRETTY_FUNCTION__,__LINE__,##__VA_ARGS__) 23 | 24 | #else 25 | 26 | #define DBLOG(fmt,...) NSLog(fmt,##__VA_ARGS__) 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DB/DBController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBController.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "DBController.h" 10 | 11 | 12 | @implementation DBController 13 | 14 | //清空数据 15 | + (void)clearDB 16 | { 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DataItem/ChatsItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // ChatsItem.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ChatsItem : NSObject 12 | 13 | @property (nonatomic, assign) UInt64 userID; 14 | @property (nonatomic, copy) NSString *userName; 15 | @property (nonatomic, copy) NSString *latestMsg; 16 | @property (nonatomic, copy) NSString *avatarUrl; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DataItem/ChatsItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // ChatsItem.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "ChatsItem.h" 10 | 11 | @implementation ChatsItem 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DataItem/UserInfo.h: -------------------------------------------------------------------------------- 1 | // 2 | // UserInfo.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-7. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UserInfo : NSObject 12 | 13 | @property (nonatomic, assign) UInt64 userID; 14 | @property (nonatomic, copy) NSString *userName; 15 | @property (nonatomic, copy) NSString *avatarUrl; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /ExceedMVC/Core/DataItem/UserInfo.m: -------------------------------------------------------------------------------- 1 | // 2 | // UserInfo.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-7. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "UserInfo.h" 10 | 11 | @implementation UserInfo 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ExceedMVC/Core/Engine/CoreEngine+Receive.h: -------------------------------------------------------------------------------- 1 | // 2 | // CoreEngine+Receive.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "CoreEngine.h" 10 | 11 | @interface CoreEngine (Receive) 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ExceedMVC/Core/Engine/CoreEngine+Receive.m: -------------------------------------------------------------------------------- 1 | // 2 | // CoreEngine+Receive.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "CoreEngine+Receive.h" 10 | 11 | @implementation CoreEngine (Receive) 12 | 13 | // 网络原因登录失败 14 | - (void)netController:(NetController *)netController loginError:(NSError *)error 15 | { 16 | [[NSNotificationCenter defaultCenter] postNotificationName:NetLoginFailure 17 | object:nil]; 18 | } 19 | 20 | // 登录返回数据 21 | - (void)netController:(NetController *)netController loginResult:(NSDictionary *)userInfo 22 | { 23 | //假设成功 24 | [[NSNotificationCenter defaultCenter] postNotificationName:NetLoginSuccess object:nil userInfo:@{@"userInfo": userInfo}]; 25 | } 26 | 27 | // 网络原因用户资料获取失败 28 | - (void)netController:(NetController *)netController userInfoError:(NSError *)error of:(UInt64)userID 29 | { 30 | NSDictionary *dicUserInfo = @{@"userid": @(userID), @"error": error}; 31 | [[NSNotificationCenter defaultCenter] postNotificationName:NetUserInfoFailure object:nil userInfo:dicUserInfo]; 32 | } 33 | 34 | // 获取用户资料返回数据 35 | - (void)netController:(NetController *)netController userInfo:(NSDictionary *)userInfo of:(UInt64)userID 36 | { 37 | //数据错误的话也是失败噢,这里以正确处理 38 | 39 | //是个网页数据,服务器端不通的情况下可以在这里加测试数据 40 | //需要先将数据保存到数据库噢 41 | //消息中心多发 42 | NSString *newUserName = [NSString stringWithFormat:@"好友%lld新名字", userID]; 43 | NSDictionary *dicUserInfo = @{@"userid": @(userID), 44 | @"username": newUserName, 45 | @"avatar": @"http://avatar.csdn.net/0/E/4/1_yorhomwang.jpg"}; 46 | [[NSNotificationCenter defaultCenter] postNotificationName:NetUserInfoSuccess object:nil userInfo:dicUserInfo]; 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /ExceedMVC/Core/Engine/CoreEngine.h: -------------------------------------------------------------------------------- 1 | // 2 | // CoreEngine.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "NetController.h" 11 | 12 | 13 | @interface CoreEngine : NSObject 14 | 15 | @property (nonatomic, assign) BOOL online; 16 | 17 | - (void)applicationWillEnterForeground; 18 | 19 | - (void)applicationDidEnterBackground; 20 | 21 | - (void)applicationDidBecomeActive; 22 | 23 | @end 24 | 25 | 26 | extern NSString *const NetLoginFailure; 27 | extern NSString *const NetLoginSuccess; 28 | extern NSString *const NetUserInfoFailure; 29 | extern NSString *const NetUserInfoSuccess; 30 | 31 | 32 | #ifdef DEBUG 33 | 34 | #define CORELOG(fmt,...) NSLog((@"CORE->%s(%d):"fmt),__PRETTY_FUNCTION__,__LINE__,##__VA_ARGS__) 35 | 36 | #else 37 | 38 | #define CORELOG(fmt,...) NSLog(fmt,##__VA_ARGS__) 39 | 40 | #endif 41 | 42 | -------------------------------------------------------------------------------- /ExceedMVC/Core/Engine/CoreEngine.m: -------------------------------------------------------------------------------- 1 | // 2 | // CoreEngine.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "CoreEngine.h" 10 | #import "DBConnection.h" 11 | #import "DBController+Update.h" 12 | #import "AppSetting+Server.h" 13 | 14 | @implementation CoreEngine 15 | 16 | - (id)init 17 | { 18 | self = [super init]; 19 | if (self) { 20 | //复制数据库文件 21 | [DBConnection createCopyOfDatabaseIfNeeded]; 22 | //数据库升级 23 | [DBController update]; 24 | //本地数据升级 25 | [self update]; 26 | 27 | //网络 28 | [NetController sharedInstance].delegate = self; 29 | 30 | //这里设置成NO,即可测试需要登录的情况 31 | self.online = YES; 32 | } 33 | return self; 34 | } 35 | 36 | - (void)dealloc 37 | { 38 | } 39 | 40 | 41 | #pragma mark - Public 42 | 43 | - (void)applicationWillEnterForeground 44 | { 45 | //版本更新记录上传 46 | NSString *oldVersion = [AppSetting oldVersion]; 47 | NSString *newVersion = [AppSetting newVersion]; 48 | if (oldVersion.length > 0 && newVersion.length > 0) { 49 | } 50 | } 51 | 52 | - (void)applicationDidEnterBackground 53 | { 54 | } 55 | 56 | - (void)applicationDidBecomeActive 57 | { 58 | 59 | } 60 | 61 | 62 | #pragma mark - Private 63 | 64 | - (void)update 65 | { 66 | 67 | } 68 | 69 | @end 70 | 71 | 72 | #pragma mark - Notification 73 | 74 | NSString *const NetLoginFailure = @"NetLoginFailure"; 75 | NSString *const NetLoginSuccess = @"NetLoginSuccess"; 76 | NSString *const NetUserInfoFailure = @"NetUserInfoFailure"; 77 | NSString *const NetUserInfoSuccess = @"NetUserInfoSuccess"; 78 | 79 | -------------------------------------------------------------------------------- /ExceedMVC/Core/FileManager/FileManager+Picture.h: -------------------------------------------------------------------------------- 1 | // 2 | // FileManager+Picture.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "FileManager.h" 10 | #import 11 | 12 | @interface FileManager (Picture) 13 | 14 | // 图片缓存目录 15 | + (NSString *)cachePathForPicture; 16 | 17 | // 获取指定url的图片保存路径 18 | + (NSString *)picturePathOfUrl:(NSString *)picUrl; 19 | 20 | // 将图片数据保存到指定路径 21 | + (void)savePictureData:(NSData *)picData to:(NSString *)filePath; 22 | 23 | // 获取指定url的图片 24 | + (UIImage *)pictureOfUrl:(NSString *)picUrl; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /ExceedMVC/Core/FileManager/FileManager+Picture.m: -------------------------------------------------------------------------------- 1 | // 2 | // FileManager+Picture.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "FileManager+Picture.h" 10 | 11 | #define PicPath(picUrl) [FileManager picturePathOfUrl:picUrl] 12 | 13 | @implementation FileManager (Picture) 14 | 15 | // 图片缓存目录 16 | + (NSString *)cachePathForPicture 17 | { 18 | NSString *picCachePath = [[FileManager cachePathForFile] stringByAppendingPathComponent:@"Pictures"]; 19 | // 20 | NSFileManager *fileManager = [NSFileManager defaultManager]; 21 | if (![fileManager fileExistsAtPath:picCachePath]) { 22 | [fileManager createDirectoryAtPath:picCachePath withIntermediateDirectories:YES 23 | attributes:nil error:nil]; 24 | } 25 | // 26 | return picCachePath; 27 | } 28 | 29 | // 获取指定url的图片保存路径 30 | + (NSString *)picturePathOfUrl:(NSString *)picUrl 31 | { 32 | //拼接图片文件名 33 | NSCharacterSet *setChars = [NSCharacterSet characterSetWithCharactersInString:@":/."]; 34 | NSArray *components = [picUrl componentsSeparatedByCharactersInSet:setChars]; 35 | NSMutableString *mstrFileName = [NSMutableString string]; 36 | for (NSString *component in components) { 37 | [mstrFileName appendString:component]; 38 | [mstrFileName appendString:@"_"]; 39 | } 40 | //拼接后缀 41 | [mstrFileName appendFormat:@".%@", [[picUrl pathExtension] lowercaseString]]; 42 | //拼接图片文件路径 43 | NSString *picPath = [NSString stringWithFormat:@"%@/%@", 44 | [FileManager cachePathForPicture], mstrFileName]; 45 | // 46 | return picPath; 47 | } 48 | 49 | // 将图片数据保存到指定路径 50 | + (void)savePictureData:(NSData *)picData to:(NSString *)filePath 51 | { 52 | [picData writeToFile:filePath atomically:YES]; 53 | } 54 | 55 | // 获取指定url的图片 56 | + (UIImage *)pictureOfUrl:(NSString *)picUrl 57 | { 58 | return [UIImage imageWithContentsOfFile:PicPath(picUrl)]; 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /ExceedMVC/Core/FileManager/FileManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // FileManager.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FileManager : NSObject 12 | 13 | // 获取缓存文件目录 14 | + (NSString *)cachePathForFile; 15 | 16 | // 查看缓存占用空间(MB) 17 | + (float)cacheUsed; 18 | 19 | // 清理缓存 20 | + (void)clearAllCache; 21 | 22 | @end 23 | 24 | 25 | #ifdef DEBUG 26 | 27 | #define FMLOG(fmt,...) NSLog((@"FM->%s(%d):"fmt),__PRETTY_FUNCTION__,__LINE__,##__VA_ARGS__) 28 | 29 | #else 30 | 31 | #define FMLOG(fmt,...) NSLog(fmt,##__VA_ARGS__) 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /ExceedMVC/Core/FileManager/FileManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // FileManager.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "FileManager.h" 10 | #import "FileManager+Picture.h" 11 | 12 | @implementation FileManager 13 | 14 | // 获取缓存文件目录 15 | + (NSString *)cachePathForFile 16 | { 17 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); 18 | NSString *cachesDirectory = [paths objectAtIndex:0]; 19 | if (cachesDirectory.length > 0) { 20 | return cachesDirectory; 21 | } 22 | return [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Caches"]; 23 | } 24 | 25 | // 查看缓存占用空间(MB) 26 | + (float)cacheUsed 27 | { 28 | NSUInteger capacity = 0; 29 | //图片缓存目录 30 | capacity += [FileManager allCapacityInPath:[FileManager cachePathForPicture]]; 31 | // 32 | return capacity/(1024.0f*1024.0f); 33 | } 34 | 35 | // 清理缓存 36 | + (void)clearAllCache 37 | { 38 | //清理图片缓存 39 | [FileManager removeAllFilesInPath:[FileManager cachePathForPicture]]; 40 | } 41 | 42 | 43 | #pragma mark - Private 44 | 45 | // 统计指定目录下文件总大小 46 | + (NSUInteger)allCapacityInPath:(NSString *)path 47 | { 48 | NSUInteger capacity = 0.0f; 49 | // 50 | NSFileManager *fileManager = [NSFileManager defaultManager]; 51 | NSArray *arrFileName = [fileManager contentsOfDirectoryAtPath:path error:nil]; 52 | for (NSString *fileName in arrFileName) { 53 | NSString *filePath = [path stringByAppendingPathComponent:fileName]; 54 | NSDictionary *dicAttributes = [fileManager attributesOfItemAtPath:filePath error:nil]; 55 | capacity += [[dicAttributes objectForKey:NSFileSize] intValue]; 56 | } 57 | return capacity; 58 | } 59 | 60 | // 删除指定目录下的所有文件 61 | + (void)removeAllFilesInPath:(NSString *)path 62 | { 63 | NSFileManager *fileManager = [NSFileManager defaultManager]; 64 | NSArray *arrFileName = [fileManager contentsOfDirectoryAtPath:path error:nil]; 65 | for (NSString *fileName in arrFileName) { 66 | NSString *filePath = [path stringByAppendingPathComponent:fileName]; 67 | [fileManager removeItemAtPath:filePath error:nil]; 68 | } 69 | } 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /ExceedMVC/Core/Net/NetController.h: -------------------------------------------------------------------------------- 1 | // 2 | // NetController.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol NetControllerDelegate; 12 | 13 | @interface NetController : NSObject 14 | 15 | @property (nonatomic, weak) id delegate; 16 | 17 | + (instancetype)sharedInstance; 18 | 19 | // 登录 20 | - (void)loginWithUserName:(NSString *)userName 21 | andPassword:(NSString *)password; 22 | 23 | // 获取指定用户资料 24 | - (void)getUserInfoOf:(UInt64)userID; 25 | 26 | @end 27 | 28 | 29 | 30 | @protocol NetControllerDelegate 31 | 32 | @optional 33 | 34 | // 网络原因登录失败 35 | - (void)netController:(NetController *)netController loginError:(NSError *)error; 36 | 37 | // 登录返回数据 38 | - (void)netController:(NetController *)netController loginResult:(NSDictionary *)userInfo; 39 | 40 | // 网络原因用户资料获取失败 41 | - (void)netController:(NetController *)netController userInfoError:(NSError *)error of:(UInt64)userID; 42 | 43 | // 获取用户资料返回数据 44 | - (void)netController:(NetController *)netController userInfo:(NSDictionary *)userInfo of:(UInt64)userID; 45 | 46 | @end 47 | 48 | 49 | #ifdef DEBUG 50 | 51 | #define NETLOG(fmt,...) NSLog((@"NET->%s(%d):"fmt),__PRETTY_FUNCTION__,__LINE__,##__VA_ARGS__) 52 | 53 | #else 54 | 55 | #define NETLOG(fmt,...) NSLog(fmt,##__VA_ARGS__) 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /ExceedMVC/Core/Net/NetController.m: -------------------------------------------------------------------------------- 1 | // 2 | // NetController.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "NetController.h" 10 | #import "NBLHTTPManager.h" 11 | 12 | 13 | #ifdef DEBUG 14 | #define HOST_Interface @"http://192.168.1.110" 15 | #else 16 | #define HOST_Interface @"http://www.baidu.com" 17 | #endif 18 | 19 | #define UserInfoUrl HOST_Interface @"?uid=%lld" 20 | 21 | 22 | typedef NS_ENUM(NSUInteger, NetRequestType) { 23 | NetRequestType_None, 24 | NetRequestType_Login, 25 | NetRequestType_UserInfo, 26 | }; 27 | 28 | @interface NetController () 29 | 30 | @end 31 | 32 | 33 | @implementation NetController 34 | 35 | + (instancetype)sharedInstance 36 | { 37 | static NetController *sharedInstance = nil; 38 | static dispatch_once_t onceToken; 39 | dispatch_once(&onceToken, ^{ 40 | sharedInstance = [[NetController alloc] init]; 41 | }); 42 | 43 | return sharedInstance; 44 | } 45 | 46 | 47 | #pragma mark - Public 48 | 49 | // 登录 50 | - (void)loginWithUserName:(NSString *)userName 51 | andPassword:(NSString *)password 52 | { 53 | NSString *url = @"http://www.sina.com"; 54 | [[NBLHTTPManager sharedManager] requestObject:NBLResponseObjectType_String fromURL:url withParam:@{@"type": @(NetRequestType_Login)} andResult:^(NSHTTPURLResponse *httpResponse, id responseObject, NSError *error, NSDictionary *dicParam) { 55 | if (responseObject) { 56 | if ([self.delegate respondsToSelector:@selector(netController:loginResult:)]) { 57 | [self.delegate netController:self loginResult:responseObject]; 58 | } 59 | } 60 | else { 61 | if ([self.delegate respondsToSelector:@selector(netController:loginError:)]) { 62 | [self.delegate netController:self loginError:error]; 63 | } 64 | } 65 | }]; 66 | } 67 | 68 | // 获取指定用户资料 69 | - (void)getUserInfoOf:(UInt64)userID 70 | { 71 | NSString *url = [NSString stringWithFormat:UserInfoUrl, userID]; 72 | [[NBLHTTPManager sharedManager] requestObject:NBLResponseObjectType_String fromURL:url withParam:@{@"type": @(NetRequestType_UserInfo), @"userid": @(userID)} andResult:^(NSHTTPURLResponse *httpResponse, id responseObject, NSError *error, NSDictionary *dicParam) { 73 | if (responseObject) { 74 | if ([self.delegate respondsToSelector:@selector(netController:userInfo:of:)]) { 75 | UInt64 userID = [dicParam[@"userid"] longLongValue]; 76 | [self.delegate netController:self userInfo:responseObject of:userID]; 77 | } 78 | } 79 | else { 80 | if ([self.delegate respondsToSelector:@selector(netController:userInfoError:of:)]) { 81 | UInt64 userID = [dicParam[@"userid"] longLongValue]; 82 | [self.delegate netController:self userInfoError:error of:userID]; 83 | } 84 | } 85 | }]; 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /ExceedMVC/Core/Net/SimplifiedAFN+UIKit/UIImageView+NBL.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageView+NBL.h 3 | // testOurApps 4 | // 5 | // Created by CocoaChina_yangjh on 16/1/29. 6 | // 7 | // 8 | 9 | #import 10 | 11 | 12 | // 图片下载结束后的block回调 13 | // error只在图片下载失败时有效,表示下载失败时的错误 14 | typedef void (^UIImageViewDownloadImageResult) (UIImageView *imageView, NSString *picUrl, 15 | float progress, BOOL finished, NSError *error); 16 | 17 | 18 | @interface UIImageView (NBL) 19 | 20 | /** 21 | * @brief 清除UIImageView的缓存 22 | */ 23 | + (void)clearCacheOfUIImageView; 24 | 25 | /** 26 | * @brief 获取UIImageView的缓存路径 27 | * 28 | * @return UIImageView默认的缓存路径 29 | */ 30 | + (NSString *)cachePathOfUIImageView; 31 | 32 | /** 33 | * @brief 设置图片路径和网址(不全为空) 34 | * 35 | * @param filePath 缓存图片保存路径 36 | * @param picUrl 图片下载地址 37 | */ 38 | - (void)loadImageFromCachePath:(NSString *)filePath orPicUrl:(NSString *)picUrl; 39 | 40 | /** 41 | * @brief 设置图片路径和网址(不全为空) 42 | * 43 | * @param filePath 缓存图片保存路径 44 | * @param picUrl 图片下载地址 45 | * @param result 图片下载结束后的block回调 46 | */ 47 | - (void)loadImageFromCachePath:(NSString *)filePath orPicUrl:(NSString *)picUrl 48 | withDownloadResult:(UIImageViewDownloadImageResult)downloadResult; 49 | 50 | /** 51 | * @brief 取消下载图片 52 | */ 53 | - (void)cancelDownload; 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /ExceedMVC/Core/Net/SimplifiedAFN+UIKit/UIImageView+NBL.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageView+NBL.m 3 | // testOurApps 4 | // 5 | // Created by CocoaChina_yangjh on 16/1/29. 6 | // 7 | // 8 | 9 | #import "UIImageView+NBL.h" 10 | #import "NBLHTTPFileManager.h" 11 | 12 | 13 | #pragma mark - UIImageViewManager 14 | 15 | @interface UIImageViewManager : NSObject { 16 | NSMutableDictionary *_mdicURLKey; 17 | } 18 | @end 19 | 20 | @implementation UIImageViewManager 21 | 22 | - (id)init 23 | { 24 | self = [super init]; 25 | if (self) { 26 | _mdicURLKey = [[NSMutableDictionary alloc] init]; 27 | } 28 | return self; 29 | } 30 | 31 | + (UIImageViewManager *)sharedInstance 32 | { 33 | static dispatch_once_t onceToken; 34 | static UIImageViewManager *sSharedInstance; 35 | 36 | dispatch_once(&onceToken, ^{ 37 | sSharedInstance = [[UIImageViewManager alloc] init]; 38 | }); 39 | 40 | return sSharedInstance; 41 | } 42 | 43 | - (void)downloadFile:(NSString *)filePath from:(NSString *)url showOn:(UIImageView *)imageView 44 | withResult:(UIImageViewDownloadImageResult)downloadResult 45 | { 46 | // 取出url相应的任务项 47 | NSMutableDictionary *mdicURLItem = _mdicURLKey[url]; 48 | if (nil == mdicURLItem) { 49 | mdicURLItem = [NSMutableDictionary dictionary]; 50 | _mdicURLKey[url] = mdicURLItem; 51 | } 52 | // 从任务项中取出任务列表 53 | NSMutableArray *marrItem = mdicURLItem[@"list"]; 54 | if (nil == marrItem) { 55 | marrItem = [NSMutableArray array]; 56 | mdicURLItem[@"list"] = marrItem; 57 | } 58 | // 加入下载任务 59 | if (downloadResult) { 60 | UIImageViewDownloadImageResult result = [downloadResult copy]; 61 | [marrItem addObject:@{@"view": imageView, @"path": filePath, @"block": result}]; 62 | } 63 | else { 64 | [marrItem addObject:@{@"view": imageView, @"path": filePath}]; 65 | } 66 | // 第一次请求该url时才会下载 67 | if (marrItem.count == 1) { 68 | [[NBLHTTPFileManager sharedManager] downloadFile:filePath from:url withParam:@{@"url": url} progress:^(int64_t bytesReceived, int64_t totalBytes, NSDictionary *dicParam) { 69 | // 找到当前url对应的所有任务项 70 | NSString *url = dicParam[@"url"]; 71 | NSArray *arrItem = _mdicURLKey[url][@"list"]; 72 | // 遍历该任务项列表 73 | for (NSDictionary *dicItem in arrItem) { 74 | // 开始下载的回调 75 | UIImageViewDownloadImageResult downloadResult = dicItem[@"block"]; 76 | if (downloadResult) { 77 | downloadResult(dicItem[@"view"], url, 1.0f*bytesReceived/totalBytes, NO, nil); 78 | } 79 | } 80 | } andResult:^(NSString *filePath, NSHTTPURLResponse *httpResponse, NSError *error, NSDictionary *dicParam) { 81 | // 找到当前url对应的所有任务项 82 | NSString *url = dicParam[@"url"]; 83 | NSArray *arrItem = _mdicURLKey[url][@"list"]; 84 | // 加载下载到的图片 85 | UIImage *image = [UIImage imageWithContentsOfFile:filePath]; 86 | if (image) { 87 | // 遍历该任务项列表 88 | for (NSDictionary *dicItem in arrItem) { 89 | // 设置图片 90 | UIImageView *imageView = dicItem[@"view"]; 91 | imageView.image = image; 92 | // 开始下载的回调 93 | UIImageViewDownloadImageResult downloadResult = dicItem[@"block"]; 94 | if (downloadResult) { 95 | downloadResult(imageView, url, 1.0f, YES, nil); 96 | } 97 | } 98 | } 99 | else { 100 | NSError *error = [NSError errorWithDomain:@"NBLError" code:NSURLErrorCannotDecodeContentData userInfo:@{NSLocalizedDescriptionKey: @"图片数据错误"}]; 101 | // 遍历该任务项列表 102 | for (NSDictionary *dicItem in arrItem) { 103 | // 开始下载的回调 104 | UIImageViewDownloadImageResult downloadResult = dicItem[@"block"]; 105 | if (downloadResult) { 106 | downloadResult(dicItem[@"view"], url, 1.0f, YES, error); 107 | } 108 | } 109 | } 110 | }]; 111 | } 112 | } 113 | 114 | - (void)cancelDownload:(UIImageView *)imageView 115 | { 116 | NSArray *arrKey = [_mdicURLKey allKeys]; 117 | // 遍历所有url 118 | for (NSString *strKey in arrKey) { 119 | NSMutableDictionary *mdicURLItem = _mdicURLKey[strKey]; 120 | NSMutableArray *marrItem = mdicURLItem[@"list"]; 121 | // 遍历任务列表 122 | for (int i = 0; i < marrItem.count; i++) { 123 | NSDictionary *dicItem = marrItem[i]; 124 | // 找到需要取消的UIImageView 125 | if (dicItem[@"view"] == imageView) { 126 | [marrItem removeObjectAtIndex:i]; 127 | // 只有这一个下载则要取消下载任务 128 | if (0 == marrItem.count) { 129 | [[NBLHTTPFileManager sharedManager] cancelDownloadFileFrom:strKey]; 130 | [_mdicURLKey removeObjectForKey:strKey]; 131 | } 132 | break; 133 | } 134 | } 135 | } 136 | } 137 | 138 | @end 139 | 140 | 141 | #pragma mark - UIImageView (NBL) 142 | 143 | #define CachePath_UIImageView [NSHomeDirectory() stringByAppendingPathComponent:@"Library/UIImageView"] 144 | 145 | @implementation UIImageView (NBL) 146 | 147 | /** 148 | * @brief 清除UIImageView的缓存 149 | */ 150 | + (void)clearCacheOfUIImageView 151 | { 152 | NSString *cachePath = CachePath_UIImageView; 153 | // 删除缓存目录下的所有文件 154 | NSFileManager *fileManager = [NSFileManager defaultManager]; 155 | NSArray *array = [fileManager subpathsAtPath:cachePath]; 156 | for (NSString *fileName in array) { 157 | [fileManager removeItemAtPath:[cachePath stringByAppendingPathComponent:fileName] error:nil]; 158 | } 159 | } 160 | 161 | /** 162 | * @brief 获取UIImageView的缓存路径 163 | * 164 | * @return UIImageView默认的缓存路径 165 | */ 166 | + (NSString *)cachePathOfUIImageView 167 | { 168 | return CachePath_UIImageView; 169 | } 170 | 171 | /** 172 | * @brief 设置图片路径和网址(不全为空) 173 | * 174 | * @param filePath 缓存图片保存路径 175 | * @param picUrl 图片下载地址 176 | */ 177 | - (void)loadImageFromCachePath:(NSString *)filePath orPicUrl:(NSString *)picUrl 178 | { 179 | [self loadImageFromCachePath:filePath orPicUrl:picUrl withDownloadResult:nil]; 180 | } 181 | 182 | /** 183 | * @brief 设置图片路径和网址(不全为空) 184 | * 185 | * @param filePath 缓存图片保存路径 186 | * @param picUrl 图片下载地址 187 | * @param result 图片下载结束后的block回调 188 | */ 189 | - (void)loadImageFromCachePath:(NSString *)filePath orPicUrl:(NSString *)picUrl 190 | withDownloadResult:(UIImageViewDownloadImageResult)downloadResult 191 | { 192 | // 无路径则使用默认路径 193 | if (filePath.length == 0) { 194 | NSString *cachePath = CachePath_UIImageView; 195 | NSFileManager *fileManager = [NSFileManager defaultManager]; 196 | if (![fileManager fileExistsAtPath:cachePath]) { 197 | [fileManager createDirectoryAtPath:cachePath withIntermediateDirectories:YES 198 | attributes:nil error:nil]; 199 | } 200 | NSString *fileName = transferFileNameFromURL(picUrl); 201 | filePath = [cachePath stringByAppendingPathComponent:fileName]; 202 | } 203 | // 读缓存图片 204 | UIImage *imageCache = [UIImage imageWithContentsOfFile:filePath]; 205 | // 读取缓存成功 206 | if (imageCache) { 207 | self.image = imageCache; 208 | } 209 | // 缓存图片没读取到,且url存在,则下载 210 | else if (picUrl) { 211 | [[UIImageViewManager sharedInstance] downloadFile:filePath from:picUrl showOn:self 212 | withResult:downloadResult]; 213 | } 214 | } 215 | 216 | /** 217 | * @brief 取消下载图片 218 | */ 219 | - (void)cancelDownload 220 | { 221 | [[UIImageViewManager sharedInstance] cancelDownload:self]; 222 | } 223 | 224 | @end 225 | -------------------------------------------------------------------------------- /ExceedMVC/Core/Net/SimplifiedAFN/NBLHTTPFileManager.h: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2015-2016 NBL ( https://github.com/yjh4866/SimplifiedAFN ) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #import 24 | 25 | // 将url转换为文件名 26 | NSString *transferFileNameFromURL(NSString *url); 27 | 28 | /** 29 | * HTTP文件下载进度 30 | * 31 | * @param bytesReceived 已接收到的数据长度 32 | * @param totalBytes 数据总长度。-1表示长度未知 33 | * @param dicParam 回传对象 34 | */ 35 | typedef void (^NBLHTTPFileProgress)(int64_t bytesReceived, int64_t totalBytes, 36 | NSDictionary *dicParam); 37 | /** 38 | * HTTP文件下载结果 39 | * 40 | * @param filePath 文件保存路径 41 | * @param httpResponse HTTP响应对象NSHTTPURLResponse 42 | * @param error 发生的错误。nil表示成功 43 | * @param dicParam 回传对象 44 | */ 45 | typedef void (^NBLHTTPFileResult)(NSString *filePath, NSHTTPURLResponse *httpResponse, 46 | NSError *error, NSDictionary *dicParam); 47 | 48 | 49 | /* 保留[[NBLHTTPFileManager alloc] init]的实例化方案。 50 | 可以使用默认的单例请求数据,也可以另外实例化以与默认的单例对象区分开 51 | */ 52 | @interface NBLHTTPFileManager : NSObject 53 | 54 | // 通用对象 55 | + (NBLHTTPFileManager *)sharedManager; 56 | 57 | // 指定url的下载任务是否存在 58 | - (BOOL)downloadTaskIsExist:(NSString *)url; 59 | 60 | // 下载文件到指定路径 61 | // url相同则认为是同一下载任务 62 | - (BOOL)downloadFile:(NSString *)filePath from:(NSString *)url withParam:(NSDictionary *)dicParam 63 | progress:(NBLHTTPFileProgress)progress andResult:(NBLHTTPFileResult)result; 64 | 65 | // 取消下载 66 | - (void)cancelDownloadFileFrom:(NSString *)url; 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /ExceedMVC/Core/Net/SimplifiedAFN/NBLHTTPFileManager.m: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2015-2016 NBL ( https://github.com/yjh4866/SimplifiedAFN ) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #import "NBLHTTPFileManager.h" 24 | #import 25 | #import "NBLHTTPManager.h" 26 | 27 | 28 | #define FilePath(url) [NSTemporaryDirectory() stringByAppendingPathComponent:transferFileNameFromURL(url)] 29 | #define FilePath_Temp(filePath) [filePath stringByAppendingPathExtension:NSClassFromString(@"NSURLSession")?@"NBLNewTempFile":@"NBLTempFile"] 30 | 31 | 32 | // 将url转换为文件名 33 | NSString *transferFileNameFromURL(NSString *url) 34 | { 35 | if (url.length > 0) { 36 | // 将url字符MD5处理 37 | const char *cStr = [url UTF8String]; 38 | unsigned char result[16]; 39 | CC_MD5(cStr, (CC_LONG)strlen(cStr), result); 40 | NSString *fileName = [NSString stringWithFormat: 41 | @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 42 | result[0], result[1], result[2], result[3], 43 | result[4], result[5], result[6], result[7], 44 | result[8], result[9], result[10], result[11], 45 | result[12], result[13], result[14], result[15]]; 46 | // 加上后缀名 47 | NSString *pathExtension = [[NSURL URLWithString:url] pathExtension]; 48 | if (pathExtension.length > 0) { 49 | fileName = [fileName stringByAppendingPathExtension:pathExtension]; 50 | } 51 | return fileName; 52 | } 53 | return @""; 54 | } 55 | 56 | 57 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 58 | 59 | #pragma mark - 60 | #pragma mark - NSURLConnection方案 61 | #pragma mark - 62 | 63 | typedef NS_ENUM(unsigned int, NBLHTTPFileTaskStatus) { 64 | NBLHTTPFileTaskStatus_Canceling = 1, 65 | NBLHTTPFileTaskStatus_Waiting, 66 | NBLHTTPFileTaskStatus_GetFileSize, 67 | NBLHTTPFileTaskStatus_GetFileData, 68 | NBLHTTPFileTaskStatus_Finished, 69 | }; 70 | 71 | static inline NSString * KeyPathFromHTTPFileTaskStatus(NBLHTTPFileTaskStatus state) { 72 | switch (state) { 73 | case NBLHTTPFileTaskStatus_Canceling: 74 | return @"isCanceling"; 75 | case NBLHTTPFileTaskStatus_Waiting: 76 | return @"isWaiting"; 77 | case NBLHTTPFileTaskStatus_GetFileSize: 78 | return @"isGettingFileSize"; 79 | case NBLHTTPFileTaskStatus_GetFileData: 80 | return @"isGettingFileData"; 81 | case NBLHTTPFileTaskStatus_Finished: 82 | return @"isFinished"; 83 | default: { 84 | #pragma clang diagnostic push 85 | #pragma clang diagnostic ignored "-Wunreachable-code" 86 | return @"state"; 87 | #pragma clang diagnostic pop 88 | } 89 | } 90 | } 91 | 92 | 93 | static dispatch_queue_t httpfile_operation_headcompletion_queue() { 94 | static dispatch_queue_t httpfile_operation_headcompletion_queue; 95 | static dispatch_once_t onceToken; 96 | dispatch_once(&onceToken, ^{ 97 | httpfile_operation_headcompletion_queue = dispatch_queue_create("com.yjh4866.httpfile.completion.head.queue", DISPATCH_QUEUE_CONCURRENT); 98 | }); 99 | return httpfile_operation_headcompletion_queue; 100 | } 101 | static dispatch_queue_t httpfile_operation_completion_queue() { 102 | static dispatch_queue_t httpfile_operation_completion_queue; 103 | static dispatch_once_t onceToken; 104 | dispatch_once(&onceToken, ^{ 105 | httpfile_operation_completion_queue = dispatch_queue_create("com.yjh4866.httpfile.completion.queue", DISPATCH_QUEUE_CONCURRENT); 106 | }); 107 | return httpfile_operation_completion_queue; 108 | } 109 | 110 | 111 | #pragma mark - NBLHTTPManager (NBLHTTPFileManager) 112 | 113 | @interface NBLHTTPManager (Private) 114 | // 根据NSURLRequest获取Web数据(dicParam不为空则以此为去重依据,否则以url为去重依据) 115 | // dicParam 可用于回传数据,需要取消时不可为nil 116 | - (BOOL)requestObject:(NBLResponseObjectType)resObjType 117 | withRequest:(NSURLRequest *)request param:(NSDictionary *)dicParam 118 | progress:(NBLHTTPProgress)progress andResult:(NBLHTTPResult)result 119 | onCompletionQueue:(dispatch_queue_t)completionQueue; 120 | @end 121 | @implementation NBLHTTPManager (NBLHTTPFileManager) 122 | // NBLHTTPFileManager的专用单例 123 | + (NBLHTTPManager *)sharedManagerForHTTPFileManger 124 | { 125 | static NBLHTTPManager *sharedManagerForHTTPFileManger = nil; 126 | static dispatch_once_t onceToken; 127 | dispatch_once(&onceToken, ^{ 128 | sharedManagerForHTTPFileManger = [[NBLHTTPManager alloc] init]; 129 | }); 130 | return sharedManagerForHTTPFileManger; 131 | } 132 | @end 133 | 134 | 135 | typedef void (^Block_Void)(); 136 | 137 | #pragma mark - NBLHTTPFileTaskOperation 138 | 139 | @interface NBLHTTPFileTaskOperation : NSOperation 140 | @property (readwrite, nonatomic, strong) NSRecursiveLock *lock; 141 | @property (nonatomic, strong) NSString *filePath; 142 | @property (nonatomic, strong) NSString *url; 143 | @property (nonatomic, strong) NSHTTPURLResponse *httpResponse; 144 | @property (nonatomic, strong) NSDictionary *param; 145 | @property (nonatomic, assign) int64_t bytesReceived; 146 | @property (nonatomic, assign) int64_t totalBytes; 147 | @property (nonatomic, strong) NSMutableDictionary *mdicSubTaskInfo; 148 | @property (nonatomic, assign) int countOfExecuteSubTask; 149 | @property (nonatomic, assign) int errCountOfSubTask; 150 | @property (nonatomic, copy) Block_Void executeSubTask; 151 | @property (nonatomic, assign) NBLHTTPFileTaskStatus taskStatus; 152 | @property (nonatomic, copy) NBLHTTPFileProgress progress; 153 | @property (nonatomic, copy) NBLHTTPFileResult result; 154 | - (instancetype)init NS_UNAVAILABLE; 155 | @end 156 | 157 | #pragma mark Implementation NBLHTTPFileTaskOperation 158 | 159 | @implementation NBLHTTPFileTaskOperation 160 | 161 | + (void)networkRequestThreadEntryPoint:(id)__unused object { 162 | @autoreleasepool { 163 | [[NSThread currentThread] setName:@"yjh4866.NBLHTTPFileTaskOperation"]; 164 | 165 | NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 166 | [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; 167 | [runLoop run]; 168 | } 169 | } 170 | 171 | + (NSThread *)networkRequestThread { 172 | static NSThread *_networkRequestThread = nil; 173 | static dispatch_once_t oncePredicate; 174 | dispatch_once(&oncePredicate, ^{ 175 | _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; 176 | [_networkRequestThread start]; 177 | }); 178 | 179 | return _networkRequestThread; 180 | } 181 | 182 | - (instancetype)initWithFilePath:(NSString *)filePath andUrl:(NSString *)url 183 | { 184 | self = [super init]; 185 | if (self) { 186 | self.filePath = filePath; 187 | self.url = url; 188 | _taskStatus = NBLHTTPFileTaskStatus_Waiting; 189 | self.countOfExecuteSubTask = 0; 190 | 191 | self.lock = [[NSRecursiveLock alloc] init]; 192 | self.lock.name = @"com.yjh4866.NBLHTTPFileTaskOperation.lock"; 193 | 194 | __weak typeof(self) weakSelf = self; 195 | self.executeSubTask = ^() { 196 | [weakSelf.lock lock]; 197 | // 一共下载两次,即下载失败可以再试一次 198 | if (weakSelf.countOfExecuteSubTask < 2) { 199 | // 启动线程以执行子任务 200 | [weakSelf performSelector:@selector(operationDidStart) onThread:[NBLHTTPFileTaskOperation networkRequestThread] withObject:nil waitUntilDone:NO modes:@[NSRunLoopCommonModes]]; 201 | } 202 | // 下载失败 203 | else { 204 | // 文件下载任务结束 205 | weakSelf.taskStatus = NBLHTTPFileTaskStatus_Finished; 206 | // GCD异步通过dispatch_get_main_queue回调 207 | __strong typeof(weakSelf) strongSelf = weakSelf; 208 | dispatch_async(dispatch_get_main_queue(), ^{ 209 | if (strongSelf.result) { 210 | NSError *error = [NSError errorWithDomain:@"NBLHTTPFileManager" code:NSURLErrorTimedOut userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"未下载完成"]}]; 211 | strongSelf.result(nil, strongSelf.httpResponse, error, strongSelf.param); 212 | } 213 | }); 214 | } 215 | [weakSelf.lock unlock]; 216 | }; 217 | } 218 | return self; 219 | } 220 | 221 | - (void)dealloc 222 | { 223 | 224 | } 225 | 226 | - (void)setTaskStatus:(NBLHTTPFileTaskStatus)taskStatus { 227 | [self.lock lock]; 228 | NSString *oldStateKey = KeyPathFromHTTPFileTaskStatus(self.taskStatus); 229 | NSString *newStateKey = KeyPathFromHTTPFileTaskStatus(taskStatus); 230 | 231 | // 下面这四行KVO代码很重要,用以通知Operation任务状态变更 232 | [self willChangeValueForKey:newStateKey]; 233 | [self willChangeValueForKey:oldStateKey]; 234 | _taskStatus = taskStatus; 235 | [self didChangeValueForKey:oldStateKey]; 236 | [self didChangeValueForKey:newStateKey]; 237 | [self.lock unlock]; 238 | } 239 | 240 | - (BOOL)isCancelled 241 | { 242 | return NBLHTTPFileTaskStatus_Canceling == self.taskStatus; 243 | } 244 | - (BOOL)isExecuting 245 | { 246 | return ((NBLHTTPFileTaskStatus_GetFileSize == self.taskStatus) || 247 | (NBLHTTPFileTaskStatus_GetFileData == self.taskStatus)); 248 | } 249 | - (BOOL)isFinished 250 | { 251 | return NBLHTTPFileTaskStatus_Finished == self.taskStatus; 252 | } 253 | 254 | - (void)start 255 | { 256 | [self.lock lock]; 257 | [super start]; 258 | BOOL needHeadRequest = YES; 259 | // 如果临时文件存在,则从中提取任务信息并添加到下载队列 260 | NSString *filePathTemp = FilePath_Temp(self.filePath); 261 | if ([[NSFileManager defaultManager] fileExistsAtPath:filePathTemp]) { 262 | // 先获取实际文件大小(实际文件大小+配置数据+8字节的实际文件大小) 263 | int64_t fileSize = 0; 264 | NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePathTemp]; 265 | int64_t tempFileSize = [fileHandle seekToEndOfFile]; 266 | [fileHandle seekToFileOffset:tempFileSize-8]; 267 | NSData *dataFileSize = [fileHandle readDataOfLength:8]; 268 | [dataFileSize getBytes:&fileSize length:8]; 269 | self.totalBytes = fileSize; 270 | // 再获取任务信息数据 271 | [fileHandle seekToFileOffset:fileSize]; 272 | NSData *dataTaskInfo = [fileHandle readDataOfLength:(NSUInteger)(tempFileSize-fileSize-8)]; 273 | [fileHandle closeFile]; 274 | // 解析成字典,即为子任务字典 275 | self.mdicSubTaskInfo = [NSJSONSerialization JSONObjectWithData:dataTaskInfo options:NSJSONReadingMutableContainers error:nil]; 276 | // 任务信息数据为字典 277 | if (self.mdicSubTaskInfo && [self.mdicSubTaskInfo isKindOfClass:NSDictionary.class]) { 278 | needHeadRequest = NO; 279 | // 计算当前进度 280 | self.bytesReceived = self.totalBytes; 281 | for (NSDictionary *dicSubTask in self.mdicSubTaskInfo.allValues) { 282 | self.bytesReceived -= [dicSubTask[@"Len"] intValue]; 283 | } 284 | // 告知当前进度 285 | int64_t bytesReceived = self.bytesReceived; 286 | dispatch_async(dispatch_get_main_queue(), ^{ 287 | if (self.progress) { 288 | self.progress(bytesReceived, self.totalBytes, self.param); 289 | } 290 | }); 291 | // 执行子任务 292 | self.taskStatus = NBLHTTPFileTaskStatus_GetFileData; 293 | self.executeSubTask(); 294 | } 295 | else { 296 | self.mdicSubTaskInfo = nil; 297 | [[NSFileManager defaultManager] removeItemAtPath:filePathTemp error:nil]; 298 | } 299 | } 300 | // 先获取文件大小 301 | if (needHeadRequest) { 302 | self.taskStatus = NBLHTTPFileTaskStatus_GetFileSize; 303 | // 创建URLRequest 304 | NSMutableURLRequest *mURLRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]]; 305 | [mURLRequest setHTTPMethod:@"HEAD"]; 306 | [mURLRequest setCachePolicy:NSURLRequestReloadIgnoringCacheData]; 307 | [mURLRequest setValue:@"" forHTTPHeaderField:@"Accept-Encoding"]; 308 | // 获取文件大小 309 | __weak typeof(self) weakSelf = self; 310 | [[NBLHTTPManager sharedManagerForHTTPFileManger] requestObject:NBLResponseObjectType_Data withRequest:mURLRequest param:@{@"Type": @"HEAD", @"url": self.url} progress:nil andResult:^(NSHTTPURLResponse *httpResponse, id responseObject, NSError *error, NSDictionary *dicParam) { 311 | weakSelf.httpResponse = httpResponse; 312 | // 存在错误,或数据长度过短,则结束 313 | if (error || httpResponse.expectedContentLength < 1) { 314 | // 文件下载任务结束 315 | weakSelf.taskStatus = NBLHTTPFileTaskStatus_Finished; 316 | dispatch_async(dispatch_get_main_queue(), ^{ 317 | if (weakSelf.result) { 318 | weakSelf.result(weakSelf.filePath, weakSelf.httpResponse, error, weakSelf.param); 319 | } 320 | }); 321 | } 322 | else { 323 | weakSelf.totalBytes = httpResponse.expectedContentLength; 324 | // 通过下载进度提示总大小 325 | dispatch_async(dispatch_get_main_queue(), ^{ 326 | if (weakSelf.progress) { 327 | weakSelf.progress(0, weakSelf.totalBytes, weakSelf.param); 328 | } 329 | }); 330 | // 生成子任务列表 331 | int64_t fileSize = weakSelf.httpResponse.expectedContentLength; 332 | const int sizePartFile = 256*1024; 333 | weakSelf.mdicSubTaskInfo = [NSMutableDictionary dictionary]; 334 | unsigned int subTaskCount = ceilf(1.0f*fileSize/sizePartFile); // 每一个任务项大小 335 | unsigned int subTaskLen = ceilf(1.0f*fileSize/subTaskCount); 336 | for (int i = 0; i < subTaskCount-1; i++) { 337 | [weakSelf.mdicSubTaskInfo setObject:[NSMutableDictionary dictionaryWithDictionary:@{@"Len": @(subTaskLen)}] forKey:[NSString stringWithFormat:@"%@", @(i*subTaskLen)]]; 338 | } 339 | unsigned int startLast = (subTaskCount-1)*subTaskLen; 340 | [weakSelf.mdicSubTaskInfo setValue:[NSMutableDictionary dictionaryWithDictionary:@{@"Len": @(fileSize-startLast)}] forKey:[NSString stringWithFormat:@"%@", @(startLast)]]; 341 | 342 | // 生成临时文件 343 | NSString *filePathTemp = FilePath_Temp(weakSelf.filePath); 344 | [[NSFileManager defaultManager] createFileAtPath:filePathTemp contents:nil attributes:nil]; 345 | // 保存临时文件数据 346 | NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePathTemp]; 347 | // 跳过实际文件数据区,保存任务信息 348 | [fileHandle seekToFileOffset:fileSize]; 349 | NSData *dataTaskInfo = [NSJSONSerialization dataWithJSONObject:weakSelf.mdicSubTaskInfo options:NSJSONWritingPrettyPrinted error:nil]; 350 | [fileHandle writeData:dataTaskInfo]; 351 | // 保存文件大小 352 | [fileHandle seekToFileOffset:fileSize+dataTaskInfo.length]; 353 | [fileHandle writeData:[NSData dataWithBytes:&fileSize length:8]]; 354 | // 掐掉可能多余的数据 355 | [fileHandle truncateFileAtOffset:fileSize+dataTaskInfo.length+8]; 356 | [fileHandle closeFile]; 357 | 358 | // 执行子任务 359 | weakSelf.executeSubTask(); 360 | } 361 | } onCompletionQueue:httpfile_operation_headcompletion_queue()]; 362 | } 363 | [self.lock unlock]; 364 | } 365 | 366 | - (void)operationDidStart { 367 | [self.lock lock]; 368 | 369 | self.countOfExecuteSubTask += 1; 370 | self.errCountOfSubTask = 0; 371 | self.bytesReceived = self.totalBytes; 372 | // 遍历子任务并启动下载 373 | for (NSString *strKey in self.mdicSubTaskInfo.allKeys) { 374 | NSMutableDictionary *mdicSubTask = self.mdicSubTaskInfo[strKey]; 375 | self.bytesReceived -= [mdicSubTask[@"Len"] intValue]; 376 | // 创建URLRequest 377 | NSMutableURLRequest *mURLRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]]; 378 | [mURLRequest setValue:[NSString stringWithFormat:@"bytes=%@-%@", strKey, @([strKey intValue]+[mdicSubTask[@"Len"] intValue]-1)] forHTTPHeaderField:@"RANGE"]; 379 | [mURLRequest setCachePolicy:NSURLRequestReloadIgnoringCacheData]; 380 | [mURLRequest setValue:@"" forHTTPHeaderField:@"Accept-Encoding"]; 381 | // 下载文件段 382 | __weak typeof(self) weakSelf = self; 383 | [[NBLHTTPManager sharedManagerForHTTPFileManger] requestObject:NBLResponseObjectType_Data withRequest:mURLRequest param:@{@"url": self.url, @"Start": strKey, @"Len": mdicSubTask[@"Len"]} progress:nil andResult:^(NSHTTPURLResponse *httpResponse, id responseObject, NSError *error, NSDictionary *dicParam) { 384 | [weakSelf.lock lock]; 385 | // 下载成功 386 | if (nil == error) { 387 | // 将下载到的数据保存到临时文件 388 | NSString *filePathTemp = FilePath_Temp(weakSelf.filePath); 389 | NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePathTemp]; 390 | [fileHandle seekToFileOffset:[strKey intValue]]; 391 | [fileHandle writeData:responseObject]; 392 | // 删除该子任务 393 | [weakSelf.mdicSubTaskInfo removeObjectForKey:dicParam[@"Start"]]; 394 | // 还存在未完成的子任务,更新任务进度 395 | if (weakSelf.mdicSubTaskInfo.count > 0) { 396 | weakSelf.bytesReceived += [dicParam[@"Len"] intValue]; 397 | // 将任务进度更新到文件 398 | [fileHandle seekToFileOffset:weakSelf.totalBytes]; 399 | NSData *dataTaskInfo = [NSJSONSerialization dataWithJSONObject:weakSelf.mdicSubTaskInfo options:NSJSONWritingPrettyPrinted error:nil]; 400 | [fileHandle writeData:dataTaskInfo]; 401 | // 保存文件大小 402 | [fileHandle seekToFileOffset:weakSelf.totalBytes+dataTaskInfo.length]; 403 | int64_t fileSize = weakSelf.totalBytes; 404 | [fileHandle writeData:[NSData dataWithBytes:&fileSize length:8]]; 405 | // 掐掉可能多余的数据 406 | [fileHandle truncateFileAtOffset:fileSize+dataTaskInfo.length+8]; 407 | [fileHandle closeFile]; 408 | // 文件下载进度变更 409 | int64_t bytesReceived = weakSelf.bytesReceived; 410 | dispatch_async(dispatch_get_main_queue(), ^{ 411 | if (weakSelf.progress) { 412 | weakSelf.progress(bytesReceived, weakSelf.totalBytes, weakSelf.param); 413 | } 414 | }); 415 | // 错误的子任务数量,与剩余子任务数量相同,则所有子任务均已完成,再次执行子任务 416 | if (weakSelf.mdicSubTaskInfo.count == weakSelf.errCountOfSubTask) { 417 | weakSelf.executeSubTask(); 418 | } 419 | } 420 | else { 421 | // 掐掉下载进度相关数据 422 | [fileHandle truncateFileAtOffset:weakSelf.totalBytes]; 423 | [fileHandle closeFile]; 424 | // 将临时文件修改为正式文件 425 | [[NSFileManager defaultManager] removeItemAtPath:weakSelf.filePath error:nil]; 426 | [[NSFileManager defaultManager] moveItemAtPath:filePathTemp toPath:weakSelf.filePath error:nil]; 427 | // 文件下载任务结束 428 | weakSelf.taskStatus = NBLHTTPFileTaskStatus_Finished; 429 | dispatch_async(dispatch_get_main_queue(), ^{ 430 | if (weakSelf.result) { 431 | weakSelf.result(weakSelf.filePath, weakSelf.httpResponse, nil, weakSelf.param); 432 | } 433 | }); 434 | } 435 | } 436 | // 下载失败 437 | else { 438 | weakSelf.errCountOfSubTask += 1; 439 | // 错误的子任务数量,与剩余子任务数量相同,则所有子任务均已完成 440 | if (weakSelf.mdicSubTaskInfo.count == weakSelf.errCountOfSubTask) { 441 | weakSelf.executeSubTask(); 442 | } 443 | } 444 | [weakSelf.lock unlock]; 445 | } onCompletionQueue:httpfile_operation_completion_queue()]; 446 | } 447 | [self.lock unlock]; 448 | } 449 | 450 | @end 451 | 452 | #endif 453 | 454 | 455 | #pragma mark - 456 | #pragma mark - NSURLSessionDownloadTask方案 457 | #pragma mark - 458 | 459 | static dispatch_queue_t urlsession_creation_queue() { 460 | static dispatch_queue_t urlsession_creation_queue; 461 | static dispatch_once_t onceToken; 462 | dispatch_once(&onceToken, ^{ 463 | urlsession_creation_queue = dispatch_queue_create("com.yjh4866.urlsession.httpfile.creation.queue", DISPATCH_QUEUE_SERIAL); 464 | }); 465 | 466 | return urlsession_creation_queue; 467 | } 468 | 469 | static dispatch_group_t urlsession_completion_group() { 470 | static dispatch_group_t urlsession_completion_group; 471 | static dispatch_once_t onceToken; 472 | dispatch_once(&onceToken, ^{ 473 | urlsession_completion_group = dispatch_group_create(); 474 | }); 475 | 476 | return urlsession_completion_group; 477 | } 478 | 479 | 480 | #pragma mark URLSessionDownloadTaskItem 481 | 482 | @interface URLSessionDownloadTaskItem : NSObject 483 | @property (nonatomic, strong) NSURLSessionDownloadTask *urlSessionTask; 484 | @property (nonatomic, strong) NSString *filePath; 485 | @property (nonatomic, strong) NSString *url; 486 | @property (nonatomic, strong) NSHTTPURLResponse *httpResponse; 487 | @property (nonatomic, copy) NBLHTTPFileProgress progress; 488 | @property (nonatomic, copy) NBLHTTPFileResult result; 489 | @property (nonatomic, strong) NSDictionary *param; 490 | @end 491 | @implementation URLSessionDownloadTaskItem 492 | @end 493 | 494 | 495 | #pragma mark - NBLHTTPFileManager 496 | 497 | @interface NBLHTTPFileManager () 498 | @property (nonatomic, strong) NSOperationQueue *operationQueue; 499 | @property (readwrite, nonatomic, strong) NSURLSession *urlSession; 500 | @property (readwrite, nonatomic, strong) NSMutableDictionary *mdicTaskItemForTaskIdentifier; 501 | @property (readwrite, nonatomic, strong) NSLock *lock; 502 | @end 503 | 504 | #pragma mark Implementation NBLHTTPFileManager 505 | 506 | @implementation NBLHTTPFileManager 507 | 508 | - (instancetype)init 509 | { 510 | self = [super init]; 511 | if (self) { 512 | self.operationQueue = [[NSOperationQueue alloc] init]; 513 | // NSURLSession存在,即系统版本为7.0及以上,则采用NSURLSession来下载文件 514 | if (NSClassFromString(@"NSURLSession")) { 515 | self.operationQueue.maxConcurrentOperationCount = 1; 516 | NSURLSessionConfiguration *urlSessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; 517 | urlSessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringCacheData; 518 | self.urlSession = [NSURLSession sessionWithConfiguration:urlSessionConfig delegate:self delegateQueue:self.operationQueue]; 519 | self.mdicTaskItemForTaskIdentifier = [[NSMutableDictionary alloc] init]; 520 | self.lock = [[NSLock alloc] init]; 521 | self.lock.name = @"com.yjh4866.NBLHTTPFileManager.lock"; 522 | } 523 | 524 | self.lock = [[NSLock alloc] init]; 525 | self.lock.name = @"com.yjh4866.NBLHTTPFileManager.lock"; 526 | } 527 | return self; 528 | } 529 | 530 | - (void)dealloc 531 | { 532 | } 533 | 534 | // 通用对象 535 | + (NBLHTTPFileManager *)sharedManager 536 | { 537 | static NBLHTTPFileManager *sharedManager = nil; 538 | static dispatch_once_t onceToken; 539 | dispatch_once(&onceToken, ^{ 540 | sharedManager = [[NBLHTTPFileManager alloc] init]; 541 | }); 542 | return sharedManager; 543 | } 544 | 545 | // 指定url的下载任务是否存在 546 | - (BOOL)downloadTaskIsExist:(NSString *)url 547 | { 548 | if (self.urlSession) { 549 | [self.lock lock]; 550 | // 先查一下是否已经存在 551 | for (URLSessionDownloadTaskItem *taskItem in self.mdicTaskItemForTaskIdentifier.allValues) { 552 | // 参数相等,且未取消未完成 553 | if ([taskItem.url isEqualToString:url] && 554 | NSURLSessionTaskStateCanceling != taskItem.urlSessionTask.state && 555 | NSURLSessionTaskStateCompleted != taskItem.urlSessionTask.state) { 556 | [self.lock unlock]; 557 | return YES; 558 | } 559 | } 560 | [self.lock unlock]; 561 | } 562 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 563 | else { 564 | // 先查一下下载任务是否已经存在 565 | for (NBLHTTPFileTaskOperation *operation in self.operationQueue.operations) { 566 | // 参数相等,且未取消未完成 567 | if ([operation.url isEqualToString:url] && 568 | !operation.isCancelled && !operation.finished) { 569 | return YES; 570 | } 571 | } 572 | } 573 | #endif 574 | return NO; 575 | } 576 | 577 | // 下载文件到指定路径 578 | // url相同则认为是同一下载任务 579 | - (BOOL)downloadFile:(NSString *)filePath from:(NSString *)url withParam:(NSDictionary *)dicParam 580 | progress:(NBLHTTPFileProgress)progress andResult:(NBLHTTPFileResult)result 581 | { 582 | // 先判断url是否有效 583 | NSURL *URLFile = [NSURL URLWithString:url]; 584 | if (nil == URLFile) { 585 | return NO; 586 | } 587 | // 任务已经存在则直接返回 588 | if ([self downloadTaskIsExist:url]) { 589 | return NO; 590 | } 591 | 592 | // 未给定文件保存路径,则生成一个路径 593 | if (nil == filePath) { 594 | filePath = FilePath(url); 595 | } 596 | if (self.urlSession) { 597 | // 创建NSURLSessionDataTask 598 | __block NSURLSessionDownloadTask *urlSessionTask = nil; 599 | dispatch_sync(urlsession_creation_queue(), ^{ 600 | // 如果临时文件存在,则用该文件继续下载 601 | NSString *filePathTemp = FilePath_Temp(filePath); 602 | NSData *fileData = [NSData dataWithContentsOfFile:filePathTemp]; 603 | if (fileData.length > 0) { 604 | urlSessionTask = [self.urlSession downloadTaskWithResumeData:fileData]; 605 | // 删除临时文件 606 | [[NSFileManager defaultManager] removeItemAtPath:filePathTemp error:nil]; 607 | } 608 | // 临时文件不存在,或者创建失败则重新创建一个任务 609 | if (nil == urlSessionTask) { 610 | urlSessionTask = [self.urlSession downloadTaskWithURL:URLFile]; 611 | } 612 | }); 613 | // 配备任务项以保存相关数据 614 | URLSessionDownloadTaskItem *taskItem = [[URLSessionDownloadTaskItem alloc] init]; 615 | taskItem.urlSessionTask = urlSessionTask; 616 | taskItem.filePath = filePath; 617 | taskItem.url = url; 618 | taskItem.progress = progress; 619 | taskItem.result = result; 620 | taskItem.param = dicParam; 621 | [self.lock lock]; 622 | self.mdicTaskItemForTaskIdentifier[@(urlSessionTask.taskIdentifier)] = taskItem; 623 | [self.lock unlock]; 624 | // 启动网络连接 625 | [urlSessionTask resume]; 626 | return YES; 627 | } 628 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 629 | else { 630 | // 创建Operation 631 | NBLHTTPFileTaskOperation *operation = [[NBLHTTPFileTaskOperation alloc] initWithFilePath:filePath andUrl:url]; 632 | operation.progress = progress; 633 | operation.result = result; 634 | operation.param = dicParam; 635 | [self.operationQueue addOperation:operation]; 636 | return YES; 637 | } 638 | #endif 639 | return NO; 640 | } 641 | 642 | // 取消下载 643 | - (void)cancelDownloadFileFrom:(NSString *)url 644 | { 645 | if (nil == url) { 646 | return; 647 | } 648 | if (self.urlSession) { 649 | [self.lock lock]; 650 | // 遍历任务队列 651 | for (URLSessionDownloadTaskItem *taskItem in self.mdicTaskItemForTaskIdentifier.allValues) { 652 | // 参数相等,且未完成,则取消该任务 653 | if ([taskItem.url isEqualToString:url] && 654 | NSURLSessionTaskStateCompleted != taskItem.urlSessionTask.state) { 655 | // 取消下载 656 | [taskItem.urlSessionTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) { 657 | // 保存到固定的临时目录以备续传 658 | NSString *filePathTemp = FilePath_Temp(taskItem.filePath); 659 | [resumeData writeToFile:filePathTemp atomically:YES]; 660 | }]; 661 | } 662 | } 663 | [self.lock unlock]; 664 | } 665 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 666 | else { 667 | // 遍历任务队列 668 | for (NBLHTTPFileTaskOperation *operation in self.operationQueue.operations) { 669 | // 参数相等,且未完成,则取消该任务 670 | if ([operation.url isEqualToString:url] && !operation.finished) { 671 | [operation cancel]; 672 | } 673 | } 674 | } 675 | #endif 676 | } 677 | 678 | 679 | #pragma mark NSURLSessionDelegate 680 | 681 | /* The last message a session receives. A session will only become 682 | * invalid because of a systemic error or when it has been 683 | * explicitly invalidated, in which case the error parameter will be nil. 684 | */ 685 | - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error 686 | { 687 | [self.lock lock]; 688 | // 所有请求均出错 689 | for (URLSessionDownloadTaskItem *taskItem in self.mdicTaskItemForTaskIdentifier.allValues) { 690 | dispatch_group_async(urlsession_completion_group(), dispatch_get_main_queue(), ^{ 691 | if (taskItem.result) { 692 | taskItem.result(taskItem.filePath, taskItem.httpResponse, error, taskItem.param); 693 | } 694 | }); 695 | } 696 | [self.mdicTaskItemForTaskIdentifier removeAllObjects]; 697 | [self.lock unlock]; 698 | } 699 | 700 | #pragma mark NSURLSessionTaskDelegate 701 | 702 | /* Sent as the last message related to a specific task. Error may be 703 | * nil, which implies that no error occurred and this task is complete. 704 | */ 705 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task 706 | didCompleteWithError:(NSError *)error 707 | { 708 | [self.lock lock]; 709 | URLSessionDownloadTaskItem *taskItem = self.mdicTaskItemForTaskIdentifier[@(task.taskIdentifier)]; 710 | [self.lock unlock]; 711 | // 网络连接意外断开,则需要保存缓存数据以备续传 712 | if (error.userInfo[NSURLSessionDownloadTaskResumeData]) { 713 | // 保存到固定的临时目录以备续传 714 | NSString *filePathTemp = FilePath_Temp(taskItem.filePath); 715 | [error.userInfo[NSURLSessionDownloadTaskResumeData] writeToFile:filePathTemp atomically:YES]; 716 | } 717 | // 取消不算下载失败 718 | if (NSURLErrorCancelled != error.code) { 719 | // 通知下载结果 720 | dispatch_group_async(urlsession_completion_group(), dispatch_get_main_queue(), ^{ 721 | if (taskItem.result) { 722 | taskItem.result(taskItem.filePath, taskItem.httpResponse, error, taskItem.param); 723 | } 724 | }); 725 | } 726 | [self.lock lock]; 727 | [self.mdicTaskItemForTaskIdentifier removeObjectForKey:@(task.taskIdentifier)]; 728 | [self.lock unlock]; 729 | } 730 | 731 | 732 | #pragma mark NSURLSessionDownloadTaskDelegate 733 | 734 | - (void)URLSession:(NSURLSession *)session 735 | downloadTask:(NSURLSessionDownloadTask *)downloadTask 736 | didFinishDownloadingToURL:(NSURL *)location 737 | { 738 | [self.lock lock]; 739 | URLSessionDownloadTaskItem *taskItem = self.mdicTaskItemForTaskIdentifier[@(downloadTask.taskIdentifier)]; 740 | [self.lock unlock]; 741 | // 移到指定的目录 742 | NSURL *dstURL = [NSURL fileURLWithPath:taskItem.filePath]; 743 | [[NSFileManager defaultManager] moveItemAtURL:location toURL:dstURL error:nil]; 744 | } 745 | 746 | - (void)URLSession:(__unused NSURLSession *)session 747 | downloadTask:(__unused NSURLSessionDownloadTask *)downloadTask 748 | didWriteData:(__unused int64_t)bytesWritten 749 | totalBytesWritten:(int64_t)totalBytesWritten 750 | totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite 751 | { 752 | [self.lock lock]; 753 | URLSessionDownloadTaskItem *taskItem = self.mdicTaskItemForTaskIdentifier[@(downloadTask.taskIdentifier)]; 754 | [self.lock unlock]; 755 | // 告知进度更新 756 | dispatch_group_async(urlsession_completion_group(), dispatch_get_main_queue(), ^{ 757 | if (taskItem.progress) { 758 | taskItem.progress(totalBytesWritten, totalBytesExpectedToWrite, taskItem.param); 759 | } 760 | }); 761 | } 762 | 763 | - (void)URLSession:(__unused NSURLSession *)session 764 | downloadTask:(__unused NSURLSessionDownloadTask *)downloadTask 765 | didResumeAtOffset:(int64_t)fileOffset 766 | expectedTotalBytes:(int64_t)expectedTotalBytes 767 | { 768 | [self.lock lock]; 769 | URLSessionDownloadTaskItem *taskItem = self.mdicTaskItemForTaskIdentifier[@(downloadTask.taskIdentifier)]; 770 | [self.lock unlock]; 771 | // 告知进度更新 772 | dispatch_group_async(urlsession_completion_group(), dispatch_get_main_queue(), ^{ 773 | if (taskItem.progress) { 774 | taskItem.progress(fileOffset, expectedTotalBytes, taskItem.param); 775 | } 776 | }); 777 | } 778 | 779 | @end 780 | -------------------------------------------------------------------------------- /ExceedMVC/Core/Net/SimplifiedAFN/NBLHTTPManager.h: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2015-2016 NBL ( https://github.com/yjh4866/SimplifiedAFN ) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #import 24 | 25 | /** 26 | * HTTP请求的响应类型 27 | */ 28 | typedef NS_ENUM(unsigned int, NBLResponseObjectType){ 29 | /** 30 | * NSData 31 | */ 32 | NBLResponseObjectType_Data = 0, 33 | /** 34 | * NSString 35 | */ 36 | NBLResponseObjectType_String, 37 | /** 38 | * JSON对象 39 | */ 40 | NBLResponseObjectType_JSON 41 | }; 42 | 43 | /** 44 | * HTTP请求进度 45 | * 46 | * @param webData webData为nil表示收到响应 47 | * @param bytesReceived 已接收到的数据长度 48 | * @param totalBytes 数据总长度。-1表示长度未知 49 | * @param dicParam 回传对象 50 | */ 51 | typedef void (^NBLHTTPProgress)(NSData *webData, int64_t bytesReceived, 52 | int64_t totalBytes, NSDictionary *dicParam); 53 | /** 54 | * HTTP请求结果 55 | * 56 | * @param httpResponse HTTP响应对象NSHTTPURLResponse 57 | * @param responseObject 请求到的对象, 58 | * @param error 发生的错误。nil表示成功 59 | * @param dicParam 回传对象 60 | */ 61 | typedef void (^NBLHTTPResult)(NSHTTPURLResponse *httpResponse, id responseObject, 62 | NSError *error, NSDictionary *dicParam); 63 | 64 | 65 | /* 保留[[NBLHTTPManager alloc] init]的实例化方案。 66 | 可以使用默认的单例请求数据,也可以另外实例化以与默认的单例对象区分开 67 | */ 68 | @interface NBLHTTPManager : NSObject 69 | 70 | // 通用单例 71 | + (NBLHTTPManager *)sharedManager; 72 | 73 | // 指定参数的网络请求是否存在 74 | - (BOOL)requestIsExist:(NSDictionary *)dicParam; 75 | 76 | // 指定url的网络请求是否存在 77 | - (BOOL)urlIsRequesting:(NSString *)url; 78 | 79 | // 根据url获取Web数据(dicParam不为空则以此为去重依据,否则以url为去重依据) 80 | // dicParam 可用于回传数据,需要取消时不可为nil 81 | - (BOOL)requestObject:(NBLResponseObjectType)resObjType fromURL:(NSString *)url 82 | withParam:(NSDictionary *)dicParam andResult:(NBLHTTPResult)result; 83 | 84 | // 根据NSURLRequest获取Web数据(dicParam不为空则以此为去重依据,否则以url为去重依据) 85 | // dicParam 可用于回传数据,需要取消时不可为nil 86 | - (BOOL)requestObject:(NBLResponseObjectType)resObjType 87 | withRequest:(NSURLRequest *)request param:(NSDictionary *)dicParam 88 | andResult:(NBLHTTPResult)result; 89 | 90 | // 根据url获取Web数据(dicParam不为空则以此为去重依据,否则以url为去重依据) 91 | // dicParam 可用于回传数据,需要取消时不可为nil 92 | - (BOOL)requestObject:(NBLResponseObjectType)resObjType fromURL:(NSString *)url 93 | withParam:(NSDictionary *)dicParam 94 | progress:(NBLHTTPProgress)progress andResult:(NBLHTTPResult)result; 95 | 96 | // 根据NSURLRequest获取Web数据(dicParam不为空则以此为去重依据,否则以url为去重依据) 97 | // dicParam 可用于回传数据,需要取消时不可为nil 98 | - (BOOL)requestObject:(NBLResponseObjectType)resObjType 99 | withRequest:(NSURLRequest *)request param:(NSDictionary *)dicParam 100 | progress:(NBLHTTPProgress)progress andResult:(NBLHTTPResult)result; 101 | 102 | // 取消网络请求 103 | - (void)cancelRequestWithParam:(NSDictionary *)dicParam; 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /ExceedMVC/Core/Net/SimplifiedAFN/NBLHTTPManager.m: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2015-2016 NBL ( https://github.com/yjh4866/SimplifiedAFN ) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #import "NBLHTTPManager.h" 24 | 25 | 26 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 27 | 28 | #pragma mark - 29 | #pragma mark - NSURLConnection方案 30 | #pragma mark - 31 | 32 | static dispatch_group_t urlconnection_operation_completion_group() { 33 | static dispatch_group_t urlconnection_operation_completion_group; 34 | static dispatch_once_t onceToken; 35 | dispatch_once(&onceToken, ^{ 36 | urlconnection_operation_completion_group = dispatch_group_create(); 37 | }); 38 | return urlconnection_operation_completion_group; 39 | } 40 | 41 | static dispatch_queue_t urlconnection_operation_completion_queue() { 42 | static dispatch_queue_t urlconnection_operation_completion_queue; 43 | static dispatch_once_t onceToken; 44 | dispatch_once(&onceToken, ^{ 45 | urlconnection_operation_completion_queue = dispatch_queue_create("com.yjh4866.urlconnection.completion.queue", DISPATCH_QUEUE_CONCURRENT); 46 | }); 47 | return urlconnection_operation_completion_queue; 48 | } 49 | 50 | static dispatch_group_t http_request_operation_completion_group() { 51 | static dispatch_group_t http_request_operation_completion_group; 52 | static dispatch_once_t onceToken; 53 | dispatch_once(&onceToken, ^{ 54 | http_request_operation_completion_group = dispatch_group_create(); 55 | }); 56 | return http_request_operation_completion_group; 57 | } 58 | 59 | static dispatch_queue_t http_request_operation_processing_queue() { 60 | static dispatch_queue_t http_request_operation_processing_queue; 61 | static dispatch_once_t onceToken; 62 | dispatch_once(&onceToken, ^{ 63 | http_request_operation_processing_queue = dispatch_queue_create("com.yjh4866.http_request.processing.queue", DISPATCH_QUEUE_CONCURRENT); 64 | }); 65 | return http_request_operation_processing_queue; 66 | } 67 | 68 | typedef NS_ENUM(unsigned int, URLConnectionStatus) { 69 | URLConnectionStatus_Canceling = 1, 70 | URLConnectionStatus_Waiting, 71 | URLConnectionStatus_Running, 72 | URLConnectionStatus_Finished, 73 | }; 74 | 75 | static inline NSString * KeyPathFromHTTPTaskStatus(URLConnectionStatus state) { 76 | switch (state) { 77 | case URLConnectionStatus_Canceling: 78 | return @"isCanceling"; 79 | case URLConnectionStatus_Waiting: 80 | return @"isWaiting"; 81 | case URLConnectionStatus_Running: 82 | return @"isRunning"; 83 | case URLConnectionStatus_Finished: 84 | return @"isFinished"; 85 | default: { 86 | #pragma clang diagnostic push 87 | #pragma clang diagnostic ignored "-Wunreachable-code" 88 | return @"state"; 89 | #pragma clang diagnostic pop 90 | } 91 | } 92 | } 93 | 94 | 95 | #pragma mark - URLConnectionOperation 96 | 97 | @interface URLConnectionOperation : NSOperation 98 | @property (nonatomic, assign) NBLResponseObjectType responseObjectType; 99 | @property (readwrite, nonatomic, strong) NSRecursiveLock *lock; 100 | @property (readwrite, nonatomic, strong) NSURLConnection *urlConnection; 101 | @property (nonatomic, strong) NSURLRequest *request; 102 | @property (nonatomic, strong) NSHTTPURLResponse *httpResponse; 103 | @property (nonatomic, strong) NSMutableData *mdataCache; 104 | @property (nonatomic, copy) NBLHTTPProgress progress; 105 | @property (readwrite, nonatomic, strong) NSError *error; 106 | @property (nonatomic, strong) NSDictionary *param; 107 | @property (readwrite, nonatomic, assign) URLConnectionStatus taskStatus; 108 | 109 | @property (nonatomic, strong, nullable) dispatch_queue_t completionQueue; 110 | 111 | - (instancetype)init NS_UNAVAILABLE; 112 | @end 113 | 114 | 115 | #pragma mark - Implementation URLConnectionOperation 116 | 117 | @implementation URLConnectionOperation 118 | 119 | + (void)networkRequestThreadEntryPoint:(id)__unused object { 120 | @autoreleasepool { 121 | [[NSThread currentThread] setName:@"yjh4866.URLConnectionOperation"]; 122 | 123 | NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 124 | [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; 125 | [runLoop run]; 126 | } 127 | } 128 | 129 | + (NSThread *)networkRequestThread { 130 | static NSThread *_networkRequestThread = nil; 131 | static dispatch_once_t oncePredicate; 132 | dispatch_once(&oncePredicate, ^{ 133 | _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; 134 | [_networkRequestThread start]; 135 | }); 136 | 137 | return _networkRequestThread; 138 | } 139 | 140 | - (instancetype)initWithURLRequest:(NSURLRequest *)urlRequest 141 | { 142 | self = [super init]; 143 | if (self) { 144 | self.mdataCache = [[NSMutableData alloc] init]; 145 | self.request = urlRequest; 146 | _taskStatus = URLConnectionStatus_Waiting; 147 | 148 | self.lock = [[NSRecursiveLock alloc] init]; 149 | self.lock.name = @"com.yjh4866.URLConnectionOperation.lock"; 150 | } 151 | return self; 152 | } 153 | 154 | - (void)dealloc 155 | { 156 | } 157 | 158 | - (void)setTaskStatus:(URLConnectionStatus)taskStatus { 159 | [self.lock lock]; 160 | NSString *oldStateKey = KeyPathFromHTTPTaskStatus(self.taskStatus); 161 | NSString *newStateKey = KeyPathFromHTTPTaskStatus(taskStatus); 162 | 163 | // 下面这四行KVO代码很重要,用以通知Operation任务状态变更 164 | [self willChangeValueForKey:newStateKey]; 165 | [self willChangeValueForKey:oldStateKey]; 166 | _taskStatus = taskStatus; 167 | [self didChangeValueForKey:oldStateKey]; 168 | [self didChangeValueForKey:newStateKey]; 169 | [self.lock unlock]; 170 | } 171 | 172 | - (int64_t)totalBytes 173 | { 174 | return self.httpResponse.expectedContentLength; 175 | } 176 | 177 | #pragma mark NSOperation 178 | 179 | // 这里是为了让该NSOperation能正常释放,不然会在setHTTPResult:中的block循环引用 180 | - (void)setCompletionBlock:(void (^)(void))block { 181 | [self.lock lock]; 182 | if (!block) { 183 | [super setCompletionBlock:nil]; 184 | } else { 185 | __weak typeof(self) weakSelf = self; 186 | [super setCompletionBlock:^ { 187 | 188 | dispatch_group_t group = urlconnection_operation_completion_group(); 189 | 190 | dispatch_group_async(group, dispatch_get_main_queue(), ^{ 191 | block(); 192 | }); 193 | 194 | __strong typeof(weakSelf) strongSelf = weakSelf; 195 | dispatch_group_notify(group, urlconnection_operation_completion_queue(), ^{ 196 | [strongSelf setCompletionBlock:nil]; 197 | }); 198 | }]; 199 | } 200 | [self.lock unlock]; 201 | } 202 | 203 | - (BOOL)isCancelled 204 | { 205 | return URLConnectionStatus_Canceling == self.taskStatus; 206 | } 207 | - (BOOL)isExecuting 208 | { 209 | return URLConnectionStatus_Running == self.taskStatus; 210 | } 211 | - (BOOL)isFinished 212 | { 213 | return URLConnectionStatus_Finished == self.taskStatus; 214 | } 215 | 216 | - (void)start 217 | { 218 | [self.lock lock]; 219 | [super start]; 220 | self.taskStatus = URLConnectionStatus_Running; 221 | [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:@[NSRunLoopCommonModes]]; 222 | [self.lock unlock]; 223 | } 224 | 225 | - (void)operationDidStart { 226 | [self.lock lock]; 227 | // 用NSURLConnection创建网络连接 228 | self.urlConnection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; 229 | // 启动网络连接 230 | [self.urlConnection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; 231 | [self.urlConnection start]; 232 | [self.lock unlock]; 233 | } 234 | 235 | - (void)cancel 236 | { 237 | [self.lock lock]; 238 | [super cancel]; 239 | self.taskStatus = URLConnectionStatus_Canceling; 240 | [self.urlConnection cancel]; 241 | [self.lock unlock]; 242 | } 243 | 244 | - (void)setHTTPResult:(NBLHTTPResult)result 245 | { 246 | #pragma clang diagnostic push 247 | #pragma clang diagnostic ignored "-Warc-retain-cycles" 248 | #pragma clang diagnostic ignored "-Wgnu" 249 | self.completionBlock = ^{ 250 | dispatch_async(http_request_operation_processing_queue(), ^{ 251 | if (result) { 252 | dispatch_group_async(http_request_operation_completion_group(), self.completionQueue?:dispatch_get_main_queue(), ^{ 253 | if (nil != self.error) { 254 | // 将已有数据保存到Error的userInfo中 255 | NSMutableDictionary *mdicUserInfo = [NSMutableDictionary dictionary]; 256 | if (self.error.userInfo) { 257 | [mdicUserInfo setDictionary:self.error.userInfo]; 258 | } 259 | [mdicUserInfo setObject:self.mdataCache forKey:@"data"]; 260 | // 失败 261 | NSError *err = [NSError errorWithDomain:self.error.domain code:self.error.code 262 | userInfo:mdicUserInfo]; 263 | result(self.httpResponse, nil, err, self.param); 264 | } 265 | else if (NBLResponseObjectType_String == self.responseObjectType) { 266 | NSString *strWebData = [[NSString alloc] initWithData:self.mdataCache 267 | encoding:NSUTF8StringEncoding]; 268 | if (strWebData) { 269 | result(self.httpResponse, strWebData, nil, self.param); 270 | } 271 | else { 272 | // 解析失败 273 | NSError *err = [NSError errorWithDomain:@"NBLErrorDomain" 274 | code:NSURLErrorCannotDecodeContentData 275 | userInfo:@{@"data": self.mdataCache}]; 276 | result(self.httpResponse, nil, err, self.param); 277 | } 278 | } 279 | else if (NBLResponseObjectType_JSON == self.responseObjectType) { 280 | id responseObject = [NSJSONSerialization JSONObjectWithData:self.mdataCache options:NSJSONReadingAllowFragments error:nil]; 281 | if (responseObject) { 282 | result(self.httpResponse, responseObject, nil, self.param); 283 | } 284 | else { 285 | // 解析失败 286 | NSError *err = [NSError errorWithDomain:@"NBLErrorDomain" 287 | code:NSURLErrorCannotDecodeContentData 288 | userInfo:@{@"data": self.mdataCache}]; 289 | result(self.httpResponse, nil, err, self.param); 290 | } 291 | } 292 | else { 293 | result(self.httpResponse, self.mdataCache, nil, self.param); 294 | } 295 | }); 296 | } 297 | }); 298 | }; 299 | #pragma clang diagnostic pop 300 | } 301 | 302 | 303 | #pragma mark NSURLConnectionDataDelegate 304 | 305 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 306 | { 307 | [self.lock lock]; 308 | self.error = error; 309 | self.urlConnection = nil; 310 | self.taskStatus = URLConnectionStatus_Finished; 311 | [self.lock unlock]; 312 | } 313 | 314 | - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 315 | { 316 | self.httpResponse = nil; 317 | if ([response isKindOfClass:NSHTTPURLResponse.class]) { 318 | self.httpResponse = (NSHTTPURLResponse *)response; 319 | } 320 | // 通过下载进度提示总大小 321 | dispatch_async(dispatch_get_main_queue(), ^{ 322 | if (self.progress) { 323 | self.progress(nil, 0, self.totalBytes, self.param); 324 | } 325 | }); 326 | } 327 | 328 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 329 | { 330 | // 追加数据 331 | [self.lock lock]; 332 | [self.mdataCache appendData:data]; 333 | [self.lock unlock]; 334 | // 下载进度更新 335 | dispatch_async(dispatch_get_main_queue(), ^{ 336 | if (self.progress) { 337 | self.progress(data, self.mdataCache.length, self.totalBytes, self.param); 338 | } 339 | }); 340 | } 341 | 342 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection 343 | { 344 | [self.lock lock]; 345 | self.urlConnection = nil; 346 | self.taskStatus = URLConnectionStatus_Finished; 347 | [self.lock unlock]; 348 | } 349 | 350 | @end 351 | 352 | #endif 353 | 354 | 355 | #pragma mark - 356 | #pragma mark - NSURLSessionDataTask方案 357 | #pragma mark - 358 | 359 | static dispatch_queue_t urlsession_creation_queue() { 360 | static dispatch_queue_t urlsession_creation_queue; 361 | static dispatch_once_t onceToken; 362 | dispatch_once(&onceToken, ^{ 363 | urlsession_creation_queue = dispatch_queue_create("com.yjh4866.urlsession.http.creation.queue", DISPATCH_QUEUE_SERIAL); 364 | }); 365 | 366 | return urlsession_creation_queue; 367 | } 368 | 369 | static dispatch_group_t urlsession_completion_group() { 370 | static dispatch_group_t urlsession_completion_group; 371 | static dispatch_once_t onceToken; 372 | dispatch_once(&onceToken, ^{ 373 | urlsession_completion_group = dispatch_group_create(); 374 | }); 375 | 376 | return urlsession_completion_group; 377 | } 378 | 379 | 380 | #pragma mark URLSessionTaskItem 381 | 382 | @interface URLSessionTaskItem : NSObject 383 | @property (nonatomic, assign) NBLResponseObjectType responseObjectType; 384 | @property (nonatomic, strong) NSURLSessionDataTask *urlSessionTask; 385 | @property (readwrite, nonatomic, strong) NSURLRequest *request; 386 | @property (nonatomic, strong) NSHTTPURLResponse *httpResponse; 387 | @property (nonatomic, strong) NSMutableData *mdataCache; 388 | @property (nonatomic, copy) NBLHTTPProgress progress; 389 | @property (nonatomic, copy) NBLHTTPResult result; 390 | @property (nonatomic, strong) NSDictionary *param; 391 | 392 | @property (nonatomic, strong, nullable) dispatch_queue_t completionQueue; 393 | @end 394 | 395 | 396 | #pragma mark Implementation URLSessionTaskItem 397 | 398 | @implementation URLSessionTaskItem 399 | - (instancetype)init 400 | { 401 | self = [super init]; 402 | if (self) { 403 | self.mdataCache = [[NSMutableData alloc] init]; 404 | } 405 | return self; 406 | } 407 | - (void)dealloc 408 | { 409 | 410 | } 411 | - (int64_t)totalBytes 412 | { 413 | return self.httpResponse.expectedContentLength; 414 | } 415 | @end 416 | 417 | 418 | #pragma mark - NBLHTTPManager () 419 | 420 | @interface NBLHTTPManager () 421 | @property (nonatomic, strong) NSOperationQueue *operationQueue; 422 | @property (readwrite, nonatomic, strong) NSURLSession *urlSession; 423 | @property (readwrite, nonatomic, strong) NSMutableDictionary *mdicTaskItemForTaskIdentifier; 424 | @property (readwrite, nonatomic, strong) NSLock *lock; 425 | @end 426 | 427 | 428 | #pragma mark - Implementation NBLHTTPManager 429 | 430 | @implementation NBLHTTPManager 431 | 432 | - (instancetype)init 433 | { 434 | self = [super init]; 435 | if (self) { 436 | self.operationQueue = [[NSOperationQueue alloc] init]; 437 | // NSURLSession存在,即系统版本为7.0及以上,则采用NSURLSession来发网络请求 438 | if (NSClassFromString(@"NSURLSession")) { 439 | self.operationQueue.maxConcurrentOperationCount = 1; 440 | NSURLSessionConfiguration *urlSessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; 441 | urlSessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringCacheData; 442 | self.urlSession = [NSURLSession sessionWithConfiguration:urlSessionConfig delegate:self delegateQueue:self.operationQueue]; 443 | self.mdicTaskItemForTaskIdentifier = [[NSMutableDictionary alloc] init]; 444 | self.lock = [[NSLock alloc] init]; 445 | self.lock.name = @"com.yjh4866.NBLHTTPManager.lock"; 446 | } 447 | } 448 | return self; 449 | } 450 | 451 | - (void)dealloc 452 | { 453 | } 454 | 455 | // 通用单例 456 | + (NBLHTTPManager *)sharedManager 457 | { 458 | static NBLHTTPManager *sharedManager = nil; 459 | static dispatch_once_t onceToken; 460 | dispatch_once(&onceToken, ^{ 461 | sharedManager = [[NBLHTTPManager alloc] init]; 462 | }); 463 | return sharedManager; 464 | } 465 | 466 | // 指定参数的网络请求是否存在 467 | - (BOOL)requestIsExist:(NSDictionary *)dicParam 468 | { 469 | if (self.urlSession) { 470 | [self.lock lock]; 471 | // 先查一下是否已经存在 472 | for (URLSessionTaskItem *taskItem in self.mdicTaskItemForTaskIdentifier.allValues) { 473 | // 参数相等,且未取消未完成 474 | if ([taskItem.param isEqualToDictionary:dicParam] && 475 | NSURLSessionTaskStateCanceling != taskItem.urlSessionTask.state && 476 | NSURLSessionTaskStateCompleted != taskItem.urlSessionTask.state) { 477 | [self.lock unlock]; 478 | return YES; 479 | } 480 | } 481 | [self.lock unlock]; 482 | } 483 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 484 | else { 485 | // 先查一下是否已经存在 486 | for (URLConnectionOperation *operation in self.operationQueue.operations) { 487 | // 参数相等,且未取消未完成 488 | if ([operation.param isEqualToDictionary:dicParam] && 489 | !operation.isCancelled && !operation.finished) { 490 | return YES; 491 | } 492 | } 493 | } 494 | #endif 495 | return NO; 496 | } 497 | 498 | // 指定url的网络请求是否存在 499 | - (BOOL)urlIsRequesting:(NSString *)url 500 | { 501 | if (self.urlSession) { 502 | [self.lock lock]; 503 | // 先查一下是否已经存在 504 | for (URLSessionTaskItem *taskItem in self.mdicTaskItemForTaskIdentifier.allValues) { 505 | // 参数相等,且未取消未完成 506 | if ([taskItem.request.URL.absoluteString isEqualToString:url] && 507 | NSURLSessionTaskStateCanceling != taskItem.urlSessionTask.state && 508 | NSURLSessionTaskStateCompleted != taskItem.urlSessionTask.state) { 509 | [self.lock unlock]; 510 | return YES; 511 | } 512 | } 513 | [self.lock unlock]; 514 | } 515 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 516 | else { 517 | // 先查一下是否已经存在 518 | for (URLConnectionOperation *operation in self.operationQueue.operations) { 519 | // 参数相等,且未取消未完成 520 | if ([operation.request.URL.absoluteString isEqualToString:url] && 521 | !operation.isCancelled && !operation.finished) { 522 | return YES; 523 | } 524 | } 525 | } 526 | #endif 527 | return NO; 528 | } 529 | 530 | // 根据url获取Web数据(dicParam不为空则以此为去重依据,否则以url为去重依据) 531 | // dicParam 可用于回传数据,需要取消时不可为nil 532 | - (BOOL)requestObject:(NBLResponseObjectType)resObjType fromURL:(NSString *)url 533 | withParam:(NSDictionary *)dicParam andResult:(NBLHTTPResult)result 534 | { 535 | return [self requestObject:resObjType fromURL:url withParam:dicParam 536 | progress:nil andResult:result]; 537 | } 538 | 539 | // 根据NSURLRequest获取Web数据(dicParam不为空则以此为去重依据,否则以url为去重依据) 540 | // dicParam 可用于回传数据,需要取消时不可为nil 541 | - (BOOL)requestObject:(NBLResponseObjectType)resObjType 542 | withRequest:(NSURLRequest *)request param:(NSDictionary *)dicParam 543 | andResult:(NBLHTTPResult)result 544 | { 545 | return [self requestObject:resObjType withRequest:request param:dicParam 546 | progress:nil andResult:result]; 547 | } 548 | 549 | // 根据url获取Web数据(dicParam不为空则以此为去重依据,否则以url为去重依据) 550 | // dicParam 可用于回传数据,需要取消时不可为nil 551 | - (BOOL)requestObject:(NBLResponseObjectType)resObjType fromURL:(NSString *)url 552 | withParam:(NSDictionary *)dicParam 553 | progress:(NBLHTTPProgress)progress andResult:(NBLHTTPResult)result 554 | { 555 | // 先判断url是否有效 556 | NSURL *URL = [NSURL URLWithString:url]; 557 | if (nil == URL) { 558 | return NO; 559 | } 560 | // 实例化NSURLRequest 561 | NSURLRequest *urlRequest = [NSURLRequest requestWithURL:URL]; 562 | // 开始请求数据 563 | return [self requestObject:resObjType withRequest:urlRequest param:dicParam 564 | progress:progress andResult:result]; 565 | } 566 | 567 | // 根据NSURLRequest获取Web数据(dicParam不为空则以此为去重依据,否则以url为去重依据) 568 | // dicParam 可用于回传数据,需要取消时不可为nil 569 | - (BOOL)requestObject:(NBLResponseObjectType)resObjType 570 | withRequest:(NSURLRequest *)request param:(NSDictionary *)dicParam 571 | progress:(NBLHTTPProgress)progress andResult:(NBLHTTPResult)result 572 | { 573 | return [self requestObject:resObjType withRequest:request param:dicParam 574 | progress:progress andResult:result onCompletionQueue:nil]; 575 | } 576 | 577 | // 根据NSURLRequest获取Web数据(dicParam不为空则以此为去重依据,否则以url为去重依据) 578 | // dicParam 可用于回传数据,需要取消时不可为nil 579 | - (BOOL)requestObject:(NBLResponseObjectType)resObjType 580 | withRequest:(NSURLRequest *)request param:(NSDictionary *)dicParam 581 | progress:(NBLHTTPProgress)progress andResult:(NBLHTTPResult)result 582 | onCompletionQueue:(dispatch_queue_t)completionQueue 583 | { 584 | // 任务已经存在则直接返回 585 | // dicParam存在则以此为判断依据 586 | if (nil != dicParam && [self requestIsExist:dicParam]) { 587 | return NO; 588 | } 589 | // dicParam不存在则以url为判断依据 590 | if (nil == dicParam && [self urlIsRequesting:request.URL.absoluteString]) { 591 | return NO; 592 | } 593 | 594 | if (self.urlSession) { 595 | // 创建NSURLSessionDataTask 596 | __block NSURLSessionDataTask *urlSessionTask = nil; 597 | dispatch_sync(urlsession_creation_queue(), ^{ 598 | urlSessionTask = [self.urlSession dataTaskWithRequest:request]; 599 | }); 600 | // 配备任务项以保存相关数据 601 | URLSessionTaskItem *taskItem = [[URLSessionTaskItem alloc] init]; 602 | taskItem.responseObjectType = resObjType; 603 | taskItem.urlSessionTask = urlSessionTask; 604 | taskItem.progress = progress; 605 | taskItem.result = result; 606 | taskItem.param = dicParam; 607 | taskItem.completionQueue = completionQueue; 608 | [self.lock lock]; 609 | self.mdicTaskItemForTaskIdentifier[@(urlSessionTask.taskIdentifier)] = taskItem; 610 | [self.lock unlock]; 611 | // 启动网络连接 612 | [urlSessionTask resume]; 613 | return YES; 614 | } 615 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 616 | else { 617 | // 创建Operation 618 | URLConnectionOperation *operation = [[URLConnectionOperation alloc] initWithURLRequest:request]; 619 | operation.responseObjectType = resObjType; 620 | operation.progress = progress; 621 | operation.param = dicParam; 622 | operation.completionQueue = completionQueue; 623 | [operation setHTTPResult:result]; 624 | [self.operationQueue addOperation:operation]; 625 | return YES; 626 | } 627 | #endif 628 | return NO; 629 | } 630 | 631 | // 取消网络请求 632 | - (void)cancelRequestWithParam:(NSDictionary *)dicParam 633 | { 634 | if (nil == dicParam) { 635 | return; 636 | } 637 | if (self.urlSession) { 638 | [self.lock lock]; 639 | // 遍历任务队列 640 | for (URLSessionTaskItem *taskItem in self.mdicTaskItemForTaskIdentifier.allValues) { 641 | // 参数相等,且未完成,则取消该任务 642 | if ([taskItem.param isEqualToDictionary:dicParam] && 643 | NSURLSessionTaskStateCompleted != taskItem.urlSessionTask.state) { 644 | [taskItem.urlSessionTask cancel]; 645 | } 646 | } 647 | [self.lock unlock]; 648 | } 649 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 650 | else { 651 | // 遍历任务队列 652 | for (URLConnectionOperation *operation in self.operationQueue.operations) { 653 | // 参数相等,且未完成,则取消该任务 654 | if ([operation.param isEqualToDictionary:dicParam] && !operation.finished) { 655 | [operation cancel]; 656 | } 657 | } 658 | } 659 | #endif 660 | } 661 | 662 | 663 | #pragma mark NSURLSessionDelegate 664 | 665 | /* The last message a session receives. A session will only become 666 | * invalid because of a systemic error or when it has been 667 | * explicitly invalidated, in which case the error parameter will be nil. 668 | */ 669 | - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error 670 | { 671 | [self.lock lock]; 672 | // 所有请求均出错 673 | for (URLSessionTaskItem *taskItem in self.mdicTaskItemForTaskIdentifier.allValues) { 674 | dispatch_group_async(urlsession_completion_group(), taskItem.completionQueue?:dispatch_get_main_queue(), ^{ 675 | if (taskItem.result) { 676 | taskItem.result(taskItem.httpResponse, taskItem.mdataCache, error, taskItem.param); 677 | } 678 | }); 679 | } 680 | [self.mdicTaskItemForTaskIdentifier removeAllObjects]; 681 | [self.lock unlock]; 682 | } 683 | 684 | #pragma mark NSURLSessionTaskDelegate 685 | 686 | /* Sent as the last message related to a specific task. Error may be 687 | * nil, which implies that no error occurred and this task is complete. 688 | */ 689 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task 690 | didCompleteWithError:(NSError *)error 691 | { 692 | [self.lock lock]; 693 | URLSessionTaskItem *taskItem = self.mdicTaskItemForTaskIdentifier[@(task.taskIdentifier)]; 694 | [self.lock unlock]; 695 | // 取消不算失败 696 | if (NSURLErrorCancelled != error.code) { 697 | // 通知网络请求结果 698 | dispatch_group_async(urlsession_completion_group(), taskItem.completionQueue?:dispatch_get_main_queue(), ^{ 699 | if (taskItem.result) { 700 | if (nil != error) { 701 | // 将已有数据保存到Error的userInfo中 702 | NSMutableDictionary *mdicUserInfo = [NSMutableDictionary dictionary]; 703 | if (error.userInfo) { 704 | [mdicUserInfo setDictionary:error.userInfo]; 705 | } 706 | [mdicUserInfo setObject:taskItem.mdataCache forKey:@"data"]; 707 | // 失败 708 | NSError *err = [NSError errorWithDomain:error.domain code:error.code userInfo:mdicUserInfo]; 709 | taskItem.result(taskItem.httpResponse, nil, err, taskItem.param); 710 | } 711 | else if (NBLResponseObjectType_String == taskItem.responseObjectType) { 712 | NSString *strWebData = [[NSString alloc] initWithData:taskItem.mdataCache 713 | encoding:NSUTF8StringEncoding]; 714 | if (strWebData) { 715 | taskItem.result(taskItem.httpResponse, strWebData, nil, taskItem.param); 716 | } 717 | else { 718 | // 解析失败 719 | NSError *err = [NSError errorWithDomain:@"NBLErrorDomain" 720 | code:NSURLErrorCannotDecodeContentData 721 | userInfo:@{@"data": taskItem.mdataCache}]; 722 | taskItem.result(taskItem.httpResponse, nil, err, taskItem.param); 723 | } 724 | } 725 | else if (NBLResponseObjectType_JSON == taskItem.responseObjectType) { 726 | id responseObject = [NSJSONSerialization JSONObjectWithData:taskItem.mdataCache options:NSJSONReadingAllowFragments error:nil]; 727 | if (responseObject) { 728 | taskItem.result(taskItem.httpResponse, responseObject, nil, taskItem.param); 729 | } 730 | else { 731 | // 解析失败 732 | NSError *err = [NSError errorWithDomain:@"NBLErrorDomain" 733 | code:NSURLErrorCannotDecodeContentData 734 | userInfo:@{@"data": taskItem.mdataCache}]; 735 | taskItem.result(taskItem.httpResponse, nil, err, taskItem.param); 736 | } 737 | } 738 | else { 739 | taskItem.result(taskItem.httpResponse, taskItem.mdataCache, nil, taskItem.param); 740 | } 741 | } 742 | }); 743 | } 744 | [self.lock lock]; 745 | [self.mdicTaskItemForTaskIdentifier removeObjectForKey:@(task.taskIdentifier)]; 746 | [self.lock unlock]; 747 | } 748 | 749 | 750 | #pragma mark NSURLSessionDataDelegate 751 | 752 | // The task has received a response 753 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 754 | didReceiveResponse:(NSURLResponse *)response 755 | completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler 756 | { 757 | [self.lock lock]; 758 | URLSessionTaskItem *taskItem = self.mdicTaskItemForTaskIdentifier[@(dataTask.taskIdentifier)]; 759 | [self.lock unlock]; 760 | if ([response isKindOfClass:NSHTTPURLResponse.class]) { 761 | taskItem.httpResponse = (NSHTTPURLResponse *)response; 762 | } 763 | // 通过下载进度提示总大小 764 | dispatch_group_async(urlsession_completion_group(), dispatch_get_main_queue(), ^{ 765 | if (taskItem.progress) { 766 | taskItem.progress(nil, 0, taskItem.totalBytes, taskItem.param); 767 | } 768 | }); 769 | 770 | if (completionHandler) { 771 | completionHandler(NSURLSessionResponseAllow); 772 | } 773 | } 774 | 775 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 776 | didReceiveData:(NSData *)data 777 | { 778 | [self.lock lock]; 779 | URLSessionTaskItem *taskItem = self.mdicTaskItemForTaskIdentifier[@(dataTask.taskIdentifier)]; 780 | [self.lock unlock]; 781 | // 追加数据 782 | [taskItem.mdataCache appendData:data]; 783 | // 下载进度更新 784 | dispatch_group_async(urlsession_completion_group(), dispatch_get_main_queue(), ^{ 785 | if (taskItem.progress) { 786 | taskItem.progress(data, taskItem.mdataCache.length, taskItem.totalBytes, taskItem.param); 787 | } 788 | }); 789 | } 790 | 791 | @end 792 | -------------------------------------------------------------------------------- /ExceedMVC/ExceedMVC.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjh4866/ExceedMVC/682df3ca84e5d45a9df18e13ae25e023af7df0db/ExceedMVC/ExceedMVC.sqlite -------------------------------------------------------------------------------- /ExceedMVC/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | 40 | UISupportedInterfaceOrientations~ipad 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationPortraitUpsideDown 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /ExceedMVC/UI/Engine/UIEngine+TabBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIEngine+TabBar.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-7. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "UIEngine.h" 10 | 11 | @interface UIEngine (TabBar) 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ExceedMVC/UI/Engine/UIEngine+TabBar.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIEngine+TabBar.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-7. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "UIEngine+TabBar.h" 10 | #import "DBController+Message.h" 11 | #import "DBController+UserInfo.h" 12 | 13 | @implementation UIEngine (TabBar) 14 | 15 | 16 | #pragma mark - MainVCDataSource 17 | 18 | // 加载Tab页面 19 | - (void)mainVC:(MainVC *)mainVC loadViewControllers:(NSMutableArray *)marray 20 | { 21 | //会话 22 | ChatsVC *chatsVC = [[ChatsVC alloc] init]; 23 | chatsVC.dataSource = self; 24 | chatsVC.delegate = self; 25 | UINavigationController *navChats = [[UINavigationController alloc] initWithRootViewController:chatsVC]; 26 | navChats.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemBookmarks tag:0]; 27 | [marray addObject:navChats]; 28 | //联系人 29 | ContactsVC *contactsVC = [[ContactsVC alloc] init]; 30 | contactsVC.dataSource = self; 31 | contactsVC.delegate = self; 32 | UINavigationController *navContacts = [[UINavigationController alloc] initWithRootViewController:contactsVC]; 33 | navContacts.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemContacts tag:1]; 34 | [marray addObject:navContacts]; 35 | //更多 36 | MoreVC *moreVC = [[MoreVC alloc] init]; 37 | moreVC.dataSource = self; 38 | moreVC.delegate = self; 39 | UINavigationController *navMore = [[UINavigationController alloc] initWithRootViewController:moreVC]; 40 | navMore.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemMore tag:2]; 41 | [marray addObject:navMore]; 42 | } 43 | 44 | 45 | #pragma mark - ChatsVCDataSource 46 | 47 | // 加载会话列表 48 | - (void)chatsVC:(ChatsVC *)chatsVC loadChats:(NSMutableArray *)marrChat 49 | { 50 | [DBController loadChats:marrChat]; 51 | } 52 | 53 | 54 | #pragma mark - ChatsVCDelegate 55 | 56 | // 进入聊天页面 57 | - (void)chatsVC:(ChatsVC *)chatsVC chatWithFriend:(UInt64)friendID 58 | { 59 | ChatVC *chatVC = [[ChatVC alloc] init]; 60 | chatVC.friendID = friendID; 61 | chatVC.dataSource = self; 62 | chatVC.delegate = self; 63 | [chatsVC.navigationController pushViewController:chatVC animated:YES]; 64 | } 65 | 66 | 67 | #pragma mark - ContactsVCDataSource 68 | 69 | // 加载已有联系人 70 | - (void)contactsVC:(ContactsVC *)contactsVC loadContacts:(NSMutableArray *)marray 71 | { 72 | [DBController loadContacts:marray]; 73 | } 74 | 75 | 76 | #pragma mark - ContactsVCDelegate 77 | 78 | // 进入用户详细资料页面 79 | - (void)contactsVC:(ContactsVC *)contactsVC showContactsInfo:(UInt64)userID 80 | { 81 | ContactInfoVC *contactInfoVC = [[ContactInfoVC alloc] init]; 82 | contactInfoVC.userID = userID; 83 | contactInfoVC.dataSource = self; 84 | contactInfoVC.delegate = self; 85 | [contactsVC.navigationController pushViewController:contactInfoVC 86 | animated:YES]; 87 | } 88 | 89 | 90 | #pragma mark - MoreVCDelegate 91 | 92 | // 显示关于页面 93 | - (void)moreVCShowAbout:(MoreVC *)moreVC 94 | { 95 | AboutVC *aboutVC = [[AboutVC alloc] init]; 96 | aboutVC.delegate = self; 97 | [moreVC.navigationController pushViewController:aboutVC animated:YES]; 98 | } 99 | 100 | @end 101 | -------------------------------------------------------------------------------- /ExceedMVC/UI/Engine/UIEngine.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIEngine.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | //Root 13 | #import "RootVC.h" 14 | 15 | #import "MainVC.h" 16 | #import "ChatsVC.h" 17 | #import "ContactsVC.h" 18 | #import "MoreVC.h" 19 | #import "ChatVC.h" 20 | #import "LoginVC.h" 21 | #import "ContactInfoVC.h" 22 | #import "AboutVC.h" 23 | 24 | 25 | @class CoreEngine; 26 | 27 | @interface UIEngine : NSObject 31 | 32 | @property (nonatomic, readonly) RootVC *rootViewController; 33 | @property (nonatomic, strong) CoreEngine *engineCore; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /ExceedMVC/UI/Engine/UIEngine.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIEngine.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "UIEngine.h" 10 | 11 | #import "DBController+UserInfo.h" 12 | 13 | #import "CoreEngine.h" 14 | #import "NetController.h" 15 | 16 | @interface UIEngine () { 17 | 18 | RootVC *_rootViewController; 19 | } 20 | @property (nonatomic, strong) RootVC *rootViewController; 21 | @end 22 | 23 | @implementation UIEngine 24 | 25 | @synthesize rootViewController = _rootViewController; 26 | 27 | - (id)init 28 | { 29 | self = [super init]; 30 | if (self) { 31 | // Custom initialization 32 | // 33 | self.rootViewController = [[RootVC alloc] init]; 34 | self.rootViewController.delegate = self; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)dealloc 40 | { 41 | } 42 | 43 | 44 | #pragma mark - Public 45 | 46 | 47 | #pragma mark - RootVCDelegate 48 | 49 | // 是否为第一次显示 50 | - (void)rootVC:(RootVC *)rootVC didFirstAppear:(BOOL)firstAppear 51 | { 52 | if (firstAppear) { 53 | if (self.engineCore.online) { 54 | MainVC *mainVC = [[MainVC alloc] init]; 55 | mainVC.dataSource = self; 56 | [_rootViewController presentViewController:mainVC animated:NO completion:nil]; 57 | } 58 | else { 59 | LoginVC *loginVC = [[LoginVC alloc] init]; 60 | loginVC.delegate = self; 61 | UINavigationController *navLogin = [[UINavigationController alloc] initWithRootViewController:loginVC]; 62 | [_rootViewController presentViewController:navLogin animated:NO completion:nil]; 63 | } 64 | } 65 | } 66 | 67 | 68 | #pragma mark - ChatVCDataSource 69 | 70 | // 查询姓名 71 | - (NSString *)chatVC:(ChatVC *)chatVC getUserNameOf:(UInt64)userID 72 | { 73 | return [DBController getUserNameOf:userID]; 74 | } 75 | 76 | 77 | #pragma mark - ChatVCDelegate 78 | 79 | // 进入用户详细资料页面 80 | - (void)chatVCShowUserInfo:(ChatVC *)chatVC 81 | { 82 | ContactInfoVC *contactInfoVC = [[ContactInfoVC alloc] init]; 83 | contactInfoVC.userID = chatVC.friendID; 84 | contactInfoVC.dataSource = self; 85 | contactInfoVC.delegate = self; 86 | [chatVC.navigationController pushViewController:contactInfoVC animated:YES]; 87 | } 88 | 89 | 90 | #pragma mark - ContactInfoVCDataSource 91 | 92 | // 加载已有资料 93 | - (void)contactInfoVC:(ContactInfoVC *)contactInfoVC 94 | loadUserInfo:(UserInfo *)userInfo 95 | { 96 | //加载已有资料 97 | } 98 | 99 | 100 | #pragma mark - ContactInfoVCDelegate 101 | 102 | // 更新用户资料 103 | - (void)contactInfoVCGetUserInfo:(ContactInfoVC *)contactInfoVC 104 | { 105 | [[NetController sharedInstance] getUserInfoOf:contactInfoVC.userID]; 106 | } 107 | 108 | 109 | #pragma mark - LoginVCDelegate 110 | 111 | - (void)loginVC:(LoginVC *)loginVC loginWithUserName:(NSString *)userName 112 | andPasswrod:(NSString *)password 113 | { 114 | [[NetController sharedInstance] loginWithUserName:userName andPassword:password]; 115 | } 116 | 117 | - (void)loginVCLoginSuccess:(LoginVC *)loginVC 118 | { 119 | [self.rootViewController dismissViewControllerAnimated:NO completion:nil]; 120 | // 121 | MainVC *mainVC = [[MainVC alloc] init]; 122 | mainVC.dataSource = self; 123 | [self.rootViewController presentViewController:mainVC animated:NO completion:nil]; 124 | [mainVC presentViewController:loginVC.navigationController animated:NO completion:nil]; 125 | 126 | [mainVC dismissViewControllerAnimated:YES completion:nil]; 127 | } 128 | 129 | 130 | #pragma mark - AboutVCDelegate 131 | 132 | - (void)aboutVCClose:(AboutVC *)aboutVC 133 | { 134 | [aboutVC.navigationController popViewControllerAnimated:YES]; 135 | } 136 | 137 | 138 | #pragma mark - Private 139 | 140 | @end 141 | -------------------------------------------------------------------------------- /ExceedMVC/UI/OtherView/MBProgressHUD.h: -------------------------------------------------------------------------------- 1 | // 2 | // MBProgressHUD.h 3 | // Version 0.5 4 | // Created by Matej Bukovinski on 2.4.09. 5 | // 6 | 7 | // This code is distributed under the terms and conditions of the MIT license. 8 | 9 | // Copyright (c) 2011 Matej Bukovinski 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | #import 30 | #import 31 | #import 32 | 33 | @protocol MBProgressHUDDelegate; 34 | 35 | 36 | typedef enum { 37 | /** Progress is shown using an UIActivityIndicatorView. This is the default. */ 38 | MBProgressHUDModeIndeterminate, 39 | /** Progress is shown using a round, pie-chart like, progress view. */ 40 | MBProgressHUDModeDeterminate, 41 | /** Progress is shown using a ring-shaped progress view. */ 42 | MBProgressHUDModeAnnularDeterminate, 43 | /** Shows a custom view */ 44 | MBProgressHUDModeCustomView, 45 | /** Shows only labels */ 46 | MBProgressHUDModeText 47 | } MBProgressHUDMode; 48 | 49 | typedef enum { 50 | /** Opacity animation */ 51 | MBProgressHUDAnimationFade, 52 | /** Opacity + scale animation */ 53 | MBProgressHUDAnimationZoom, 54 | MBProgressHUDAnimationZoomOut = MBProgressHUDAnimationZoom, 55 | MBProgressHUDAnimationZoomIn 56 | } MBProgressHUDAnimation; 57 | 58 | 59 | #ifndef MB_STRONG 60 | #if __has_feature(objc_arc) 61 | #define MB_STRONG strong 62 | #else 63 | #define MB_STRONG retain 64 | #endif 65 | #endif 66 | 67 | #ifndef MB_WEAK 68 | #if __has_feature(objc_arc_weak) 69 | #define MB_WEAK weak 70 | #elif __has_feature(objc_arc) 71 | #define MB_WEAK unsafe_unretained 72 | #else 73 | #define MB_WEAK assign 74 | #endif 75 | #endif 76 | 77 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 78 | #define MBLabelAlignmentCenter NSTextAlignmentCenter 79 | #else 80 | #define MBLabelAlignmentCenter UITextAlignmentCenter 81 | #endif 82 | 83 | #if NS_BLOCKS_AVAILABLE 84 | typedef void (^MBProgressHUDCompletionBlock)(); 85 | #endif 86 | 87 | 88 | 89 | 90 | /** 91 | * Displays a simple HUD window containing a progress indicator and two optional labels for short messages. 92 | * 93 | * This is a simple drop-in class for displaying a progress HUD view similar to Apple's private UIProgressHUD class. 94 | * The MBProgressHUD window spans over the entire space given to it by the initWithFrame constructor and catches all 95 | * user input on this region, thereby preventing the user operations on components below the view. The HUD itself is 96 | * drawn centered as a rounded semi-transparent view which resizes depending on the user specified content. 97 | * 98 | * This view supports four modes of operation: 99 | * - MBProgressHUDModeIndeterminate - shows a UIActivityIndicatorView 100 | * - MBProgressHUDModeDeterminate - shows a custom round progress indicator 101 | * - MBProgressHUDModeAnnularDeterminate - shows a custom annular progress indicator 102 | * - MBProgressHUDModeCustomView - shows an arbitrary, user specified view (@see customView) 103 | * 104 | * All three modes can have optional labels assigned: 105 | * - If the labelText property is set and non-empty then a label containing the provided content is placed below the 106 | * indicator view. 107 | * - If also the detailsLabelText property is set then another label is placed below the first label. 108 | */ 109 | @interface MBProgressHUD : UIView 110 | 111 | /** 112 | * Creates a new HUD, adds it to provided view and shows it. The counterpart to this method is hideHUDForView:animated:. 113 | * 114 | * @param view The view that the HUD will be added to 115 | * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use 116 | * animations while appearing. 117 | * @return A reference to the created HUD. 118 | * 119 | * @see hideHUDForView:animated: 120 | * @see animationType 121 | */ 122 | + (MBProgressHUD *)showHUDAddedTo:(UIView *)view animated:(BOOL)animated; 123 | 124 | /** 125 | * Finds the top-most HUD subview and hides it. The counterpart to this method is showHUDAddedTo:animated:. 126 | * 127 | * @param view The view that is going to be searched for a HUD subview. 128 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 129 | * animations while disappearing. 130 | * @return YES if a HUD was found and removed, NO otherwise. 131 | * 132 | * @see showHUDAddedTo:animated: 133 | * @see animationType 134 | */ 135 | + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated; 136 | 137 | /** 138 | * Finds all the HUD subviews and hides them. 139 | * 140 | * @param view The view that is going to be searched for HUD subviews. 141 | * @param animated If set to YES the HUDs will disappear using the current animationType. If set to NO the HUDs will not use 142 | * animations while disappearing. 143 | * @return the number of HUDs found and removed. 144 | * 145 | * @see hideAllHUDForView:animated: 146 | * @see animationType 147 | */ 148 | + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated; 149 | 150 | /** 151 | * Finds the top-most HUD subview and returns it. 152 | * 153 | * @param view The view that is going to be searched. 154 | * @return A reference to the last HUD subview discovered. 155 | */ 156 | + (MBProgressHUD *)HUDForView:(UIView *)view; 157 | 158 | /** 159 | * Finds all HUD subviews and returns them. 160 | * 161 | * @param view The view that is going to be searched. 162 | * @return All found HUD views (array of MBProgressHUD objects). 163 | */ 164 | + (NSArray *)allHUDsForView:(UIView *)view; 165 | 166 | /** 167 | * A convenience constructor that initializes the HUD with the window's bounds. Calls the designated constructor with 168 | * window.bounds as the parameter. 169 | * 170 | * @param window The window instance that will provide the bounds for the HUD. Should be the same instance as 171 | * the HUD's superview (i.e., the window that the HUD will be added to). 172 | */ 173 | - (id)initWithWindow:(UIWindow *)window; 174 | 175 | /** 176 | * A convenience constructor that initializes the HUD with the view's bounds. Calls the designated constructor with 177 | * view.bounds as the parameter 178 | * 179 | * @param view The view instance that will provide the bounds for the HUD. Should be the same instance as 180 | * the HUD's superview (i.e., the view that the HUD will be added to). 181 | */ 182 | - (id)initWithView:(UIView *)view; 183 | 184 | /** 185 | * Display the HUD. You need to make sure that the main thread completes its run loop soon after this method call so 186 | * the user interface can be updated. Call this method when your task is already set-up to be executed in a new thread 187 | * (e.g., when using something like NSOperation or calling an asynchronous call like NSURLRequest). 188 | * 189 | * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use 190 | * animations while appearing. 191 | * 192 | * @see animationType 193 | */ 194 | - (void)show:(BOOL)animated; 195 | 196 | /** 197 | * Hide the HUD. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to 198 | * hide the HUD when your task completes. 199 | * 200 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 201 | * animations while disappearing. 202 | * 203 | * @see animationType 204 | */ 205 | - (void)hide:(BOOL)animated; 206 | 207 | /** 208 | * Hide the HUD after a delay. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to 209 | * hide the HUD when your task completes. 210 | * 211 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 212 | * animations while disappearing. 213 | * @param delay Delay in secons until the HUD is hidden. 214 | * 215 | * @see animationType 216 | */ 217 | - (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay; 218 | 219 | /** 220 | * Shows the HUD while a background task is executing in a new thread, then hides the HUD. 221 | * 222 | * This method also takes care of autorelease pools so your method does not have to be concerned with setting up a 223 | * pool. 224 | * 225 | * @param method The method to be executed while the HUD is shown. This method will be executed in a new thread. 226 | * @param target The object that the target method belongs to. 227 | * @param object An optional object to be passed to the method. 228 | * @param animated If set to YES the HUD will (dis)appear using the current animationType. If set to NO the HUD will not use 229 | * animations while (dis)appearing. 230 | */ 231 | - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated; 232 | 233 | #if NS_BLOCKS_AVAILABLE 234 | 235 | /** 236 | * Shows the HUD while a block is executing on a background queue, then hides the HUD. 237 | * 238 | * @see showAnimated:whileExecutingBlock:onQueue:completion: 239 | */ 240 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block; 241 | 242 | /** 243 | * Shows the HUD while a block is executing on a background queue, then hides the HUD. 244 | * 245 | * @see showAnimated:whileExecutingBlock:onQueue:completion: 246 | */ 247 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(MBProgressHUDCompletionBlock)completion; 248 | 249 | /** 250 | * Shows the HUD while a block is executing on the specified dispatch queue, then hides the HUD. 251 | * 252 | * @see showAnimated:whileExecutingBlock:onQueue:completion: 253 | */ 254 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue; 255 | 256 | /** 257 | * Shows the HUD while a block is executing on the specified dispatch queue, executes completion block on the main queue, and then hides the HUD. 258 | * 259 | * @param animated If set to YES the HUD will (dis)appear using the current animationType. If set to NO the HUD will 260 | * not use animations while (dis)appearing. 261 | * @param block The block to be executed while the HUD is shown. 262 | * @param queue The dispatch queue on which the block should be execouted. 263 | * @param completion The block to be executed on completion. 264 | * 265 | * @see completionBlock 266 | */ 267 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue 268 | completionBlock:(MBProgressHUDCompletionBlock)completion; 269 | 270 | /** 271 | * A block that gets called after the HUD was completely hiden. 272 | */ 273 | @property (copy) MBProgressHUDCompletionBlock completionBlock; 274 | 275 | #endif 276 | 277 | /** 278 | * MBProgressHUD operation mode. The default is MBProgressHUDModeIndeterminate. 279 | * 280 | * @see MBProgressHUDMode 281 | */ 282 | @property (assign) MBProgressHUDMode mode; 283 | 284 | /** 285 | * The animation type that should be used when the HUD is shown and hidden. 286 | * 287 | * @see MBProgressHUDAnimation 288 | */ 289 | @property (assign) MBProgressHUDAnimation animationType; 290 | 291 | /** 292 | * The UIView (e.g., a UIImageView) to be shown when the HUD is in MBProgressHUDModeCustomView. 293 | * For best results use a 37 by 37 pixel view (so the bounds match the built in indicator bounds). 294 | */ 295 | @property (MB_STRONG) UIView *customView; 296 | 297 | /** 298 | * The HUD delegate object. 299 | * 300 | * @see MBProgressHUDDelegate 301 | */ 302 | @property (MB_WEAK) id delegate; 303 | 304 | /** 305 | * An optional short message to be displayed below the activity indicator. The HUD is automatically resized to fit 306 | * the entire text. If the text is too long it will get clipped by displaying "..." at the end. If left unchanged or 307 | * set to @"", then no message is displayed. 308 | */ 309 | @property (copy) NSString *labelText; 310 | 311 | /** 312 | * An optional details message displayed below the labelText message. This message is displayed only if the labelText 313 | * property is also set and is different from an empty string (@""). The details text can span multiple lines. 314 | */ 315 | @property (copy) NSString *detailsLabelText; 316 | 317 | /** 318 | * The opacity of the HUD window. Defaults to 0.8 (80% opacity). 319 | */ 320 | @property (assign) float opacity; 321 | 322 | /** 323 | * The color of the HUD window. Defaults to black. If this property is set, color is set using 324 | * this UIColor and the opacity property is not used. using retain because performing copy on 325 | * UIColor base colors (like [UIColor greenColor]) cause problems with the copyZone. 326 | */ 327 | @property (MB_STRONG) UIColor *color; 328 | 329 | /** 330 | * The x-axis offset of the HUD relative to the centre of the superview. 331 | */ 332 | @property (assign) float xOffset; 333 | 334 | /** 335 | * The y-ayis offset of the HUD relative to the centre of the superview. 336 | */ 337 | @property (assign) float yOffset; 338 | 339 | /** 340 | * The amounth of space between the HUD edge and the HUD elements (labels, indicators or custom views). 341 | * Defaults to 20.0 342 | */ 343 | @property (assign) float margin; 344 | 345 | /** 346 | * Cover the HUD background view with a radial gradient. 347 | */ 348 | @property (assign) BOOL dimBackground; 349 | 350 | /* 351 | * Grace period is the time (in seconds) that the invoked method may be run without 352 | * showing the HUD. If the task finishes before the grace time runs out, the HUD will 353 | * not be shown at all. 354 | * This may be used to prevent HUD display for very short tasks. 355 | * Defaults to 0 (no grace time). 356 | * Grace time functionality is only supported when the task status is known! 357 | * @see taskInProgress 358 | */ 359 | @property (assign) float graceTime; 360 | 361 | /** 362 | * The minimum time (in seconds) that the HUD is shown. 363 | * This avoids the problem of the HUD being shown and than instantly hidden. 364 | * Defaults to 0 (no minimum show time). 365 | */ 366 | @property (assign) float minShowTime; 367 | 368 | /** 369 | * Indicates that the executed operation is in progress. Needed for correct graceTime operation. 370 | * If you don't set a graceTime (different than 0.0) this does nothing. 371 | * This property is automatically set when using showWhileExecuting:onTarget:withObject:animated:. 372 | * When threading is done outside of the HUD (i.e., when the show: and hide: methods are used directly), 373 | * you need to set this property when your task starts and completes in order to have normal graceTime 374 | * functionality. 375 | */ 376 | @property (assign) BOOL taskInProgress; 377 | 378 | /** 379 | * Removes the HUD from its parent view when hidden. 380 | * Defaults to NO. 381 | */ 382 | @property (assign) BOOL removeFromSuperViewOnHide; 383 | 384 | /** 385 | * Font to be used for the main label. Set this property if the default is not adequate. 386 | */ 387 | @property (MB_STRONG) UIFont* labelFont; 388 | 389 | /** 390 | * Font to be used for the details label. Set this property if the default is not adequate. 391 | */ 392 | @property (MB_STRONG) UIFont* detailsLabelFont; 393 | 394 | /** 395 | * The progress of the progress indicator, from 0.0 to 1.0. Defaults to 0.0. 396 | */ 397 | @property (assign) float progress; 398 | 399 | /** 400 | * The minimum size of the HUD bezel. Defaults to CGSizeZero (no minimum size). 401 | */ 402 | @property (assign) CGSize minSize; 403 | 404 | /** 405 | * Force the HUD dimensions to be equal if possible. 406 | */ 407 | @property (assign, getter = isSquare) BOOL square; 408 | 409 | @end 410 | 411 | 412 | @protocol MBProgressHUDDelegate 413 | 414 | @optional 415 | 416 | /** 417 | * Called after the HUD was fully hidden from the screen. 418 | */ 419 | - (void)hudWasHidden:(MBProgressHUD *)hud; 420 | 421 | @end 422 | 423 | 424 | /** 425 | * A progress view for showing definite progress by filling up a circle (pie chart). 426 | */ 427 | @interface MBRoundProgressView : UIView 428 | 429 | /** 430 | * Progress (0.0 to 1.0) 431 | */ 432 | @property (nonatomic, assign) float progress; 433 | 434 | /** 435 | * Indicator progress color. 436 | * Defaults to white [UIColor whiteColor] 437 | */ 438 | @property (nonatomic, MB_STRONG) UIColor *progressTintColor; 439 | 440 | /** 441 | * Indicator background (non-progress) color. 442 | * Defaults to translucent white (alpha 0.1) 443 | */ 444 | @property (nonatomic, MB_STRONG) UIColor *backgroundTintColor; 445 | 446 | /* 447 | * Display mode - NO = round or YES = annular. Defaults to round. 448 | */ 449 | @property (nonatomic, assign, getter = isAnnular) BOOL annular; 450 | 451 | @end 452 | -------------------------------------------------------------------------------- /ExceedMVC/UI/OtherView/MBProgressHUD.m: -------------------------------------------------------------------------------- 1 | // 2 | // MBProgressHUD.m 3 | // Version 0.5 4 | // Created by Matej Bukovinski on 2.4.09. 5 | // 6 | 7 | #import "MBProgressHUD.h" 8 | 9 | 10 | #if __has_feature(objc_arc) 11 | #define MB_AUTORELEASE(exp) exp 12 | #define MB_RELEASE(exp) exp 13 | #define MB_RETAIN(exp) exp 14 | #else 15 | #define MB_AUTORELEASE(exp) [exp autorelease] 16 | #define MB_RELEASE(exp) [exp release] 17 | #define MB_RETAIN(exp) [exp retain] 18 | #endif 19 | 20 | 21 | static const CGFloat kPadding = 4.f; 22 | static const CGFloat kLabelFontSize = 16.f; 23 | static const CGFloat kDetailsLabelFontSize = 12.f; 24 | 25 | 26 | @interface MBProgressHUD () 27 | 28 | - (void)setupLabels; 29 | - (void)registerForKVO; 30 | - (void)unregisterFromKVO; 31 | - (NSArray *)observableKeypaths; 32 | - (void)registerForNotifications; 33 | - (void)unregisterFromNotifications; 34 | - (void)updateUIForKeypath:(NSString *)keyPath; 35 | - (void)hideUsingAnimation:(BOOL)animated; 36 | - (void)showUsingAnimation:(BOOL)animated; 37 | - (void)done; 38 | - (void)updateIndicators; 39 | - (void)handleGraceTimer:(NSTimer *)theTimer; 40 | - (void)handleMinShowTimer:(NSTimer *)theTimer; 41 | - (void)setTransformForCurrentOrientation:(BOOL)animated; 42 | - (void)cleanUp; 43 | - (void)launchExecution; 44 | - (void)deviceOrientationDidChange:(NSNotification *)notification; 45 | - (void)hideDelayed:(NSNumber *)animated; 46 | 47 | @property (atomic, MB_STRONG) UIView *indicator; 48 | @property (atomic, MB_STRONG) NSTimer *graceTimer; 49 | @property (atomic, MB_STRONG) NSTimer *minShowTimer; 50 | @property (atomic, MB_STRONG) NSDate *showStarted; 51 | @property (atomic, assign) CGSize size; 52 | 53 | @end 54 | 55 | 56 | @implementation MBProgressHUD { 57 | BOOL useAnimation; 58 | SEL methodForExecution; 59 | id targetForExecution; 60 | id objectForExecution; 61 | UILabel *label; 62 | UILabel *detailsLabel; 63 | BOOL isFinished; 64 | CGAffineTransform rotationTransform; 65 | } 66 | 67 | #pragma mark - Properties 68 | 69 | @synthesize animationType; 70 | @synthesize delegate; 71 | @synthesize opacity; 72 | @synthesize color; 73 | @synthesize labelFont; 74 | @synthesize detailsLabelFont; 75 | @synthesize indicator; 76 | @synthesize xOffset; 77 | @synthesize yOffset; 78 | @synthesize minSize; 79 | @synthesize square; 80 | @synthesize margin; 81 | @synthesize dimBackground; 82 | @synthesize graceTime; 83 | @synthesize minShowTime; 84 | @synthesize graceTimer; 85 | @synthesize minShowTimer; 86 | @synthesize taskInProgress; 87 | @synthesize removeFromSuperViewOnHide; 88 | @synthesize customView; 89 | @synthesize showStarted; 90 | @synthesize mode; 91 | @synthesize labelText; 92 | @synthesize detailsLabelText; 93 | @synthesize progress; 94 | @synthesize size; 95 | #if NS_BLOCKS_AVAILABLE 96 | @synthesize completionBlock; 97 | #endif 98 | 99 | #pragma mark - Class methods 100 | 101 | + (MBProgressHUD *)showHUDAddedTo:(UIView *)view animated:(BOOL)animated { 102 | MBProgressHUD *hud = [[MBProgressHUD alloc] initWithView:view]; 103 | [view addSubview:hud]; 104 | [hud show:animated]; 105 | return MB_AUTORELEASE(hud); 106 | } 107 | 108 | + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated { 109 | MBProgressHUD *hud = [MBProgressHUD HUDForView:view]; 110 | if (hud != nil) { 111 | hud.removeFromSuperViewOnHide = YES; 112 | [hud hide:animated]; 113 | return YES; 114 | } 115 | return NO; 116 | } 117 | 118 | + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated { 119 | NSArray *huds = [self allHUDsForView:view]; 120 | for (MBProgressHUD *hud in huds) { 121 | hud.removeFromSuperViewOnHide = YES; 122 | [hud hide:animated]; 123 | } 124 | return [huds count]; 125 | } 126 | 127 | + (MBProgressHUD *)HUDForView:(UIView *)view { 128 | Class hudClass = [MBProgressHUD class]; 129 | NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator]; 130 | for (UIView *subview in subviewsEnum) { 131 | if ([subview isKindOfClass:hudClass]) { 132 | return (MBProgressHUD *)subview; 133 | } 134 | } 135 | return nil; 136 | } 137 | 138 | + (NSArray *)allHUDsForView:(UIView *)view { 139 | NSMutableArray *huds = [NSMutableArray array]; 140 | NSArray *subviews = view.subviews; 141 | Class hudClass = [MBProgressHUD class]; 142 | for (UIView *aView in subviews) { 143 | if ([aView isKindOfClass:hudClass]) { 144 | [huds addObject:aView]; 145 | } 146 | } 147 | return [NSArray arrayWithArray:huds]; 148 | } 149 | 150 | #pragma mark - Lifecycle 151 | 152 | - (id)initWithFrame:(CGRect)frame { 153 | self = [super initWithFrame:frame]; 154 | if (self) { 155 | // Set default values for properties 156 | self.animationType = MBProgressHUDAnimationFade; 157 | self.mode = MBProgressHUDModeIndeterminate; 158 | self.labelText = nil; 159 | self.detailsLabelText = nil; 160 | self.opacity = 0.8f; 161 | self.color = nil; 162 | self.labelFont = [UIFont boldSystemFontOfSize:kLabelFontSize]; 163 | self.detailsLabelFont = [UIFont boldSystemFontOfSize:kDetailsLabelFontSize]; 164 | self.xOffset = 0.0f; 165 | self.yOffset = 0.0f; 166 | self.dimBackground = NO; 167 | self.margin = 20.0f; 168 | self.graceTime = 0.0f; 169 | self.minShowTime = 0.0f; 170 | self.removeFromSuperViewOnHide = NO; 171 | self.minSize = CGSizeZero; 172 | self.square = NO; 173 | self.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin 174 | | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; 175 | 176 | // Transparent background 177 | self.opaque = NO; 178 | self.backgroundColor = [UIColor clearColor]; 179 | // Make it invisible for now 180 | self.alpha = 0.0f; 181 | 182 | taskInProgress = NO; 183 | rotationTransform = CGAffineTransformIdentity; 184 | 185 | [self setupLabels]; 186 | [self updateIndicators]; 187 | [self registerForKVO]; 188 | [self registerForNotifications]; 189 | } 190 | return self; 191 | } 192 | 193 | - (id)initWithView:(UIView *)view { 194 | NSAssert(view, @"View must not be nil."); 195 | id me = [self initWithFrame:view.bounds]; 196 | // We need to take care of rotation ourselfs if we're adding the HUD to a window 197 | if ([view isKindOfClass:[UIWindow class]]) { 198 | [self setTransformForCurrentOrientation:NO]; 199 | } 200 | return me; 201 | } 202 | 203 | - (id)initWithWindow:(UIWindow *)window { 204 | return [self initWithView:window]; 205 | } 206 | 207 | - (void)dealloc { 208 | [self unregisterFromNotifications]; 209 | [self unregisterFromKVO]; 210 | #if !__has_feature(objc_arc) 211 | [color release]; 212 | [indicator release]; 213 | [label release]; 214 | [detailsLabel release]; 215 | [labelText release]; 216 | [detailsLabelText release]; 217 | [graceTimer release]; 218 | [minShowTimer release]; 219 | [showStarted release]; 220 | [customView release]; 221 | #if NS_BLOCKS_AVAILABLE 222 | [completionBlock release]; 223 | #endif 224 | [super dealloc]; 225 | #endif 226 | } 227 | 228 | #pragma mark - Show & hide 229 | 230 | - (void)show:(BOOL)animated { 231 | useAnimation = animated; 232 | // If the grace time is set postpone the HUD display 233 | if (self.graceTime > 0.0) { 234 | self.graceTimer = [NSTimer scheduledTimerWithTimeInterval:self.graceTime target:self 235 | selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO]; 236 | } 237 | // ... otherwise show the HUD imediately 238 | else { 239 | [self setNeedsDisplay]; 240 | [self showUsingAnimation:useAnimation]; 241 | } 242 | } 243 | 244 | - (void)hide:(BOOL)animated { 245 | useAnimation = animated; 246 | // If the minShow time is set, calculate how long the hud was shown, 247 | // and pospone the hiding operation if necessary 248 | if (self.minShowTime > 0.0 && showStarted) { 249 | NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:showStarted]; 250 | if (interv < self.minShowTime) { 251 | self.minShowTimer = [NSTimer scheduledTimerWithTimeInterval:(self.minShowTime - interv) target:self 252 | selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO]; 253 | return; 254 | } 255 | } 256 | // ... otherwise hide the HUD immediately 257 | [self hideUsingAnimation:useAnimation]; 258 | } 259 | 260 | - (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay { 261 | [self performSelector:@selector(hideDelayed:) withObject:[NSNumber numberWithBool:animated] afterDelay:delay]; 262 | } 263 | 264 | - (void)hideDelayed:(NSNumber *)animated { 265 | [self hide:[animated boolValue]]; 266 | } 267 | 268 | #pragma mark - Timer callbacks 269 | 270 | - (void)handleGraceTimer:(NSTimer *)theTimer { 271 | // Show the HUD only if the task is still running 272 | if (taskInProgress) { 273 | [self setNeedsDisplay]; 274 | [self showUsingAnimation:useAnimation]; 275 | } 276 | } 277 | 278 | - (void)handleMinShowTimer:(NSTimer *)theTimer { 279 | [self hideUsingAnimation:useAnimation]; 280 | } 281 | 282 | #pragma mark - Internal show & hide operations 283 | 284 | - (void)showUsingAnimation:(BOOL)animated { 285 | if (animated && animationType == MBProgressHUDAnimationZoomIn) { 286 | self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f)); 287 | } else if (animated && animationType == MBProgressHUDAnimationZoomOut) { 288 | self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f)); 289 | } 290 | self.showStarted = [NSDate date]; 291 | // Fade in 292 | if (animated) { 293 | [UIView beginAnimations:nil context:NULL]; 294 | [UIView setAnimationDuration:0.30]; 295 | self.alpha = 1.0f; 296 | if (animationType == MBProgressHUDAnimationZoomIn || animationType == MBProgressHUDAnimationZoomOut) { 297 | self.transform = rotationTransform; 298 | } 299 | [UIView commitAnimations]; 300 | } 301 | else { 302 | self.alpha = 1.0f; 303 | } 304 | } 305 | 306 | - (void)hideUsingAnimation:(BOOL)animated { 307 | // Fade out 308 | if (animated && showStarted) { 309 | [UIView beginAnimations:nil context:NULL]; 310 | [UIView setAnimationDuration:0.30]; 311 | [UIView setAnimationDelegate:self]; 312 | [UIView setAnimationDidStopSelector:@selector(animationFinished:finished:context:)]; 313 | // 0.02 prevents the hud from passing through touches during the animation the hud will get completely hidden 314 | // in the done method 315 | if (animationType == MBProgressHUDAnimationZoomIn) { 316 | self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f)); 317 | } else if (animationType == MBProgressHUDAnimationZoomOut) { 318 | self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f)); 319 | } 320 | 321 | self.alpha = 0.02f; 322 | [UIView commitAnimations]; 323 | } 324 | else { 325 | self.alpha = 0.0f; 326 | [self done]; 327 | } 328 | self.showStarted = nil; 329 | } 330 | 331 | - (void)animationFinished:(NSString *)animationID finished:(BOOL)finished context:(void*)context { 332 | [self done]; 333 | } 334 | 335 | - (void)done { 336 | isFinished = YES; 337 | self.alpha = 0.0f; 338 | if ([delegate respondsToSelector:@selector(hudWasHidden:)]) { 339 | [delegate performSelector:@selector(hudWasHidden:) withObject:self]; 340 | } 341 | #if NS_BLOCKS_AVAILABLE 342 | if (self.completionBlock) { 343 | self.completionBlock(); 344 | self.completionBlock = NULL; 345 | } 346 | #endif 347 | if (removeFromSuperViewOnHide) { 348 | [self removeFromSuperview]; 349 | } 350 | } 351 | 352 | #pragma mark - Threading 353 | 354 | - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated { 355 | methodForExecution = method; 356 | targetForExecution = MB_RETAIN(target); 357 | objectForExecution = MB_RETAIN(object); 358 | // Launch execution in new thread 359 | self.taskInProgress = YES; 360 | [NSThread detachNewThreadSelector:@selector(launchExecution) toTarget:self withObject:nil]; 361 | // Show HUD view 362 | [self show:animated]; 363 | } 364 | 365 | #if NS_BLOCKS_AVAILABLE 366 | 367 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block { 368 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 369 | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL]; 370 | } 371 | 372 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(void (^)())completion { 373 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 374 | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:completion]; 375 | } 376 | 377 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue { 378 | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL]; 379 | } 380 | 381 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue 382 | completionBlock:(MBProgressHUDCompletionBlock)completion { 383 | self.taskInProgress = YES; 384 | self.completionBlock = completion; 385 | dispatch_async(queue, ^(void) { 386 | block(); 387 | dispatch_async(dispatch_get_main_queue(), ^(void) { 388 | [self cleanUp]; 389 | }); 390 | }); 391 | [self show:animated]; 392 | } 393 | 394 | #endif 395 | 396 | - (void)launchExecution { 397 | @autoreleasepool { 398 | #pragma clang diagnostic push 399 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 400 | // Start executing the requested task 401 | [targetForExecution performSelector:methodForExecution withObject:objectForExecution]; 402 | #pragma clang diagnostic pop 403 | // Task completed, update view in main thread (note: view operations should 404 | // be done only in the main thread) 405 | [self performSelectorOnMainThread:@selector(cleanUp) withObject:nil waitUntilDone:NO]; 406 | } 407 | } 408 | 409 | - (void)cleanUp { 410 | taskInProgress = NO; 411 | self.indicator = nil; 412 | #if !__has_feature(objc_arc) 413 | [targetForExecution release]; 414 | [objectForExecution release]; 415 | #else 416 | targetForExecution = nil; 417 | objectForExecution = nil; 418 | #endif 419 | [self hide:useAnimation]; 420 | } 421 | 422 | #pragma mark - UI 423 | 424 | - (void)setupLabels { 425 | label = [[UILabel alloc] initWithFrame:self.bounds]; 426 | label.adjustsFontSizeToFitWidth = NO; 427 | label.textAlignment = MBLabelAlignmentCenter; 428 | label.opaque = NO; 429 | label.backgroundColor = [UIColor clearColor]; 430 | label.textColor = [UIColor whiteColor]; 431 | label.font = self.labelFont; 432 | label.text = self.labelText; 433 | [self addSubview:label]; 434 | 435 | detailsLabel = [[UILabel alloc] initWithFrame:self.bounds]; 436 | detailsLabel.font = self.detailsLabelFont; 437 | detailsLabel.adjustsFontSizeToFitWidth = NO; 438 | detailsLabel.textAlignment = MBLabelAlignmentCenter; 439 | detailsLabel.opaque = NO; 440 | detailsLabel.backgroundColor = [UIColor clearColor]; 441 | detailsLabel.textColor = [UIColor whiteColor]; 442 | detailsLabel.numberOfLines = 0; 443 | detailsLabel.font = self.detailsLabelFont; 444 | detailsLabel.text = self.detailsLabelText; 445 | [self addSubview:detailsLabel]; 446 | } 447 | 448 | - (void)updateIndicators { 449 | 450 | BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]]; 451 | BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]]; 452 | 453 | if (mode == MBProgressHUDModeIndeterminate && !isActivityIndicator) { 454 | // Update to indeterminate indicator 455 | [indicator removeFromSuperview]; 456 | self.indicator = MB_AUTORELEASE([[UIActivityIndicatorView alloc] 457 | initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]); 458 | [(UIActivityIndicatorView *)indicator startAnimating]; 459 | [self addSubview:indicator]; 460 | } 461 | else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) { 462 | if (!isRoundIndicator) { 463 | // Update to determinante indicator 464 | [indicator removeFromSuperview]; 465 | self.indicator = MB_AUTORELEASE([[MBRoundProgressView alloc] init]); 466 | [self addSubview:indicator]; 467 | } 468 | if (mode == MBProgressHUDModeAnnularDeterminate) { 469 | [(MBRoundProgressView *)indicator setAnnular:YES]; 470 | } 471 | } 472 | else if (mode == MBProgressHUDModeCustomView && customView != indicator) { 473 | // Update custom view indicator 474 | [indicator removeFromSuperview]; 475 | self.indicator = customView; 476 | [self addSubview:indicator]; 477 | } else if (mode == MBProgressHUDModeText) { 478 | [indicator removeFromSuperview]; 479 | self.indicator = nil; 480 | } 481 | } 482 | 483 | #pragma mark - Layout 484 | 485 | - (void)layoutSubviews { 486 | 487 | // Entirely cover the parent view 488 | UIView *parent = self.superview; 489 | if (parent) { 490 | self.frame = parent.bounds; 491 | } 492 | CGRect bounds = self.bounds; 493 | 494 | // Determine the total widt and height needed 495 | CGFloat maxWidth = bounds.size.width - 4 * margin; 496 | CGSize totalSize = CGSizeZero; 497 | 498 | CGRect indicatorF = indicator.bounds; 499 | indicatorF.size.width = MIN(indicatorF.size.width, maxWidth); 500 | totalSize.width = MAX(totalSize.width, indicatorF.size.width); 501 | totalSize.height += indicatorF.size.height; 502 | 503 | CGSize labelSize = [label.text sizeWithFont:label.font]; 504 | labelSize.width = MIN(labelSize.width, maxWidth); 505 | totalSize.width = MAX(totalSize.width, labelSize.width); 506 | totalSize.height += labelSize.height; 507 | if (labelSize.height > 0.f && indicatorF.size.height > 0.f) { 508 | totalSize.height += kPadding; 509 | } 510 | 511 | CGFloat remainingHeight = bounds.size.height - totalSize.height - kPadding - 4 * margin; 512 | CGSize maxSize = CGSizeMake(maxWidth, remainingHeight); 513 | CGSize detailsLabelSize = [detailsLabel.text sizeWithFont:detailsLabel.font 514 | constrainedToSize:maxSize lineBreakMode:detailsLabel.lineBreakMode]; 515 | totalSize.width = MAX(totalSize.width, detailsLabelSize.width); 516 | totalSize.height += detailsLabelSize.height; 517 | if (detailsLabelSize.height > 0.f && (indicatorF.size.height > 0.f || labelSize.height > 0.f)) { 518 | totalSize.height += kPadding; 519 | } 520 | 521 | totalSize.width += 2 * margin; 522 | totalSize.height += 2 * margin; 523 | 524 | // Position elements 525 | CGFloat yPos = roundf(((bounds.size.height - totalSize.height) / 2)) + margin + yOffset; 526 | CGFloat xPos = xOffset; 527 | indicatorF.origin.y = yPos; 528 | indicatorF.origin.x = roundf((bounds.size.width - indicatorF.size.width) / 2) + xPos; 529 | indicator.frame = indicatorF; 530 | yPos += indicatorF.size.height; 531 | 532 | if (labelSize.height > 0.f && indicatorF.size.height > 0.f) { 533 | yPos += kPadding; 534 | } 535 | CGRect labelF; 536 | labelF.origin.y = yPos; 537 | labelF.origin.x = roundf((bounds.size.width - labelSize.width) / 2) + xPos; 538 | labelF.size = labelSize; 539 | label.frame = labelF; 540 | yPos += labelF.size.height; 541 | 542 | if (detailsLabelSize.height > 0.f && (indicatorF.size.height > 0.f || labelSize.height > 0.f)) { 543 | yPos += kPadding; 544 | } 545 | CGRect detailsLabelF; 546 | detailsLabelF.origin.y = yPos; 547 | detailsLabelF.origin.x = roundf((bounds.size.width - detailsLabelSize.width) / 2) + xPos; 548 | detailsLabelF.size = detailsLabelSize; 549 | detailsLabel.frame = detailsLabelF; 550 | 551 | // Enforce minsize and quare rules 552 | if (square) { 553 | CGFloat max = MAX(totalSize.width, totalSize.height); 554 | if (max <= bounds.size.width - 2 * margin) { 555 | totalSize.width = max; 556 | } 557 | if (max <= bounds.size.height - 2 * margin) { 558 | totalSize.height = max; 559 | } 560 | } 561 | if (totalSize.width < minSize.width) { 562 | totalSize.width = minSize.width; 563 | } 564 | if (totalSize.height < minSize.height) { 565 | totalSize.height = minSize.height; 566 | } 567 | 568 | self.size = totalSize; 569 | } 570 | 571 | #pragma mark BG Drawing 572 | 573 | - (void)drawRect:(CGRect)rect { 574 | 575 | CGContextRef context = UIGraphicsGetCurrentContext(); 576 | UIGraphicsPushContext(context); 577 | 578 | if (self.dimBackground) { 579 | //Gradient colours 580 | size_t gradLocationsNum = 2; 581 | CGFloat gradLocations[2] = {0.0f, 1.0f}; 582 | CGFloat gradColors[8] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.75f}; 583 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 584 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradColors, gradLocations, gradLocationsNum); 585 | CGColorSpaceRelease(colorSpace); 586 | //Gradient center 587 | CGPoint gradCenter= CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); 588 | //Gradient radius 589 | float gradRadius = MIN(self.bounds.size.width , self.bounds.size.height) ; 590 | //Gradient draw 591 | CGContextDrawRadialGradient (context, gradient, gradCenter, 592 | 0, gradCenter, gradRadius, 593 | kCGGradientDrawsAfterEndLocation); 594 | CGGradientRelease(gradient); 595 | } 596 | 597 | // Set background rect color 598 | if (self.color) { 599 | CGContextSetFillColorWithColor(context, self.color.CGColor); 600 | } else { 601 | CGContextSetGrayFillColor(context, 0.0f, self.opacity); 602 | } 603 | 604 | 605 | // Center HUD 606 | CGRect allRect = self.bounds; 607 | // Draw rounded HUD backgroud rect 608 | CGRect boxRect = CGRectMake(roundf((allRect.size.width - size.width) / 2) + self.xOffset, 609 | roundf((allRect.size.height - size.height) / 2) + self.yOffset, size.width, size.height); 610 | float radius = 10.0f; 611 | CGContextBeginPath(context); 612 | CGContextMoveToPoint(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect)); 613 | CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMinY(boxRect) + radius, radius, 3 * (float)M_PI / 2, 0, 0); 614 | CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMaxY(boxRect) - radius, radius, 0, (float)M_PI / 2, 0); 615 | CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMaxY(boxRect) - radius, radius, (float)M_PI / 2, (float)M_PI, 0); 616 | CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect) + radius, radius, (float)M_PI, 3 * (float)M_PI / 2, 0); 617 | CGContextClosePath(context); 618 | CGContextFillPath(context); 619 | 620 | UIGraphicsPopContext(); 621 | } 622 | 623 | #pragma mark - KVO 624 | 625 | - (void)registerForKVO { 626 | for (NSString *keyPath in [self observableKeypaths]) { 627 | [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; 628 | } 629 | } 630 | 631 | - (void)unregisterFromKVO { 632 | for (NSString *keyPath in [self observableKeypaths]) { 633 | [self removeObserver:self forKeyPath:keyPath]; 634 | } 635 | } 636 | 637 | - (NSArray *)observableKeypaths { 638 | return [NSArray arrayWithObjects:@"mode", @"customView", @"labelText", @"labelFont", 639 | @"detailsLabelText", @"detailsLabelFont", @"progress", nil]; 640 | } 641 | 642 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 643 | if (![NSThread isMainThread]) { 644 | [self performSelectorOnMainThread:@selector(updateUIForKeypath:) withObject:keyPath waitUntilDone:NO]; 645 | } else { 646 | [self updateUIForKeypath:keyPath]; 647 | } 648 | } 649 | 650 | - (void)updateUIForKeypath:(NSString *)keyPath { 651 | if ([keyPath isEqualToString:@"mode"] || [keyPath isEqualToString:@"customView"]) { 652 | [self updateIndicators]; 653 | } else if ([keyPath isEqualToString:@"labelText"]) { 654 | label.text = self.labelText; 655 | } else if ([keyPath isEqualToString:@"labelFont"]) { 656 | label.font = self.labelFont; 657 | } else if ([keyPath isEqualToString:@"detailsLabelText"]) { 658 | detailsLabel.text = self.detailsLabelText; 659 | } else if ([keyPath isEqualToString:@"detailsLabelFont"]) { 660 | detailsLabel.font = self.detailsLabelFont; 661 | } else if ([keyPath isEqualToString:@"progress"]) { 662 | if ([indicator respondsToSelector:@selector(setProgress:)]) { 663 | [(id)indicator setProgress:progress]; 664 | } 665 | return; 666 | } 667 | [self setNeedsLayout]; 668 | [self setNeedsDisplay]; 669 | } 670 | 671 | #pragma mark - Notifications 672 | 673 | - (void)registerForNotifications { 674 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 675 | [nc addObserver:self selector:@selector(deviceOrientationDidChange:) 676 | name:UIDeviceOrientationDidChangeNotification object:nil]; 677 | } 678 | 679 | - (void)unregisterFromNotifications { 680 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 681 | } 682 | 683 | - (void)deviceOrientationDidChange:(NSNotification *)notification { 684 | UIView *superview = self.superview; 685 | if (!superview) { 686 | return; 687 | } else if ([superview isKindOfClass:[UIWindow class]]) { 688 | [self setTransformForCurrentOrientation:YES]; 689 | } else { 690 | self.bounds = self.superview.bounds; 691 | [self setNeedsDisplay]; 692 | } 693 | } 694 | 695 | - (void)setTransformForCurrentOrientation:(BOOL)animated { 696 | // Stay in sync with the superview 697 | if (self.superview) { 698 | self.bounds = self.superview.bounds; 699 | [self setNeedsDisplay]; 700 | } 701 | 702 | UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; 703 | CGFloat radians = 0; 704 | if (UIInterfaceOrientationIsLandscape(orientation)) { 705 | if (orientation == UIInterfaceOrientationLandscapeLeft) { radians = -(CGFloat)M_PI_2; } 706 | else { radians = (CGFloat)M_PI_2; } 707 | // Window coordinates differ! 708 | self.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width); 709 | } else { 710 | if (orientation == UIInterfaceOrientationPortraitUpsideDown) { radians = (CGFloat)M_PI; } 711 | else { radians = 0; } 712 | } 713 | rotationTransform = CGAffineTransformMakeRotation(radians); 714 | 715 | if (animated) { 716 | [UIView beginAnimations:nil context:nil]; 717 | } 718 | [self setTransform:rotationTransform]; 719 | if (animated) { 720 | [UIView commitAnimations]; 721 | } 722 | } 723 | 724 | @end 725 | 726 | 727 | @implementation MBRoundProgressView { 728 | float _progress; 729 | BOOL _annular; 730 | } 731 | 732 | #pragma mark - Properties 733 | 734 | @synthesize progressTintColor = _progressTintColor; 735 | @synthesize backgroundTintColor = _backgroundTintColor; 736 | 737 | #pragma mark - Accessors 738 | 739 | - (float)progress { 740 | return _progress; 741 | } 742 | 743 | - (void)setProgress:(float)progress { 744 | _progress = progress; 745 | [self setNeedsDisplay]; 746 | } 747 | 748 | - (BOOL)isAnnular { 749 | return _annular; 750 | } 751 | 752 | - (void)setAnnular:(BOOL)annular { 753 | _annular = annular; 754 | [self setNeedsDisplay]; 755 | } 756 | 757 | #pragma mark - Lifecycle 758 | 759 | - (id)init { 760 | return [self initWithFrame:CGRectMake(0.f, 0.f, 37.f, 37.f)]; 761 | } 762 | 763 | - (id)initWithFrame:(CGRect)frame { 764 | self = [super initWithFrame:frame]; 765 | if (self) { 766 | self.backgroundColor = [UIColor clearColor]; 767 | self.opaque = NO; 768 | _progress = 0.f; 769 | _annular = NO; 770 | _progressTintColor = [[UIColor alloc] initWithWhite:1.f alpha:1.f]; 771 | _backgroundTintColor = [[UIColor alloc] initWithWhite:1.f alpha:.1f]; 772 | [self registerForKVO]; 773 | } 774 | return self; 775 | } 776 | 777 | - (void)dealloc { 778 | [self unregisterFromKVO]; 779 | #if !__has_feature(objc_arc) 780 | [_progressTintColor release]; 781 | [_backgroundTintColor release]; 782 | [super dealloc]; 783 | #endif 784 | } 785 | 786 | #pragma mark - Drawing 787 | 788 | - (void)drawRect:(CGRect)rect { 789 | 790 | CGRect allRect = self.bounds; 791 | CGRect circleRect = CGRectInset(allRect, 2.0f, 2.0f); 792 | CGContextRef context = UIGraphicsGetCurrentContext(); 793 | 794 | if (_annular) { 795 | // Draw background 796 | CGFloat lineWidth = 5.f; 797 | UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath]; 798 | processBackgroundPath.lineWidth = lineWidth; 799 | processBackgroundPath.lineCapStyle = kCGLineCapRound; 800 | CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); 801 | CGFloat radius = (self.bounds.size.width - lineWidth)/2; 802 | CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees 803 | CGFloat endAngle = (2 * (float)M_PI) + startAngle; 804 | [processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; 805 | [_backgroundTintColor set]; 806 | [processBackgroundPath stroke]; 807 | // Draw progress 808 | UIBezierPath *processPath = [UIBezierPath bezierPath]; 809 | processPath.lineCapStyle = kCGLineCapRound; 810 | processPath.lineWidth = lineWidth; 811 | endAngle = (self.progress * 2 * (float)M_PI) + startAngle; 812 | [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; 813 | [_progressTintColor set]; 814 | [processPath stroke]; 815 | } else { 816 | // Draw background 817 | [_progressTintColor setStroke]; 818 | [_backgroundTintColor setFill]; 819 | CGContextSetLineWidth(context, 2.0f); 820 | CGContextFillEllipseInRect(context, circleRect); 821 | CGContextStrokeEllipseInRect(context, circleRect); 822 | // Draw progress 823 | CGPoint center = CGPointMake(allRect.size.width / 2, allRect.size.height / 2); 824 | CGFloat radius = (allRect.size.width - 4) / 2; 825 | CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees 826 | CGFloat endAngle = (self.progress * 2 * (float)M_PI) + startAngle; 827 | CGContextSetRGBFillColor(context, 1.0f, 1.0f, 1.0f, 1.0f); // white 828 | CGContextMoveToPoint(context, center.x, center.y); 829 | CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0); 830 | CGContextClosePath(context); 831 | CGContextFillPath(context); 832 | } 833 | } 834 | 835 | #pragma mark - KVO 836 | 837 | - (void)registerForKVO { 838 | for (NSString *keyPath in [self observableKeypaths]) { 839 | [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; 840 | } 841 | } 842 | 843 | - (void)unregisterFromKVO { 844 | for (NSString *keyPath in [self observableKeypaths]) { 845 | [self removeObserver:self forKeyPath:keyPath]; 846 | } 847 | } 848 | 849 | - (NSArray *)observableKeypaths { 850 | return [NSArray arrayWithObjects:@"progressTintColor", @"backgroundTintColor", nil]; 851 | } 852 | 853 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 854 | [self setNeedsDisplay]; 855 | } 856 | 857 | @end 858 | -------------------------------------------------------------------------------- /ExceedMVC/UI/OtherView/UIView+MBProgressHUD.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+MBProgressHUD.h 3 | // 4 | // 5 | // Created by yjh4866 on 12-12-21. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MBProgressHUD.h" 11 | 12 | @interface UIView (MBProgressHUD) 13 | 14 | // 只显示活动指示 15 | - (MBProgressHUD *)showActivity; 16 | 17 | // 显示活动指示及文本 18 | - (MBProgressHUD *)showActivityWithText:(NSString *)text; 19 | 20 | // 隐藏活动指示 21 | - (void)hideActivity; 22 | 23 | // 不显示活动指示,只显示文本,指定显示时长 24 | - (MBProgressHUD *)showTextNoActivity:(NSString *)text timeLength:(CGFloat)time; 25 | 26 | // 显示文本及指定图片,指定显示时长 27 | - (MBProgressHUD *)showText:(NSString *)text image:(UIImage *)image timeLength:(CGFloat)time; 28 | 29 | // 显示文本及指定图片,指定显示时长 30 | - (MBProgressHUD *)showText:(NSString *)text imageName:(NSString *)imageName timeLength:(CGFloat)time; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /ExceedMVC/UI/OtherView/UIView+MBProgressHUD.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+MBProgressHUD.m 3 | // 4 | // 5 | // Created by yjh4866 on 12-12-21. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "UIView+MBProgressHUD.h" 10 | 11 | @implementation UIView (MBProgressHUD) 12 | 13 | // 只显示活动指示 14 | - (MBProgressHUD *)showActivity 15 | { 16 | MBProgressHUD *hud = [MBProgressHUD HUDForView:self]; 17 | if (nil == hud) { 18 | hud = [MBProgressHUD showHUDAddedTo:self animated:YES]; 19 | } 20 | hud.removeFromSuperViewOnHide = YES; 21 | return hud; 22 | } 23 | 24 | // 显示活动指示及文本 25 | - (MBProgressHUD *)showActivityWithText:(NSString *)text 26 | { 27 | MBProgressHUD *hud = [MBProgressHUD HUDForView:self]; 28 | if (nil == hud) { 29 | hud = [MBProgressHUD showHUDAddedTo:self animated:YES]; 30 | } 31 | hud.labelText = text; 32 | hud.removeFromSuperViewOnHide = YES; 33 | return hud; 34 | } 35 | 36 | // 移除活动指示 37 | - (void)hideActivity 38 | { 39 | [[MBProgressHUD HUDForView:self] hide:YES]; 40 | } 41 | 42 | // 不显示活动指示,只显示文本,指定显示时长 43 | - (MBProgressHUD *)showTextNoActivity:(NSString *)text timeLength:(CGFloat)time 44 | { 45 | MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self animated:YES]; 46 | hud.mode = MBProgressHUDModeText; 47 | hud.labelText = text; 48 | [hud hide:YES afterDelay:time]; 49 | return hud; 50 | } 51 | 52 | // 显示文本及指定图片,指定显示时长 53 | - (MBProgressHUD *)showText:(NSString *)text image:(UIImage *)image timeLength:(CGFloat)time 54 | { 55 | MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self animated:YES]; 56 | hud.mode = MBProgressHUDModeCustomView; 57 | hud.labelText = text; 58 | [hud hide:YES afterDelay:time]; 59 | // 60 | UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; 61 | hud.customView = imageView; 62 | return hud; 63 | } 64 | 65 | // 显示文本及指定图片,指定显示时长 66 | - (MBProgressHUD *)showText:(NSString *)text imageName:(NSString *)imageName timeLength:(CGFloat)time 67 | { 68 | MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self animated:YES]; 69 | hud.mode = MBProgressHUDModeCustomView; 70 | hud.labelText = text; 71 | [hud hide:YES afterDelay:time]; 72 | // 73 | UIImage *image = [UIImage imageNamed:imageName]; 74 | UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; 75 | hud.customView = imageView; 76 | return hud; 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /ExceedMVC/UI/RootVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // RootVC.h 3 | // ExceedMVC 4 | // 5 | // Created by CocoaChina_yangjh on 16/2/18. 6 | // Copyright © 2016年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol RootVCDelegate; 12 | 13 | @interface RootVC : UIViewController 14 | 15 | @property (nonatomic, weak) id delegate; 16 | 17 | @end 18 | 19 | 20 | @protocol RootVCDelegate 21 | 22 | @optional 23 | 24 | // 是否为第一次显示 25 | - (void)rootVC:(RootVC *)rootVC didFirstAppear:(BOOL)firstAppear; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /ExceedMVC/UI/RootVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // RootVC.m 3 | // ExceedMVC 4 | // 5 | // Created by CocoaChina_yangjh on 16/2/18. 6 | // Copyright © 2016年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "RootVC.h" 10 | 11 | @interface RootVC () 12 | @property (nonatomic, assign) BOOL firstAppear; 13 | @end 14 | 15 | @implementation RootVC 16 | 17 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 18 | { 19 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 20 | if (self) { 21 | // Custom initialization 22 | self.firstAppear = YES; 23 | } 24 | return self; 25 | } 26 | 27 | - (void)viewDidLoad { 28 | [super viewDidLoad]; 29 | // Do any additional setup after loading the view. 30 | } 31 | 32 | - (void)didReceiveMemoryWarning { 33 | [super didReceiveMemoryWarning]; 34 | // Dispose of any resources that can be recreated. 35 | } 36 | 37 | - (void)viewDidAppear:(BOOL)animated 38 | { 39 | [super viewDidAppear:animated]; 40 | 41 | if ([self.delegate respondsToSelector:@selector(rootVC:didFirstAppear:)]) { 42 | [self.delegate rootVC:self didFirstAppear:self.firstAppear]; 43 | } 44 | self.firstAppear = NO; 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /ExceedMVC/UI/VCs/AboutVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // AboutVC.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-7. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol AboutVCDelegate; 12 | 13 | @interface AboutVC : UIViewController 14 | 15 | @property (nonatomic, weak) id delegate; 16 | 17 | @end 18 | 19 | 20 | @protocol AboutVCDelegate 21 | 22 | @optional 23 | 24 | - (void)aboutVCClose:(AboutVC *)aboutVC; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /ExceedMVC/UI/VCs/AboutVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // AboutVC.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-7. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "AboutVC.h" 10 | 11 | @interface AboutVC () 12 | 13 | @end 14 | 15 | @implementation AboutVC 16 | 17 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 18 | { 19 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 20 | if (self) { 21 | // Custom initialization 22 | self.title = @"关于"; 23 | } 24 | return self; 25 | } 26 | 27 | - (void)viewDidLoad 28 | { 29 | [super viewDidLoad]; 30 | self.view.backgroundColor = [UIColor lightGrayColor]; 31 | // Do any additional setup after loading the view. 32 | if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { 33 | self.edgesForExtendedLayout = UIRectEdgeNone; 34 | } 35 | 36 | UIBarButtonItem *leftItem = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:self action:@selector(clickClose:)]; 37 | self.navigationItem.leftBarButtonItem = leftItem; 38 | } 39 | 40 | - (void)didReceiveMemoryWarning 41 | { 42 | [super didReceiveMemoryWarning]; 43 | // Dispose of any resources that can be recreated. 44 | } 45 | 46 | 47 | #pragma mark - ClickEvent 48 | 49 | - (void)clickClose:(id)sender 50 | { 51 | if ([self.delegate respondsToSelector:@selector(aboutVCClose:)]) { 52 | [self.delegate aboutVCClose:self]; 53 | } 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /ExceedMVC/UI/VCs/ChatVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // ChatVC.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol ChatVCDataSource; 12 | @protocol ChatVCDelegate; 13 | 14 | @interface ChatVC : UIViewController 15 | 16 | @property (nonatomic, assign) UInt64 friendID; 17 | 18 | @property (nonatomic, weak) id dataSource; 19 | @property (nonatomic, weak) id delegate; 20 | 21 | @end 22 | 23 | 24 | @protocol ChatVCDataSource 25 | 26 | @optional 27 | 28 | // 查询姓名(把userID带在协议方法里,而不是通过chatVC获取,好处是给ChatVC个friendID就可以加载ChatVC所有的数据,而不必另外获取userName) 29 | - (NSString *)chatVC:(ChatVC *)chatVC getUserNameOf:(UInt64)userID; 30 | 31 | @end 32 | 33 | 34 | @protocol ChatVCDelegate 35 | 36 | @optional 37 | 38 | // 进入用户详细资料页面 39 | - (void)chatVCShowUserInfo:(ChatVC *)chatVC; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /ExceedMVC/UI/VCs/ChatVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // ChatVC.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "ChatVC.h" 10 | #import "CoreEngine.h" 11 | 12 | @interface ChatVC () 13 | 14 | @end 15 | 16 | @implementation ChatVC 17 | 18 | - (void)viewDidLoad 19 | { 20 | [super viewDidLoad]; 21 | self.view.backgroundColor = [UIColor whiteColor]; 22 | // Do any additional setup after loading the view. 23 | if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { 24 | self.edgesForExtendedLayout = UIRectEdgeNone; 25 | } 26 | 27 | UIBarButtonItem *rightItem = [[UIBarButtonItem alloc] initWithTitle:@"用户资料" style:UIBarButtonItemStylePlain target:self action:@selector(clickInfo:)]; 28 | self.navigationItem.rightBarButtonItem = rightItem; 29 | 30 | NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; 31 | [defaultCenter removeObserver:self]; 32 | [defaultCenter addObserver:self selector:@selector(notifUserInfoSuccess:) 33 | name:NetUserInfoSuccess object:nil]; 34 | 35 | if ([self.dataSource respondsToSelector:@selector(chatVC:getUserNameOf:)]) { 36 | self.title = [self.dataSource chatVC:self getUserNameOf:self.friendID]; 37 | } 38 | } 39 | 40 | - (void)didReceiveMemoryWarning 41 | { 42 | [super didReceiveMemoryWarning]; 43 | // Dispose of any resources that can be recreated. 44 | } 45 | 46 | - (void)dealloc 47 | { 48 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 49 | } 50 | 51 | 52 | #pragma mark - ClickEvent 53 | 54 | - (void)clickInfo:(id)sender 55 | { 56 | if ([self.delegate respondsToSelector:@selector(chatVCShowUserInfo:)]) { 57 | [self.delegate chatVCShowUserInfo:self]; 58 | } 59 | } 60 | 61 | 62 | #pragma mark - Notification 63 | 64 | - (void)notifUserInfoSuccess:(NSNotification *)notif 65 | { 66 | UInt64 userID = [[notif.userInfo objectForKey:@"userid"] longLongValue]; 67 | NSString *userName = [notif.userInfo objectForKey:@"username"]; 68 | // 69 | if (self.friendID == userID) { 70 | self.title = userName; 71 | } 72 | } 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /ExceedMVC/UI/VCs/ChatsVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // ChatsVC.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol ChatsVCDataSource; 12 | @protocol ChatsVCDelegate; 13 | 14 | @interface ChatsVC : UIViewController 15 | 16 | @property (nonatomic, weak) id dataSource; 17 | @property (nonatomic, weak) id delegate; 18 | 19 | @end 20 | 21 | 22 | @protocol ChatsVCDataSource 23 | 24 | @optional 25 | 26 | // 加载会话列表 27 | - (void)chatsVC:(ChatsVC *)chatsVC loadChats:(NSMutableArray *)marrChat; 28 | 29 | @end 30 | 31 | 32 | @protocol ChatsVCDelegate 33 | 34 | @optional 35 | 36 | // 进入聊天页面 37 | - (void)chatsVC:(ChatsVC *)chatsVC chatWithFriend:(UInt64)friendID; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /ExceedMVC/UI/VCs/ChatsVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // ChatsVC.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "ChatsVC.h" 10 | #import "ChatsItem.h" 11 | #import "CoreEngine.h" 12 | 13 | #import "UIImageView+NBL.h" 14 | 15 | @interface ChatsVC () { 16 | 17 | NSMutableArray *_marrChat; 18 | } 19 | @property (nonatomic, strong) UITableView *tableView; 20 | @end 21 | 22 | @implementation ChatsVC 23 | 24 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 25 | { 26 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 27 | if (self) { 28 | // Custom initialization 29 | self.title = @"会话"; 30 | _marrChat = [[NSMutableArray alloc] init]; 31 | } 32 | return self; 33 | } 34 | 35 | - (void)viewDidLoad 36 | { 37 | [super viewDidLoad]; 38 | self.view.backgroundColor = [UIColor whiteColor]; 39 | // Do any additional setup after loading the view. 40 | if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { 41 | self.edgesForExtendedLayout = UIRectEdgeNone; 42 | } 43 | 44 | if (nil == self.tableView) { 45 | self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain]; 46 | self.tableView.dataSource = self; 47 | self.tableView.delegate = self; 48 | 49 | [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"ChatsCell"]; 50 | } 51 | [self.view addSubview:self.tableView]; 52 | 53 | NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; 54 | [defaultCenter removeObserver:self]; 55 | [defaultCenter addObserver:self selector:@selector(notifUserInfoSuccess:) 56 | name:NetUserInfoSuccess object:nil]; 57 | 58 | // 读取本地会话纪录 59 | if ([self.dataSource respondsToSelector:@selector(chatsVC:loadChats:)]) { 60 | [_marrChat removeAllObjects]; 61 | [self.dataSource chatsVC:self loadChats:_marrChat]; 62 | } 63 | } 64 | 65 | - (void)didReceiveMemoryWarning 66 | { 67 | [super didReceiveMemoryWarning]; 68 | // Dispose of any resources that can be recreated. 69 | } 70 | 71 | - (void)dealloc 72 | { 73 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 74 | } 75 | 76 | - (void)viewWillAppear:(BOOL)animated 77 | { 78 | [super viewWillAppear:animated]; 79 | 80 | self.tableView.frame = self.view.bounds; 81 | } 82 | 83 | 84 | #pragma mark - Notification 85 | 86 | - (void)notifUserInfoSuccess:(NSNotification *)notif 87 | { 88 | UInt64 userID = [[notif.userInfo objectForKey:@"userid"] longLongValue]; 89 | NSString *userName = [notif.userInfo objectForKey:@"username"]; 90 | NSString *avatarUrl = [notif.userInfo objectForKey:@"avatar"]; 91 | // 92 | for (ChatsItem *chatsItem in _marrChat) { 93 | if (chatsItem.userID == userID) { 94 | chatsItem.userName = userName; 95 | chatsItem.avatarUrl = avatarUrl; 96 | [self.tableView reloadData]; 97 | break; 98 | } 99 | } 100 | } 101 | 102 | 103 | #pragma mark - UITableViewDataSource 104 | 105 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 106 | { 107 | return _marrChat.count; 108 | } 109 | 110 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 111 | { 112 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ChatsCell"]; 113 | // 114 | ChatsItem *chatsItem = [_marrChat objectAtIndex:indexPath.row]; 115 | cell.textLabel.text = chatsItem.userName; 116 | cell.detailTextLabel.text = chatsItem.latestMsg; 117 | // 显示头像 118 | [cell.imageView loadImageFromCachePath:nil orPicUrl:chatsItem.avatarUrl withDownloadResult:^(UIImageView *imageView, NSString *picUrl, float progress, BOOL finished, NSError *error) { 119 | // error为nil表示下载成功 120 | if (nil == error) { 121 | [_tableView reloadData]; 122 | } 123 | }]; 124 | 125 | return cell; 126 | } 127 | 128 | 129 | #pragma mark - UITableViewDelegate 130 | 131 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 132 | { 133 | return 60.0f; 134 | } 135 | 136 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 137 | { 138 | if ([self.delegate respondsToSelector:@selector(chatsVC:chatWithFriend:)]) { 139 | ChatsItem *chatsItem = [_marrChat objectAtIndex:indexPath.row]; 140 | [self.delegate chatsVC:self chatWithFriend:chatsItem.userID]; 141 | } 142 | } 143 | 144 | @end 145 | -------------------------------------------------------------------------------- /ExceedMVC/UI/VCs/ContactInfoVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // ContactInfoVC.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class UserInfo; 12 | 13 | @protocol ContactInfoVCDataSource; 14 | @protocol ContactInfoVCDelegate; 15 | 16 | @interface ContactInfoVC : UIViewController 17 | 18 | @property (nonatomic, assign) UInt64 userID; 19 | 20 | @property (nonatomic, weak) id dataSource; 21 | @property (nonatomic, weak) id delegate; 22 | 23 | @end 24 | 25 | 26 | @protocol ContactInfoVCDataSource 27 | 28 | @optional 29 | 30 | // 加载已有资料 31 | - (void)contactInfoVC:(ContactInfoVC *)contactInfoVC 32 | loadUserInfo:(UserInfo *)userInfo; 33 | 34 | @end 35 | 36 | 37 | @protocol ContactInfoVCDelegate 38 | 39 | @optional 40 | 41 | // 更新用户资料 42 | - (void)contactInfoVCGetUserInfo:(ContactInfoVC *)contactInfoVC; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /ExceedMVC/UI/VCs/ContactInfoVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // ContactInfoVC.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "ContactInfoVC.h" 10 | #import "CoreEngine.h" 11 | #import "UserInfo.h" 12 | 13 | #import "UIImageView+NBL.h" 14 | 15 | @interface ContactInfoVC () 16 | @property (nonatomic, strong) UIImageView *imageViewAvatar; 17 | @property (nonatomic, strong) UserInfo *userInfo; 18 | @end 19 | 20 | @implementation ContactInfoVC 21 | 22 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 23 | { 24 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 25 | if (self) { 26 | // Custom initialization 27 | self.title = @"详细资料"; 28 | self.userInfo = [[UserInfo alloc] init]; 29 | } 30 | return self; 31 | } 32 | 33 | - (void)viewDidLoad 34 | { 35 | [super viewDidLoad]; 36 | self.view.backgroundColor = [UIColor whiteColor]; 37 | // Do any additional setup after loading the view. 38 | if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { 39 | self.edgesForExtendedLayout = UIRectEdgeNone; 40 | } 41 | 42 | UIBarButtonItem *updateItem = [[UIBarButtonItem alloc] initWithTitle:@"更新" style:UIBarButtonItemStylePlain target:self action:@selector(clickUpdate:)]; 43 | self.navigationItem.rightBarButtonItem = updateItem; 44 | 45 | if (nil == self.imageViewAvatar) { 46 | self.imageViewAvatar = [[UIImageView alloc] initWithFrame:CGRectMake(40.0f, 40.0f, 100.0f, 100.0f)]; 47 | self.imageViewAvatar.backgroundColor = [UIColor lightGrayColor]; 48 | } 49 | [self.view addSubview:self.imageViewAvatar]; 50 | 51 | NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; 52 | [defaultCenter removeObserver:self]; 53 | [defaultCenter addObserver:self selector:@selector(notifUserInfoSuccess:) 54 | name:NetUserInfoSuccess object:nil]; 55 | 56 | if ([self.dataSource respondsToSelector:@selector(contactInfoVC:loadUserInfo:)]) { 57 | [self.dataSource contactInfoVC:self loadUserInfo:self.userInfo]; 58 | self.title = self.userInfo.userName; 59 | } 60 | } 61 | 62 | - (void)didReceiveMemoryWarning 63 | { 64 | [super didReceiveMemoryWarning]; 65 | // Dispose of any resources that can be recreated. 66 | } 67 | 68 | - (void)dealloc 69 | { 70 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 71 | } 72 | 73 | 74 | #pragma mark - Notification 75 | 76 | - (void)notifUserInfoSuccess:(NSNotification *)notif 77 | { 78 | UInt64 userID = [[notif.userInfo objectForKey:@"userid"] longLongValue]; 79 | NSString *userName = [notif.userInfo objectForKey:@"username"]; 80 | // 81 | if (self.userID == userID) { 82 | self.title = userName; 83 | // 显示头像 84 | self.userInfo.avatarUrl = [notif.userInfo objectForKey:@"avatar"]; 85 | [self.imageViewAvatar loadImageFromCachePath:nil orPicUrl:self.userInfo.avatarUrl]; 86 | } 87 | } 88 | 89 | 90 | #pragma mark - ClickEvent 91 | 92 | - (void)clickUpdate:(id)sender 93 | { 94 | if ([self.delegate respondsToSelector:@selector(contactInfoVCGetUserInfo:)]) { 95 | [self.delegate contactInfoVCGetUserInfo:self]; 96 | } 97 | } 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /ExceedMVC/UI/VCs/ContactsVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // ContactsVC.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol ContactsVCDataSource; 12 | @protocol ContactsVCDelegate; 13 | 14 | @interface ContactsVC : UIViewController 15 | 16 | @property (nonatomic, weak) id dataSource; 17 | @property (nonatomic, weak) id delegate; 18 | 19 | @end 20 | 21 | 22 | @protocol ContactsVCDataSource 23 | 24 | @optional 25 | 26 | // 加载已有联系人 27 | - (void)contactsVC:(ContactsVC *)contactsVC loadContacts:(NSMutableArray *)marray; 28 | 29 | @end 30 | 31 | 32 | @protocol ContactsVCDelegate 33 | 34 | @optional 35 | 36 | // 进入用户详细资料页面 37 | - (void)contactsVC:(ContactsVC *)contactsVC showContactsInfo:(UInt64)userID; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /ExceedMVC/UI/VCs/ContactsVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // ContactsVC.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "ContactsVC.h" 10 | #import "CoreEngine.h" 11 | #import "UserInfo.h" 12 | 13 | #import "UIImageView+NBL.h" 14 | 15 | @interface ContactsVC () { 16 | 17 | NSMutableArray *_marrFriend; 18 | } 19 | @property (nonatomic, strong) UITableView *tableView; 20 | @end 21 | 22 | @implementation ContactsVC 23 | 24 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 25 | { 26 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 27 | if (self) { 28 | // Custom initialization 29 | self.title = @"联系人"; 30 | _marrFriend = [[NSMutableArray alloc] init]; 31 | } 32 | return self; 33 | } 34 | 35 | - (void)viewDidLoad 36 | { 37 | [super viewDidLoad]; 38 | self.view.backgroundColor = [UIColor whiteColor]; 39 | // Do any additional setup after loading the view. 40 | if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { 41 | self.edgesForExtendedLayout = UIRectEdgeNone; 42 | } 43 | 44 | if (nil == self.tableView) { 45 | self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds 46 | style:UITableViewStylePlain]; 47 | self.tableView.dataSource = self; 48 | self.tableView.delegate = self; 49 | 50 | [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"FriendCell"]; 51 | } 52 | [self.view addSubview:self.tableView]; 53 | 54 | NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; 55 | [defaultCenter removeObserver:self]; 56 | [defaultCenter addObserver:self selector:@selector(notifUserInfoSuccess:) 57 | name:NetUserInfoSuccess object:nil]; 58 | 59 | if ([self.dataSource respondsToSelector:@selector(contactsVC:loadContacts:)]) { 60 | [self.dataSource contactsVC:self loadContacts:_marrFriend]; 61 | } 62 | } 63 | 64 | - (void)didReceiveMemoryWarning 65 | { 66 | [super didReceiveMemoryWarning]; 67 | // Dispose of any resources that can be recreated. 68 | } 69 | 70 | - (void)dealloc 71 | { 72 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 73 | } 74 | 75 | - (void)viewWillAppear:(BOOL)animated 76 | { 77 | self.tableView.frame = self.view.bounds; 78 | } 79 | 80 | 81 | #pragma mark - Notification 82 | 83 | - (void)notifUserInfoSuccess:(NSNotification *)notif 84 | { 85 | UInt64 userID = [[notif.userInfo objectForKey:@"userid"] longLongValue]; 86 | NSString *userName = [notif.userInfo objectForKey:@"username"]; 87 | NSString *avatarUrl = [notif.userInfo objectForKey:@"avatar"]; 88 | // 89 | for (UserInfo *friendInfo in _marrFriend) { 90 | if (friendInfo.userID == userID) { 91 | friendInfo.userName = userName; 92 | friendInfo.avatarUrl = avatarUrl; 93 | [self.tableView reloadData]; 94 | break; 95 | } 96 | } 97 | } 98 | 99 | 100 | #pragma mark - UITableViewDataSource 101 | 102 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 103 | { 104 | return _marrFriend.count; 105 | } 106 | 107 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 108 | { 109 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"FriendCell"]; 110 | // 111 | UserInfo *friendInfo = [_marrFriend objectAtIndex:indexPath.row]; 112 | cell.textLabel.text = friendInfo.userName; 113 | // 显示头像 114 | [cell.imageView loadImageFromCachePath:nil orPicUrl:friendInfo.avatarUrl withDownloadResult:^(UIImageView *imageView, NSString *picUrl, float progress, BOOL finished, NSError *error) { 115 | // error为nil表示下载成功 116 | if (nil == error) { 117 | [self.tableView reloadData]; 118 | } 119 | }]; 120 | 121 | return cell; 122 | } 123 | 124 | 125 | #pragma mark - UITableViewDelegate 126 | 127 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 128 | { 129 | return 60.0f; 130 | } 131 | 132 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 133 | { 134 | if ([self.delegate respondsToSelector:@selector(contactsVC:showContactsInfo:)]) { 135 | UserInfo *friendInfo = [_marrFriend objectAtIndex:indexPath.row]; 136 | [self.delegate contactsVC:self showContactsInfo:friendInfo.userID]; 137 | } 138 | } 139 | 140 | @end 141 | -------------------------------------------------------------------------------- /ExceedMVC/UI/VCs/LoginVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // LoginVC.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol LoginVCDelegate; 12 | 13 | @interface LoginVC : UIViewController 14 | 15 | @property (nonatomic, weak) id delegate; 16 | 17 | @end 18 | 19 | 20 | @protocol LoginVCDelegate 21 | 22 | @optional 23 | 24 | - (void)loginVC:(LoginVC *)loginVC loginWithUserName:(NSString *)userName 25 | andPasswrod:(NSString *)password; 26 | 27 | - (void)loginVCLoginSuccess:(LoginVC *)loginVC; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /ExceedMVC/UI/VCs/LoginVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // LoginVC.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "LoginVC.h" 10 | #import "CoreEngine.h" 11 | #import "UIView+MBProgressHUD.h" 12 | 13 | @interface LoginVC () 14 | 15 | @end 16 | 17 | @implementation LoginVC 18 | 19 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 20 | { 21 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 22 | if (self) { 23 | // Custom initialization 24 | self.title = @"登录"; 25 | } 26 | return self; 27 | } 28 | 29 | - (void)viewDidLoad 30 | { 31 | [super viewDidLoad]; 32 | self.view.backgroundColor = [UIColor whiteColor]; 33 | // Do any additional setup after loading the view. 34 | if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { 35 | self.edgesForExtendedLayout = UIRectEdgeNone; 36 | } 37 | 38 | UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 39 | button.frame = CGRectMake(100.0f, 100.0f, 120.0f, 50.0f); 40 | [button setTitle:@"登录" forState:UIControlStateNormal]; 41 | [button addTarget:self action:@selector(clickLogin:) 42 | forControlEvents:UIControlEventTouchUpInside]; 43 | [self.view addSubview:button]; 44 | 45 | NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; 46 | [defaultCenter removeObserver:self]; 47 | [defaultCenter addObserver:self selector:@selector(notifLoginFailure:) 48 | name:NetLoginFailure object:nil]; 49 | [defaultCenter addObserver:self selector:@selector(notifLoginSuccess:) 50 | name:NetLoginSuccess object:nil]; 51 | } 52 | 53 | - (void)didReceiveMemoryWarning 54 | { 55 | [super didReceiveMemoryWarning]; 56 | // Dispose of any resources that can be recreated. 57 | } 58 | 59 | 60 | #pragma mark - Notification 61 | 62 | - (void)notifLoginFailure:(NSNotification *)notif 63 | { 64 | 65 | } 66 | 67 | - (void)notifLoginSuccess:(NSNotification *)notif 68 | { 69 | if ([self.delegate respondsToSelector:@selector(loginVCLoginSuccess:)]) { 70 | [self.delegate loginVCLoginSuccess:self]; 71 | } 72 | [self.navigationController.view hideActivity]; 73 | } 74 | 75 | 76 | #pragma mark - ClickEvent 77 | 78 | - (void)clickLogin:(id)sender 79 | { 80 | if ([self.delegate respondsToSelector:@selector(loginVC:loginWithUserName:andPasswrod:)]) { 81 | [self.navigationController.view showActivityWithText:@"登录中..."]; 82 | [self.delegate loginVC:self 83 | loginWithUserName:@"userName" andPasswrod:@"password"]; 84 | } 85 | else { 86 | NSLog(@"未实现LoginVC的登录协议"); 87 | } 88 | } 89 | 90 | @end 91 | -------------------------------------------------------------------------------- /ExceedMVC/UI/VCs/MainVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // MainVC.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol MainVCDataSource; 12 | 13 | @interface MainVC : UITabBarController 14 | 15 | @property (nonatomic, weak) id dataSource; 16 | 17 | @end 18 | 19 | 20 | @protocol MainVCDataSource 21 | 22 | @optional 23 | 24 | // 加载Tab页面 25 | - (void)mainVC:(MainVC *)mainVC loadViewControllers:(NSMutableArray *)marray; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /ExceedMVC/UI/VCs/MainVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // MainVC.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "MainVC.h" 10 | 11 | @interface MainVC () 12 | 13 | @property (nonatomic, assign) BOOL firstLoadVCs; 14 | 15 | @end 16 | 17 | @implementation MainVC 18 | 19 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 20 | { 21 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 22 | if (self) { 23 | // Custom initialization 24 | self.firstLoadVCs = YES; 25 | } 26 | return self; 27 | } 28 | 29 | - (void)viewDidLoad 30 | { 31 | [super viewDidLoad]; 32 | self.view.backgroundColor = [UIColor whiteColor]; 33 | // Do any additional setup after loading the view. 34 | if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { 35 | self.edgesForExtendedLayout = UIRectEdgeNone; 36 | } 37 | } 38 | 39 | - (void)didReceiveMemoryWarning 40 | { 41 | [super didReceiveMemoryWarning]; 42 | // Dispose of any resources that can be recreated. 43 | } 44 | 45 | - (void)viewDidAppear:(BOOL)animated 46 | { 47 | [super viewDidAppear:animated]; 48 | 49 | [UIApplication sharedApplication].statusBarHidden = NO; 50 | if (self.firstLoadVCs) { 51 | if ([self.dataSource respondsToSelector:@selector(mainVC:loadViewControllers:)]) { 52 | NSMutableArray *marrVC = [[NSMutableArray alloc] init]; 53 | [self.dataSource mainVC:self loadViewControllers:marrVC]; 54 | self.viewControllers = marrVC; 55 | } 56 | 57 | self.firstLoadVCs = NO; 58 | } 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /ExceedMVC/UI/VCs/MoreVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // MoreVC.h 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol MoreVCDataSource; 12 | @protocol MoreVCDelegate; 13 | 14 | @interface MoreVC : UIViewController 15 | 16 | @property (nonatomic, weak) id dataSource; 17 | @property (nonatomic, weak) id delegate; 18 | 19 | @end 20 | 21 | 22 | @protocol MoreVCDataSource 23 | 24 | @optional 25 | 26 | @end 27 | 28 | 29 | @protocol MoreVCDelegate 30 | 31 | @optional 32 | 33 | // 显示关于页面 34 | - (void)moreVCShowAbout:(MoreVC *)moreVC; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /ExceedMVC/UI/VCs/MoreVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // MoreVC.m 3 | // ExceedMVC 4 | // 5 | // Created by yangjh on 13-6-6. 6 | // Copyright (c) 2013年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import "MoreVC.h" 10 | 11 | @interface MoreVC () 12 | 13 | @end 14 | 15 | @implementation MoreVC 16 | 17 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 18 | { 19 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 20 | if (self) { 21 | // Custom initialization 22 | self.title = @"更多"; 23 | } 24 | return self; 25 | } 26 | 27 | - (void)viewDidLoad 28 | { 29 | [super viewDidLoad]; 30 | self.view.backgroundColor = [UIColor whiteColor]; 31 | // Do any additional setup after loading the view. 32 | if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { 33 | self.edgesForExtendedLayout = UIRectEdgeNone; 34 | } 35 | 36 | UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 37 | button.frame = CGRectMake(100.0f, 200.0f, 120.0f, 50.0f); 38 | [button setTitle:@"关于" forState:UIControlStateNormal]; 39 | [button addTarget:self action:@selector(clickAbout:) 40 | forControlEvents:UIControlEventTouchUpInside]; 41 | [self.view addSubview:button]; 42 | } 43 | 44 | - (void)didReceiveMemoryWarning 45 | { 46 | [super didReceiveMemoryWarning]; 47 | // Dispose of any resources that can be recreated. 48 | } 49 | 50 | 51 | #pragma mark - ClickEvent 52 | 53 | - (void)clickAbout:(id)sender 54 | { 55 | if ([self.delegate respondsToSelector:@selector(moreVCShowAbout:)]) { 56 | [self.delegate moreVCShowAbout:self]; 57 | } 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /ExceedMVC/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ExceedMVC 4 | // 5 | // Created by CocoaChina_yangjh on 16/2/18. 6 | // Copyright © 2016年 yjh4866. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | --------------------------------------------------------------------------------