iOS 开发实训第八周周报
一、学习笔记
-
单例模式:
-
单例模式用于保证一个类只有一个实例,在不同类中使用单例对象时,保证获取的都是同一个对象
-
在
iOS
中应用广泛,在系统提供类中,UIApplication
、NSUserDefault
、NSNotificationCenter
、NSBundle
等都是单例类 -
单例模式的实现原理就是要保证单例类对象的
alloc
和init
操作在应用的整个生命周期中只会执行一次,当单例类对象被创建后,其他地方调用该对象时就直接返回已存在的对象即可,在iOS
中的实现,为了保证alloc
和init
只执行一次,可以使用dispatch_once
函数,dispatch_once
函数的作用就是在整个应用生命周期中只能执行一次的代码块,具体实现如下:- SingletonClass.h
#import <Foundation/Foundation.h> @interface SingletonClass : NSObject + (instancetype)singletonInstance; @end
- SingletonClass.m
#import "SingletonClass.h" @implementation SingletonClass static SingletonClass *singletonInstance = nil; #pragma mark - ARC + (instancetype)singletonInstance { static dispatch_once_t once; dispatch_once(&once, ^{ singletonInstance = [[self alloc] init]; }); return singletonInstance; } + (instancetype)allocWithZone:(struct _NSZone *)zone { static dispatch_once_t once; dispatch_once(&once, ^{ singletonInstance = [super allocWithZone:zone]; }); return singletonInstance; } - (id)copyWithZone:(struct _NSZone *)zone { return singletonInstance; // copy方法也是直接返回实例对象 } @end
-
-
MVC架构:
-
MVC模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能,使程序结构更加直观,即使得程序低耦合、可复用、结构简洁清晰,包括三个模块,各模块的定义和功能如下:
- 模型 Model:数据处理层,包括网络请求、数据加工、数据库操作等
- 视图 View:所有App上看得到的界面
- 控制器 Controller:Model和View的中介,将Model返回的数据在View上展示出来
-
在iOS下,MVC架构可以用下图表示
-
如何在代码中应用MVC模式:
-
很多初学者都会把
View
写在Controller
里,比如@implementation DemoViewController - (void)viewDidLoad { [super viewDidLoad]; // setupUI UIView *view = [[UIView alloc]init]; view.frame = CGRectMake(100, 100, 100, 100); view.backgroundColor = [UIColor orangeColor]; [self.view addSubview:view]; UIButton *btn = [UIButton buttonWithType:UIButtonTypeInfoDark]; btn.center = self.view.center; [self.view addSubview:btn]; // ... }
-
优点:
- 比如按钮,可以在当前控制器直接添加目标,添加点击事件,在当前控制器内就能调用到点击方法,不需要设置代理之类的
- 比如要找某个界面,直接切到这个界面对应的
Controller
就行,因为View
写在Controller
里面,不用去别的地方找 - 比如一个
View
,里面有一张图片,图片依赖于网络资源,这样写的好处,可以直接让View
在控制器中就能拿到资源,不需要传值
-
缺点:
- 导致
Controller
特别臃肿,里面代码特别多,因为对于复杂的View
,代码量可能超过1000行,导致Controller
难以维护 - 写在
Controller
里的View
的代码无法复用
- 导致
-
将
View
和Controller
分离:将View
封装成一个视图类,视图类里的点击事件通过代理回调,在Controller
中实现回调事件// View @implementation DemoView - (instancetype)initWithTitleStr:(NSString *)titleStr { if (self = [super init]) { UIButton *btn = [UIButton buttonWithType:UIButtonTypeInfoDark]; [self addSubview:btn]; [btn addTarget:self action:@selector(p_clickBtn:) forControlEvents:UIControlEventTouchUpInside]; // ... } return self; } - (void)p_clickBtn:(UIButton *)sender { // 通过代理回调 [_delegate respondsToSelector:@selector(clickBtn:)] [_delegate clickBtn:sender] : nil; } // Controller @implementation DemoViewController - (void)viewDidLoad { [super viewDidLoad]; // setupUI DemoView *view = [DemoView viewWithTitleStr:@"我是参数"]; // 传递参数 view.delegate = self; [self.view addSubview:view]; } #pragma mark - privateDelegate - (void)clickBtn:(UIButton *)sender { // View层按钮的点击事件回调 }
-
-
将网络访问、数据库操作等放在
Controller
中也是很常见的问题,比如@implementation DemoViewController - (void)viewDidLoad { [super viewDidLoad]; // loadDatas [[AFHTTPSessionManager manager]GET:url parameters:parameters progress:nil success:^(NSURLSessionDataTask* _Nonnull task, id _Nullable responseObject) { // 刷新tableView _datas = responseObject; [_tableView reloadDatas]; } failure:nil]; }
-
优点:
- 简单,网络请求完,直接在当前控制器刷新
TableView
的数据源 - 如果当前网络请求接口,需要外部参数,比如前一个界面的id,这样写可以直接让当前请求在控制器中就能拿到资源,不需要传值
- 简单,网络请求完,直接在当前控制器刷新
-
缺点:
- 也会导致
Controller
特别臃肿,难以维护 - 无法复用
- 如果某些借口有依赖要求,比如借口一请求完再请求借口二,嵌套起来导致代码结构过于复杂
- 也会导致
-
将
Model
和Controller
分离:按需要建立数据模型类,对数据的操作都放在模型类里,通过函数参数传值,通过Block
回调// Model @implementation DemoModel + (void)fetchDatasWithUUid:(NSString *)uuid success:(successBlock)block { // Model发送网络请求 NSDictionary *parameters = @{@"uuid":uuid} [[AFHTTPSessionManager manager]GET:url parameters:parameters progress:nil success:^(NSURLSessionDataTask* _Nonnull task, id _Nullable responseObject) { // 通过block异步回调~ block(responseObject); } failure:nil]; } // Controller @implementation DemoViewController - (void)viewDidLoad { [super viewDidLoad]; // loadDatas [DemoModel fetchDatasWithUUid:_uuid success:^(NSArray *array) { _datas = array; [_tableView reloadDatas]; }]; }
-
-
-
-
MVVM架构:
-
虽然
MVC
的层次明确,但是由于功能日益的增加、代码的维护,使得更多的代码被写在了Controller
中,这样Controller
还是会变得非常臃肿,所以在MVC
的基础上衍生出了一种新的架构模式MVVM
架构 -
MVC和MVVM的架构模式图对比:
- MVC
在MVC中,Controller要做的事还是太多,包括表示逻辑、业务逻辑,随着功能的增加代码量会过大
- MVVM
-
对比:
-
MVVM
就是在MVC
的基础上分离出业务处理的逻辑到ViewModel
层,简单来说,就是API请求完数据,解析成Model
,之后在ViewModel
中转化成能够直接被视图层使用的数据,交付给View
,下面是一个实例,分别用MVC
和MVVM
架构实现同一个功能:-
功能:一个页面,判断用户是否手动设置了用户名,如果设置了则正常显示用户名;如果没有设置,则显示“未设置用户名”
-
MVC
- Model:
// User.h #import <Foundation/Foundation.h> @interface User : NSObject @property (nonatomic, copy) NSString *userName; @property (nonatomic, assign) NSInteger userId; - (instancetype)initWithUserName:(NSString *)userName userId:(NSInteger)userId; @end // User.m @implementation User - (instancetype)initWithUserName:(NSString *)userName userId:(NSInteger)userId { self = [super init]; if (!self) return nil; _userName = userName; _userId = userId; return self; } @end
- ViewController:
// DemoViewController.h #import "HomeViewController.h" #import "User.h" @interface DemoViewController () @property (nonatomic, strong) UILabel *lb_userName; @property (nonatomic, strong) User *user; @end // DemoViewController.m @implementation DemoViewController - (void)viewDidLoad { [super viewDidLoad]; // 创建User实例并初始化 if (_user.userName.length > 0) { _lb_userName.text = _user.userName; } else { _lb_userName.text = [NSString stringWithFormat:@"未设置用户名"]; } } @end
-
MVVM
- Model:
// User.h #import <Foundation/Foundation.h> @interface User : NSObject @property (nonatomic, copy) NSString *userName; @property (nonatomic, assign) NSInteger userId; @end
- ViewModel:
// UserViewModel.h #import <Foundation/Foundation.h> #import "User.h" @interface UserViewModel : NSObject @property (nonatomic, strong) User *user; @property (nonatomic, copy) NSString *userName; - (instancetype)initWithUserName:(NSString *)userName userId:(NSInteger)userId; @end // UserViewModel.m #import "UserViewModel.h" @implementation UserViewModel - (instancetype)initWithUserName:(NSString *)userName userId:(NSInteger)userId { self = [super init]; if (!self) return nil; _user = [[User alloc] initWithUserName:userName userId:userId]; if (_user.userName.length > 0) { _userName = _user.userName; } else { _userName = [NSString stringWithFormat:@"未设置用户名"]; } return self; } @end
- Controller:
// DemoViewController.h #import "HomeViewController.h" #import "UserViewModel.h" @interface HomeViewController () @property (nonatomic, strong) UILabel *lb_userName; @property (nonatomic, strong) UserViewModel *userViewModel; @end // DemoViewController.m @implementation HomeViewController - (void)viewDidLoad { [super viewDidLoad]; _userViewModel = [[UserViewModel alloc] initWithUserName:@"liu" userId:123456]; _lb_userName.text = _userViewModel.userName; } @end
-
对比可知,
MVVM
将MVC
的Controller
中的业务逻辑等剥离出来放在ViewModel
中,那么在ViewController
中就不需要进行判断,只需要将数据显示到View
上,如下图所示
-
-
优点:
- 低耦合:
View
可以独立于Model
变化和修改,一个ViewModel
可以绑定到不同的View
上,当View
变化的时候Model
可以不变,当Model
变化的时候View
也可以不变 - 可重用性:可以把一些视图逻辑放在一个
ViewModel
里面,让很多View
重用这段视图逻辑 - 可测试性:界面素来是比较难以测试的,而现在测试可以针对
ViewModel
来写
- 低耦合:
-
二、遇到的问题及解决方法
-
UITableViewController
如何去除多余的单元格分割线:- 当单元格
Cell
的个数不够覆盖整个View
时,系统会默认自动添加一些分割线,去除的方法为用一个空的UIView
作为TableFooterView
// 设置tableFooterView UIView *view = [[UIView alloc] init]; view.backgroundColor = [UIColor clearColor]; self.tableView.tableFooterView = view;
- 当单元格