iOS项目重构周记(二)

作者: Levi_ | 来源:发表于2015-07-12 20:45 被阅读2557次

    继续上一篇,本周的重构重点是UI部分代码的优化。

    1. AutoLayout及Masonry

    AutoLayout是苹果在IOS6中推出的一种新的UI构建方式,旨在解决不同屏幕分辨率之间的适配问题。相信大多数人可能跟我一样,对这种方式是又爱又恨,因为AutoLayout中的确存在很多坑。不过随着iOS设备尺寸越来越多,还是值得去学习掌握AudoLayout的。
    本次重构中在UITableViewCell中使用AutoLayout上遇到了一个坑,正常情况下在cell中使用AutoLayout需设置约束上下左右都为-8才能铺满整个Cell。但发现在iOS6中没有问题,但在iOS7以上,左右约束需设置为-15才能铺满。我的解决方案是在cell.contentView上再添加一层父View,针对不同的系统做了一个适配。但问题的根本原因目前还没有找到,有待后续观察。
    另外,对于手写约束来说,使用苹果原生的API可能会很痛苦,因为约束代码将又臭又长,例如:

    [self.view addConstraint: [NSLayoutConstraint constraintWithItem:AView
    attribute:NSLayoutAttributeLeft
    relatedBy:NSLayoutRelationEqual
    toItem:BView
    attribute:NSLayoutAttributeLeft
    multiplier:1
    constant:0]];
    

    仅表示AView的左侧距离BView的左侧一个单位,所以有必要引入一些第三方工具。
    Masonry是一个轻量级的布局框架 拥有自己的描述语法 采用更优雅的链式语法封装自动布局 简洁明了 并具有高可读性 而且同时支持 iOS 和 Max OS X。
    上面那句使用Mansory可以精简为:

    [AView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(BView.left).with.offset(1);
    }];
    

    一个View的多个约束可以在同一个Block中实现,并且代码书写方式让人更容易理解。
    更多使用技巧请戳:Masonry

    2. Cell中对Layer的处理

    其实cell中应避免一切对Layer的处理,包括圆角,阴影,甚至不应该包含任何透明View,因为这种渲染对系统的开销非常大,众多的Cell将使页面变的非常卡,在使用Layer时,也应该使用如下的方法减少以系统的开销。

    self.layer.shouldRasterize = YES;
    self.layer.rasterizationScale = [UIScreen mainScreen].scale;
    CGPathRef path = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
    [self.layer setShadowPath:path];
    

    3. UITableViewCell中嵌套UITableView

    看到网上有人说应该避免在UITableViewCell中使用UITableView,我觉得可以视需求的不同做不同的处理。对于一个模型结构非常复杂的TabeView,嵌套TableView可以降低代码的耦合,将不同的业务模型分散处理。只是需要注意的是,子TableView和父TableView的实现不应该在同一个文件中处理,也就是说delegate和dateSource不应该指向同一个对象,可以将子TableView封装成一个Cell,delegate和dataSource都交由这个Cell处理,这样才能有效降低代码的耦合,并且精简原文件的逻辑和大小。

    4. UITableView中间层模型的封装

    相信很多人会Cell的展示逻辑直接放到TableView的delegate中处理,例如:

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if (A) {
            if(B){ 
                return BHeight;
            }
            return AHeight;
        }else if (C) {
            if(B){ 
                return BHeight;
            }
            return CHeight;
        }
        return 0;
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        if(A){
            if(B) {
                return 2;
            }
            return 1;
        }else if(C) {
            if(B){ 
                return 2;
            }
            return 1;
        }
        return 0;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if(A) {
            if(B) {
                return BCell;
            }
            return ACell;
        }else if(C) {
            if(B){ 
                return BCell;
            }
            return CCell;
        }
    }
    
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if(A) {
            if(B) {
                doB();
            }
            doA();
        }else if(C) {
            if(B){ 
                doB();
            }
            doC();
        }
    }
    

    所有代理中的逻辑都必须相同,而且同样的方法要写多次,例如上面的B。使用这样的方式,当遇到逻辑非常复杂的TableView时将使我们苦不堪言。TableView的代理应该只负责去构建Cell,而不应该来处理逻辑判断。所以,我们应该构建一个中间的模型层,在TableView reloadData的时候加载这个模型层,例如:

    - (void)setupTableModel
    {
        if(A) {
            if(B) {
                [arrayModel addObject:BModel];
            }
            [arrayModel addObject:AModel];
        }else if(C) {
            if(B){ 
                [arrayModel addObject:BModel];
            }
            [arrayModel addObject:CModel];
        }
    }
    
    - (void)reloadTable
    {
        [self setupTableModel];
        [tableView reloadData];
    }
    

    此时:

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if ([[array objectAtIndex:row] isEqual:AModel]) {
            return AHeight;
        }else if ([[array objectAtIndex:row] isEqual:BModel]) {
            return BHeight;
        }else if ([[array objectAtIndex:row] isEqual:CModel]) {
            return CHeight;
        }
        return 0;
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        return arrayModel.count;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if ([[array objectAtIndex:row] isEqual:AModel]) {
            return ACell;
        }else if ([[array objectAtIndex:row] isEqual:BModel]) {
            return BCell;
        }else if ([[array objectAtIndex:row] isEqual:CModel]) {
            return CCell;
        }
        return nil;
    }
    
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if ([[array objectAtIndex:row] isEqual:AModel]) {
            doA();
        }else if ([[array objectAtIndex:row] isEqual:BModel]) {
            doA();
        }else if ([[array objectAtIndex:row] isEqual:CModel]) {
            doA();
        }
    }
    

    此时的delegate中只关注tableView的Cell构建和Cell行为,并不关心任何构建顺序等逻辑判断。

    相关文章

      网友评论

      • 无神:对于第二条我想请教一下,如何更好的对layer设置圆角(例如把头像设置成圆的)。
        无神:@zcrhappy 我试过那个代码貌似不能设置圆形!
        小小面试官:@无神 设置之后缓存试试看吧?
      • 4ef7e44e7a90:真机支持吗?
      • Levi_:@243107d24030 嗯 那个选项在IB里是constrain to margins 代码里设置是View的layoutMargins属性
      • Levi_:@243107d24030 这次重构我遇到个问题 那个边距在iOS6上是-8 在iOS7以上是-15 iPhone6 plus又单独是-20
        vernepung:@Levi_ 试试这句看看

        if (iOSVersion > 8.0)
        {
        self.preservesSuperviewLayoutMargins = NO;
        }
      • 243107d24030:@Levi_ 要知道如果不频繁设置约束一切都是那么ok,猜测卡顿原因由于约束每次设置做了某个复杂操作。

        另外,关于苹果推荐的距离层次边缘距离的问题有这么几个地方:
        1.在Xcode 6.2(好像是这个版本,没太注意),至少,苹果默认推荐一个距离边缘距离8,默认是开启的,在之后就开始更改了,改为16(好像是16),这个和编译器解析xib有关,和手机版本无关。
        2.苹果建议的页边距只有ib中有该选项,代码设置未看到该设置。如果需要关闭,则在设置约束距离上下左右距离时候关闭那个上下左右框下方的选项,英语不是很好,没注意叫什么名字,莫笑莫笑~~
        另外,关于前文提出的约束卡顿问题我有以下不同意的地方:
        1.使用约束卡顿并不再计算frame,而是设置约束卡顿。表现形式为:设置约束一旦展开cell,则不更新此cell计算的高度则会缓存,并不存在滚动计算高度的情况,所以并不会造成卡顿。另外在进行复用时候则会对刚才消失的约束执行一系列操作来产生二级回复列表,此时因为已经缓存高度,所以不会调用高度计算函数。
        2.约束的设置如果在生成时候设置复杂的约束,重用时候并不会造成卡顿(没测试,因为看cpu 使用率,和其他的没有什么区别…)。当cpu使用率超过20则会能感觉到卡顿。
      • Levi_:@243107d24030 cell使用约束是会产生卡顿,因为中间加了一层去推断frame,一定是比直接写frame消耗要大的,而且用cell设置约束的气候边界是-8,不知道为何
      • 243107d24030:不知道lz有没有常识约束写一个有回复列表的cell?
        我写的步骤如下:
        1.重用cell时候先移除里面的二级回复view
        2.读取数据列表数组,取出对应个数的缓存的二级回复view,统一设置约束
        3.使用fd优化
        结果:卡顿,随着二级回复的增多卡顿越长,测试为,八个二级回复列表卡顿0.14秒。
        现用优化方案:改为frame进行布局一样的逻辑,无卡顿…
        备注:约束使用xib或者Masonry 均卡顿较大。
        vernepung:写了半天评论,发现是15年的- -##
      • 同学:@Levi_ 以前在面试的时候,hr说,有个人技术博客吗?我说,别人都写过了,我就没必要写了。
      • Levi_:@同学 哈哈 请不要在意这些细节
      • 同学:最后那个判断是否等于 也是逻辑判断呀😳

      本文标题:iOS项目重构周记(二)

      本文链接:https://www.haomeiwen.com/subject/gceuqttx.html