Arclin

Advocate Technology. Enjoy Technology.

0%

关于案场管家的技术点总结

案场管家的技术点总结

xib与storyboard

  1. xib和storyboard均是用于布局所用,其实如果不需要过于复杂的布局的话完全也可以时候用masonry或者直接CGRect定位亦或者NSNSLayoutConstraint进行布局,如果要用代码的话个人觉得masonry是非常好用的,并且该框架所使用的函数式编程思想和链式编程思想也是值得学习的

  2. storyboard的作用类似于包含多个xib+视图管理,代码调用storyboard里面的控制器的代码如下

    1
    2
    UIStoryboard *stroyboard = [UIStoryboard storyboardWithName:"storyboard的文件名" bundle:nil];
    XXXViewController *vc = [storyboard instantiateViewControllerWithIdentifier:“contorller的storyboard Id”];

    如果是controller类xib,那么就直接 alloc init 一个该xib所绑定的controller就可以了
    如果是view类xib

    1
    [[[NSBundle mainBundle] loadNibNamed:@"CMBuildingCell" owner:nil options:nil] firstObject]
  3. 之前曾经遇到过一个bug,从xib拖线到类文件的时候他会报错,原因是如果你的xib绑定的是一个继承UIView的类(比如一个cell),那么File Owner就应该保持为空,父view绑定类;如果绑定的是一个controller类,那父view绑定为空,File Owner绑定类,从以上说明我们可以得知,FileOwner就是这个xib所属的controller,如果他没有所属的controller,那么调用这个xib的controller就是他的File Owner,所以上面的代码里面的owner我们一直保持为nil就可以了

关于网络获取与数据本地化

  1. 关于网络方面的话我之前写过一份xmind,大概流程是这样子的

    • 首先定义一个布尔变量,判断是否有存档
    • viewDidLoad 取档并刷新数据
    • viewWillAppear 中执行 loadDataFromNet
    • loadDataFromNet方法中判断是否有存档,没有存档并且获取网络数据失败的时候才提示数据刷新失败

    示例如下

    • 这里用NSCachesDirectory,也就是缓存目录,关于目录的选择看有道云笔记,上面有比较详细的说明
    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
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
     #define XXFileName [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"xx.data"]
    - (void)viewDidLoad {
    [super viewDidLoad];
    // 取档
    self.dataArray = [NSKeyedUnarchiver unarchiveObjectWithFile:XXFileName];

    if (self.dataArray) {
    [self.tableView reloadData];
    hasArchive = YES;
    }else{
    hasArchive = NO;
    // 转菊花 或者 下拉刷新也行
    [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeBlack];
    [SVProgressHUD show];
    }
    [self loadDataFromNet];
    }
    - (void)viewWillAppear:(BOOL)animated
    {
    **如果能保证这个controller每次进来都会经过viewDidLoad(例如本身自己就是子控制器且自己也没有子控制器)则这里就不用写了**
    [self loadDataFromNet];
    }
    - (void)loadDataFromNet
    {
    [XXTool fetchMsg:^(XXData *data) {
    self.data = data;
    // 归档
    [NSKeyedArchiver archiveRootObject:profile toFile:XXFileName];
    dispatch_async(dispatch_get_main_queue(), ^{
    [SVProgressHUD dismiss];
    [self.tableView reloadData];
    });
    } failure:^(NSError *error) {
    NSLog(@"%@",error);
    dispatch_async(dispatch_get_main_queue(), ^{
    if(!_hasArchive)[SVProgressHUD showErrorWithStatus:@"加载失败"];
    });
    }];
    }
    • 不过做项目的时候还是看实际情况进行调整吧,上面所说的只是一般情况下的网络处理

    近期稍微地研究了一下Runtime框架,加上一点脑洞和MJExtendsion的配合,感觉可以做出一个框架,能够实现获取数据之后直接生成跟远程数据库结构相同的本地数据库,一方面可以避开类似salesForce的SOQL语句无法进行多表查询之类的恶心问题,另一方面做数据筛选功能的时候也会简单很多,这个就待我去看看能不能搞出来

关于单例

  1. 在这个项目里面我导入了一个外部框架(应该说是一个文件?):Singleton.h
    这个东西使用起来很爽
    只要在你想单例的类的声明h里加 singleton_interface(类名)
    实现m里加 singleton_implementation(类名)
    就可以了
    之后你想调用这个类的时候就直接 [ClassName sharedClassName] 就可以直接获得到这个类的单例

  2. 什么情况下要用到单例?
    以案场这个为例子,我每次获取数据都需要加上限制条件,筛选当前项目下和当前登录用户的数据
    那么我就会频繁使用到projectId 和 userId ,并且这两个值基本不变
    所以我就给这个单例写了两个方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    - (NSString *)currentProjectId;
    - (NSString *)userId;
    - (CMProject *)currentProject
    {
    CMProject*currentProject = [NSKeyedUnarchiverunarchiveObjectWithFile:CMSelectProjectFileName];
    return currentProject;
    }

    - (NSString *)userId{
    return [SFUserAccountManager sharedInstance].currentUser.credentials.userId;
    }

    //使用
    [[CMUserTool sharedCMUserTool] userId];

关于传值

  1. 传值有三宝:代理、通知、block 另外还有storyboard专用segue传值和成员属性传值等,因为前三个比较重要所以我就说说前三个的使用

    • 代理用来两个视图之间的传值,最常用的就是自定义view上面的点击事件通过代理告诉调用它的controller,因为一个界面只能有一个controller,一般我们都是抽出另一个controller的view添加到这个controller上面,所以这时候就要用代理进行两个controller之间的数据传递
      通知,先说说使用方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    **消息接收方**
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(方法名) name:消息名 object:nil];

    **消息发送方**
    //创建一个消息对象
    NSNotification * notice = [NSNotification notificationWithName:消息名 object:nil userInfo:nil];
    //发送消息
    [[NSNotificationCenter defaultCenter]postNotification:notice];
    移除通知

    移除单个通知
    [[NSNotificationCenter defaultCenter] removeObserver:self name:消息名 object:self];

    移除当前所有通知:[[NSNotificationCenterdefaultCenter]removeObserver:self];

    但是优缺点并存

    优势:

    1. 不需要编写多少代码,实现比较简单
    2. 对于一个发出的通知,多个对象能够做出反应,即1对多的方式实现简单
    3. controller能够传递context对象(dictionary),context对象携带了关于发送通知的自定义的信息

    缺点:

    1. 在编译期不会检查通知是否能够被观察者正确的处理;
    2. 在释放注册的对象时,需要在通知中心取消注册;
    3. 在调试的时候应用的工作以及控制过程难跟踪;
    4. 需要第三方对象来管理controller与观察者对象之间的联系;
    5. controller和观察者需要提前知道通知名称、UserInfo dictionary keys。如果这些没有在工作区间定义,那么会出现不同步的情况;
    6. 通知发出后,controller不能从观察者获得任何的反馈信息。
    • block 用于反向传值,就是在子控制器操作数据后传值回到父控制器,这时候我们用block,不过block传值也有坑,很容易引起循环引用然后烧内存。解决方法就是用weak修饰对象比如 weak typeof(self) weakSelf = self
      这样子我们用weakSelf.对象就不会引起循环引用
      • 还有block有一个小地方要注意,如果要在block里面调用外部变量的话,要给外部变量加一个__block修饰符

关于枚举

枚举不用讲太多,枚举就是为了方便判断而已,但是命名规范还是要说的,举个栗子

1
2
3
4
5
6
7
8
typedef enum : NSUInteger {
CMRelatedTypeOpportunity,
CMRelatedTypeBooking,
CMRelatedTypeSalesOrder,
CMRelatedTypeContract,
CMRelatedTypeCollection,
CMRelatedTypeTrade
} CMRelatedType;

枚举的成员名 = 枚举名 + 类型名

关于案场的客户界面 —— 复杂的逻辑 TO 一个Controller

案场那个客户界是这个app中逻辑最复杂的一个界面,虽然通过了枚举的判断方便了一个代码的阅读,但总的来说上千行代码着实还是恶心。
类似的场景应该以后应该还会经常发生,但是虽然开发的时候可能方便了但是维护起来也太费劲了,并且之前项目的种种维护的体验也告诉了我们一个事实:MVC框架不够用了

所以——我们还是换个架构吧~

我大概考虑了一下,逻辑层还是尽可能的细分,多几个类都行,然后耦合性应该低
之前我们就已经习惯了把业务层从controller中抽取出来,这种方式应该保留
然后我们可以尝试引入MVCS架构以及MVVM架构等,具体的demo应该会在近期实现