一、学习笔记
- 单例模式:
- 单例模式用于保证一个类只有一个实例,在不同类中使用单例对象时,保证获取的都是同一个对象
- 在
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;