iOS技术积累

不管生活有多不容易,都要守住自己的那一份优雅。

一次关于IconFont的调研

上周在虎哥的直播上承诺放出调研IconFont的经过,周末就坚持者写完这个吧。

调研原因

用户在APP Store下载需要的APP,APP包体积是一个用户比较敏感的数据,过大的APP会对产品的推广产生不良的影响,比如:占用过大的手机存储空间。下载消耗较多网络流量(即便是wifi,后续也可能因为升级过程较为缓慢,导致用户长时间不升级,造成市面上的软件版本碎片化严重)。所以减少APP体积会带来很多正面影响。

掌链现状

我们以掌上链家APP7.7.0举例分析: 现在在Itunes Store下载的IPA体积大约在91.1M,即便公司在iOS9之后新增了APP Slicing功能,在我自己的iPhone 6 Plus下载的APP安装包依然达到了81.7M. 安装包瘦身主要包含资源瘦身和代码段瘦身,这篇文章主要调研资源瘦身。

资源的主要部分就是图片,请看下面数据

我们把iPA解压之后里面的Assert.car资源包的体积大约在34.1M 。 使用软件导出内部的资源文件发现3X系列的图片大小约为18.1M2X系列的图片大小约为19.4M,两份加起来大于34.1M是因为软件导出的图片部分没有后缀名默认两份都会存在。所以可以看出我们的图片还是有很大的优化空间的。

相关数据请看以下图片




IconFont

首先要介绍下矢量图
鉴于矢量图占用尺寸小,放大不失真,我们完全可用一套图来替换iOS传统的2x和3x系列图片,不仅仅是iOS,Android平台也不用切多份图了。

优点

  1. 减小体积,字体文件比图片要小
  2. 图标保真缩放,解决2x/3x乃至将来的nx图问题
  3. 方便更改颜色大小,图片复用

不足

  1. 适用于纯色icon
  2. 需要维护字体库

字体管理

既然设计到字体,我们就要制作字体,制作字体有两种办法,一种是自己手动去做比如使用FontForge工具,另外一种是托管到阿里的iconfont平台,我们的UE做字体设计完全没有意义,可以托管给平台。 托管给平台的好处:

  1. 大大的降低了接入难度
  2. 更方便项目管理。和RD对接更方便

而且iconFont可以和fontForge双重使用,FontForge工具可以再压缩这个字体文件。榨干最后一点剩余空间。

DEMO

iconfont平台建立一个项目,随便去购买几个图标(免费的即可), 06,解压后的文件夹有一个字体文件,双击安装。 mac和windows都有对应的工具管理字体07
近20张icon只占用了10k的资源空间,占用空间极小
08

具体的代码我们这里不再赘述,后面的demo具体展示, 不过我们要讲一讲使用的方案

  1. UILabel作为Icon
  2. UIButton的titleLabel作为Icon
  3. 根据字体生成UIImage

鉴于将我们本来的icon当作字体使用,会让我们在项目中添加的控件方式有所变化,而且不容易控制颜色和图标大小。所以我这里推荐第三种方案。

更详细的方案是我们建立字体组件。将这一块功能单独管理,便于后期迭代和维护,而且屏蔽底层使用直接暴露出对应的图片接口可以让上层无感使用。

后期如何接入

首期工作

这里建议拿掌链下手:原因有一下几点

  1. 掌链面向实际用户,做出来的效果更明显
  2. 掌链的APP安装包也最大,也最有瘦身的价值
  3. 掌链在经过组件化之后资源管理方式较为整齐,后期处理可以分次,灰度过度。 首期可能要麻烦UI同学,整理之前图标的对应的SVG格式,创建对应的字体库文件。工作量可能较大(约千张icon)。组件负责同学建立对应组件。 推荐的模式是可以在IconFont建立项目组,UI为owner,RD为member,方便协作和通知。

后期维护

待稳定后,后续迭代更加简单,只需UI同学更新对应的图标,建立新字体,然后组件管理者可以更新。 对APP安装和UI与RD业务迭代效率都有很大提高。

CODE

文件夹见DEMO

参考

UI参考

http://www.iconfont.cn https://icomoon.io http://fontello.com http://fontawesome.io/icons/

RD参考

Android:https://github.com/mikepenz/Android-Iconics

iOS: https://github.com/PrideChung/FontAwesomeKit

使用IconFont减小iOS应用体积


华丽的分割线


沟通结果

经过沟通发现掌上链家并不太适合这样的方式,原因如下

  1. 之前的icon很多是用ps做的 如果改用iconfont很多图层效果无法实现

  2. 即便新的icon用Sketch制作矢量图,但是icon在掌链中能制作矢量图的占比较小,且对设计的人力要求过大。

  3. 位图(png,jpg)等才是占用资源大户。
    后期考虑的方案

  4. 压缩大图 推荐使用PPDUCK(收费) imageOption,[]tinyPng](https://www.baidu.com/link?url=KkC7lFY89TwZELeYuV93DGrVaUInl3IYeq69FIHm0vp6mG8NrKBif_ewcW-9aZKC&wd=&eqid=cf83ff7b00071ce80000000358d65ecf)

  5. 删除重复资源(上海链家合并期间部分资源重复,但是由于工期太短没有剔除)

  6. 替换旧icon ,且可以统一UI风格

尝试

我尝试使用了上面推荐的三个压缩软件进行压缩,对比如下

  1. imageOption可以使图片减少30%左右,但是压缩巨慢,压缩2500张图片耗费12H以上并且,CPU一直维持在70%左右,但是图片是无损压缩,可以放心使用
  2. PPDuck压缩2500张图片需要花费5分钟不到,效果达到了惊人的70%,但是缺点也很明显,收费
阅读更多

情报局(1)

2017-01-13

  1. NSArray 用 copy 和 Strong修饰的区别

例如 self.a = b;
对于不可变对象来说使用copy,和Strong都可以。
copy的话对于setter方法,如果对方传进来的是b 不可变对象,那么copy之后a还是不可变对象,别的地方修改了b对a不会影响。
如果传进来的b是可变对象,那么copy之后a是不可变对象,修改b还是不会对a造成影响。

strong对于setter方法, 如果对方传进来的是b 不可变对象,那么copy之后a还是不可变对象,别的地方修改了b对a不会影响。
如果传进来的b是可变对象,那么copy之后a是不可变对象,修改b就会对a造成影响。

综上: 对于不可变对象应当使用copy修饰符

  1. NSMutable 用 copy 和 Strong修饰的区别
    例如 self.a = b;
    假设使用的是copy,无论传递进来的是什么,copy之后都会将a变成不可变对象。但是我们明明声名的是可变对象。这时候我们如果使用了 add, remove等方法时,就会抛出
    unregonrized selector method for xx 经典错误。 因为我们声名的Mutable对象经过赋值之后就变成了imMutable对象。

综上: 对于可变对象应当使用 strong操作符

  1. 对NSArray及其子类做MutableCopy是深拷贝还是浅拷贝

    首先补充 对于copy方法,无论源对象是可变还是不可变, 使用了copy生成的都是不可变对象,
    对于MutableCopy方法,无论源对象是可变还是不可变, 使用了copy生成的都是不可变对象。

由于容器里面存的只是目标对象的指针,在做mutablecopy或者copy只是让容器变成是否可修改的,并不会修改容器内部指针所指向的内存地址。所以都是浅拷贝

2017-01-16

给出以下代码, 问 在block嵌套的时候是否需要 嵌套 weak strong dance?


@interface A : NSObject
@property (nonatomic, copy) dispatch_block_t block;
@end

@implementation A



- (void)dealloc {
    NSLog(@"%@ dealloc ",[self class]);
}


@end
@interface B : NSObject
@property (nonatomic, copy) dispatch_block_t block;
@end

@implementation B

- (void)dealloc {
    NSLog(@"%@ dealloc ",[self class]);
}

@end
#define  weakify(A) __weak typeof((A)) A##__w = A;
#define  strongify(A)  __strong typeof((A)) A = A##__w ;
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
//    [self test1];
    [self test2];
}

- (void)test1 {
    A *a = [A new];
    weakify(a)
    a.block = ^{
        strongify(a)
        NSLog(@"%@",[a class]);
        B *b = [B new];
        b.block = ^{
            NSLog(@"%@",[a class]);
        };
    };
    a.block();
}

- (void)test2 {
    A *a = [A new];
    B *b = [B new];

    weakify(b) weakify(a)
    a.block = ^{
        strongify(a) strongify(b)
        NSLog(@"%@",[b class]);
        NSLog(@"%@",[a class]);

        b.block = ^{
            NSLog(@"%@",[b class]);
            NSLog(@"%@",[a class]);
        };
    };
    a.block();
    b.block();
}
@end

补充: #define weakify(A) weak typeof((A)) A##w = A;

这行宏语法是说明我声明一个weak的变量 名字为A__w ##在宏指令的作用是拼接

答: 具体需要看情况而定:很多人使用block的时候不去追究原理而是一味的死记硬背的记住要循环使用weak strong。
这里首先说明的是block只会捕获以下变量。 除了全局变量,block参数, block的内部变量 外的所有变量
并且,block持有这些变量的时机是在编译时期决定的,而不是运行时期。 所以只要block内只要出现了上述类型的变量就会去持有。

test1: a.block在内部出现了a这个外部变量,需要捕获a,为了避免出现循环引用我们是需要对a 使用weak strong 的,
在a.block()执行期间内部初始化了变量b 并且变量b.block捕获了a。a并没有捕获b,所以这时候出现的情况是 b.-> block -> a
a.block在执行完毕只会会释放变量b。随之释放.

test2: a.block内部出现了a,b的变量,但是此刻b并没有持有a,所以在外层block的时候只存在a->block-> (a,b) 并不存在 b->a,
所以为了避免循环引用,只需要对a使用weak strong。
在执行a.block的时候,在这个时刻,a->block->(a,b) (这是前提条件)。b.block持有了 a,b ,由于b->block->b,所以需要对b使用
weak strong,并且在此时刻 a->block->b 并且 b->block->a,出现了循环引用环,所以需要内部再出现一次 weak strong。
代码改写后为:


    A *a = [A new];
    B *b = [B new];

    weakify(a)
    a.block = ^{
        strongify(a)
        NSLog(@"%@",[b class]);
        NSLog(@"%@",[a class]);

        weakify(a) weakify(b)
        b.block = ^{
            strongify(a) strongify(b)
            NSLog(@"%@",[b class]);
            NSLog(@"%@",[a class]);
        };
    };
    a.block();
    b.block();

之前有人也提出问题说,我有时候也会用block关键字,这里我想说的是 block是说在捕获基本数据类型的时候捕获的是readonly(其实是一份副本)的值
如果我想修改这个值的话,使用__block关键字,这样在编译的时候传递进去的就是指针,和外部是一个值,
这里的问题是如何避免block捕获变量带来的内存泄漏的问题的分析。

阅读更多