一、学习笔记

  • 单例模式:
  • 单例模式用于保证一个类只有一个实例,在不同类中使用单例对象时,保证获取的都是同一个对象
  • iOS中应用广泛,在系统提供类中,UIApplicationNSUserDefaultNSNotificationCenterNSBundle等都是单例类
  • 单例模式的实现原理就是要保证单例类对象的allocinit操作在应用的整个生命周期中只会执行一次,当单例类对象被创建后,其他地方调用该对象时就直接返回已存在的对象即可,在iOS中的实现,为了保证allocinit只执行一次,可以使用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架构可以用下图表示

ios开发 实例方法和类方法 ios开发实验报告_ios开发 实例方法和类方法

  • 如何在代码中应用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的代码无法复用
  • ViewController分离:将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特别臃肿,难以维护
  • 无法复用
  • 如果某些借口有依赖要求,比如借口一请求完再请求借口二,嵌套起来导致代码结构过于复杂
  • ModelController分离:按需要建立数据模型类,对数据的操作都放在模型类里,通过函数参数传值,通过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

ios开发 实例方法和类方法 ios开发实验报告_MVC_02

在MVC中,Controller要做的事还是太多,包括表示逻辑、业务逻辑,随着功能的增加代码量会过大

  • MVVM

ios开发 实例方法和类方法 ios开发实验报告_User_03

  • 对比:
  • MVVM就是在MVC的基础上分离出业务处理的逻辑到ViewModel层,简单来说,就是API请求完数据,解析成Model,之后在ViewModel中转化成能够直接被视图层使用的数据,交付给View,下面是一个实例,分别用MVCMVVM架构实现同一个功能:
  • 功能:一个页面,判断用户是否手动设置了用户名,如果设置了则正常显示用户名;如果没有设置,则显示“未设置用户名”
  • 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
  • 对比可知,MVVMMVCController中的业务逻辑等剥离出来放在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;