每天梳理1~2个问题,坚持一下

这些问题可能是模模糊糊或者不懂或者没有梳理过的零零碎碎的,现在开始每天整理1~2个问题,从4.26号开始,希望能坚持1年。

内容很多是理解和总结的,可能会有不准确或者不严谨的地方,欢迎随时指正。

19.04.26

http中常见的请求头有哪些
这个是我前一段面试被问到的,经常抓包做价格监控的我居然从来没有留意整理起来,每天画葫芦忘了葫芦长什么样,这种每天见的还能被忽略掉,真是不应该。。。

Host:原始的 URL 中的主机和端口
User-Agent:信息识别发出请求的浏览器或其他客户端
Content-Length:只适用于 POST 请求,并给出 POST 数据的大小(以字节为单位)
Connection:指示客户端是否可以处理持久 HTTP 连接。持久连接允许客户端或其他浏览器通过单个请求来检索多个文件。值 Keep-Alive 意味着使用了持续连接。
Accept:浏览器或其他客户端可以处理的 MIME 类型。有text/html,image/,/等几种常用类型。/*可以简单的概括为告诉服务器,客户端什么数据类型都支持,代表发送端(客户端)希望接受的数据类型
Accept-Charset:这个头信息指定浏览器可以用来显示信息的字符集
Accept-Encoding:这个头信息指定浏览器知道如何处理的编码类型。值 gzip 或 compress(两者均为文件压缩格式) 是最常见的两种可能值
Accept-Language:客户端的首选语言,在这种情况下,Servlet 会产生多种语言的结果。例如,en、en-us、ru 等
Authorization:这个头信息用于客户端在访问受密码保护的网页时识别自己的身份
Referer:所指向的 Web 页的 URL。例如,如果您在网页 1,点击一个链接到网页 2,当浏览器请求网页 2 时,网页 1 的 URL 就会包含在 Referer 头信息中
If-Modified-Since:只有当页面在指定的日期后已更改时才会返回新数据否则返回304
If-Unmodified-Since:只有当文档早于指定日期时,操作才会成功
If-Match:通过E-Tag判断文件是否更改,作用和If-Modified-Since差不多,不同的是E-Tag不是以时间戳作为标识而是以实体文件是否改变作为标识,为资源文件生成E-Tag的方式并不统一,只要能保证唯一性即可

Accept和Content-Type区别
首先要明确请求头和响应头:
请求头:指从客户端向服务器发送请求报文时使用的首部,补充了请求的附加内容、客户端信息、相应内容相关优先级信息;
响应头:指从服务器向客户端发送响应报文时使用的首部,补充了响应的附加内容、服务器信息、相应内容相关优先级信息;

请求头和响应头可以看做是一来一回,从客户端到服务器,再从服务器到客户端

实体头:补充了资源内容更新时间与实体有关的信息

Http报头分为通用报头,请求报头,响应报头和实体报头。
请求方的http报头结构:通用报头 | 请求报头 | 实体报头
响应方的http报头结构:通用报头 | 响应报头 | 实体报头

Accept属于请求报头,表示发送方希望接收的数据类型,Content-Type属于实体报头,表示实体的数据类型,既可以是发送方也可以是接收方对实体数据的描述

Accept:application/json
Content-Type:text/html
即代表希望接受的数据类型是application/json格式,本次请求发送的数据的数据格式是html

参考来源:
https://segmentfault.com/a/1190000013056786
https://www.cnblogs.com/shanheyongmu/p/5920136.html
https://www.cnblogs.com/avivahe/p/5630394.html
https://blog.csdn.net/qq_42820268/article/details/82424353

19.04.27

Toll-Free Bridge
官方文档

在我理解就是类型的交换使用,在这里指Foundation和Core Foundation对象类型的交换使用。

在ARC环境下,Foundation框架下的对象实现了自动引用计数,无需开发者进行对象的生命周期管理,但Core Foundation框架下则需要开发者自己去管理对象的生命周期,使用CFRetain()、CFRelease()等方式。

在C和OC之间起到桥接作用的是__bridge这个关键字,通过__bridge可以使C和OC对象相互转换,但需要注意的是,__bridge只负责类型上的转换,并不进行内存管理上的转换,也就是说这个对象在转换前和转换后无论是C类型还是OC类型依旧保持着最原本的内存管理方式:
OC->C:(不必进行内存管理操作)

NSLocale *gbNSLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"];
CFLocaleRef gbCFLocale = (__bridge CFLocaleRef) gbNSLocale;

C->OC:(需要进行内存管理操作)

CFStringRef cfIdentifier = CFLocaleGetIdentifier (gbCFLocale);
NSLog(@"cfIdentifier: %@", (__bridge NSString *)cfIdentifier);
CFRelease(cfIdentifier);

同样的,对应__bridge不进行内存管理的移交转换,__bridge_retained和__bridge_transfer则进行内存管理的移交转换。

__bridge_retained:用在OC->C的类型转换,同时将原始对象的内存管理移交给C,这就意味着你在使用之后要进行CFRelease()

NSLocale *gbNSLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"];
CFLocaleRef gbCFLocale = (__bridge_retained CFLocaleRef) gbNSLocale;
CFRelease(gbCFLocale);

__bridge_retained和CFBridgingRetain()函数作用是一样的,我们可以查看到CFBridgingRetain()方法内部其实是进行了一次__bridge_retained操作:

NS_INLINE CF_RETURNS_RETAINED CFTypeRef CFBridgingRetain(id X) {
    return (__bridge_retained CFTypeRef)X;
}

__bridge_transfer:用在C->OC的类型转换,同时将原始对象的内存管理移交,在ARC环境下我们无需对此对象进行内存管理操作:

CFStringRef cfIdentifier = CFLocaleGetIdentifier (gbCFLocale);
NSString *Identifier =  (__bridge_transfer NSString *)cfIdentifier);

同样地,也有一个和__bridge_transfer起到相同作用的函数CFBridgingRelease:

NS_INLINE id CFBridgingRelease(CFTypeRef CF_CONSUMED X) {
    return (__bridge_transfer id)X;
}

那么为什么OC中的有些对象可以和C中的对象可以便捷转换类型呢?
先说下类簇的设计模式。在OC中,NSString、NSArray、NSNumber等等都是类簇的形式存在,实例化一个对象但对象的具体类型却是不可控的,听起来很像抽象工厂的模式,其实类簇就是基于抽象工厂的实现。
这些能便捷转换的对象绝大部分是以类簇形式实现的,类簇是Foundation下的一种设计模式:公开抽象的基类提供实例化子类的接口,这就意味着你创建一个NSString对象其实是其子类的对象,例如我们可能获得__NSCFString这个对象。

支持Toll-Free Bridge的类在Core Foundation下被下面的宏定义修饰:

CF_BRIDGED_TYPE

这个宏在编译期间内部判断是否满足Toll-Free Bridge条件,为什么上面说满足条件的大部分都是类簇形式存在的?因为类簇在实例化子类的时候具体的类型是一个过渡的类,例如__NSCFString是NSString的子类,同时它的实现是由CFString实现的,它作为了CF和F之间的桥梁。

在从CF->F的过程中,CF的对象在内存中保留了isa指针,通过isa指针来进行F对象的操作;

在从F->CF的过程中,F的对象实例化具体的子类,这个子类是基于CF对象的实现,在调用CF方法的时候只需要将其自身self传入就可以调用CF的方法。

19.04.28

atomic修饰的对象不是线程安全的

@property相当于ivar + setter + getter,使用atomic原子性修饰的对象在setter和getter方法中是安全的,内部实现是每次对setter和getter方法进行操作的时候会给操作对象加上锁@synchronized,保证了setter和getter方法操作的完整性。

但上面的方式仅仅是对setter和getter方法内部加锁,并没有对整个对象的访问加以限制,所以在遇到多线程读写的情况下并不是安全的。例如我在线程1种对A对象进行setter操作,在线程2种对A对象setter操作,在线程3中对A对象getter操作,三个线程异步进行,那么线程3执行完之后的结果是不可预期的。

这也就意味着,如果我们要保证这个对象访问的线程安全,我们需要从最外层的访问就加锁:加锁->进行读或取->开锁,在某个方法内部加锁只能保证这个方法执行的完整性,并不能保证整个对象的线程安全。

苹果并不建议使用atomic去修饰一个对象,这会造成性能上额外的开销,那么如何保证性能和安全尽可能地平衡呢?UIView其实提供了一个比较好的思路。我们知道UI上的操作要在主线程中才可以生效,我猜测是在setter方法中判断了当前的线程,保证了setter方法在主线程中才可以立刻生效,而同时,多线程状态下getter方法是不受任何影响的,我可以在任意的线程中获取到视图的状态,这样实现了“一写多读”的设计,使得性能和安全尽可能达到平衡。

19.04.29

** yield和Block**

我是在看Python的时候了解到的协程,在回调机制上我觉得和Block很像,似乎使用Block可以模仿出协程效果,但是Block有几个“硬伤”是无法忽略的。

Python中下面的代码:

def consumer():
    r = ''
    while True:
        n = yield r
        if not n :
            return
        print('[consumer %s]' % n)
        r = '200 OK %s ' % n


def produce(c):
    c.send(None)
    n = 0
    while n<5:
        n = n+1
        print('produce %s' % n)
        r = c.send(n)
        print('produce consumer return %s' % r)
    c.close()

c = consumer()
produce(c)

输出效果如下:

produce 1
[consumer 1]
produce consumer return 200 OK 1 
produce 2
[consumer 2]
produce consumer return 200 OK 2 
produce 3
[consumer 3]
produce consumer return 200 OK 3 
produce 4
[consumer 4]
produce consumer return 200 OK 4 
produce 5
[consumer 5]
produce consumer return 200 OK 5 

我们现在换成Block来实现:
我有两个类,Produce和Customer

@implementation Customer

- (instancetype)init {
    self = [super init];
    if (self) {
        __block NSString *r = @"";
        _sendMsg = ^NSString *(int i) {
            NSLog(@"[consumer %d]",i);
            r = [NSString stringWithFormat:@"200 OK %d",i];
            return r;
        };
    }
    return self;
}

@implementation Produce

- (void)produce:(Customer *)c {
    int n = 0;
    while (n < 5) {
        n++;
        NSLog(@"produce %d",n);
        NSString *r = c.sendMsg(n);
        NSLog(@"produce consumer return %@",r);
    }
    NSLog(@"end");
}

打印结果如下:

2019-04-29 10:00:45.940695 函数式编程[14685:901848] produce 1
2019-04-29 10:00:45.940717 函数式编程[14685:901848] [consumer 1]
2019-04-29 10:00:45.940749 函数式编程[14685:901848] produce consumer return 200 OK 1
2019-04-29 10:00:45.940768 函数式编程[14685:901848] produce 2
2019-04-29 10:00:45.940785 函数式编程[14685:901848] [consumer 2]
2019-04-29 10:00:45.940810 函数式编程[14685:901848] produce consumer return 200 OK 2
2019-04-29 10:00:45.940828 函数式编程[14685:901848] produce 3
2019-04-29 10:00:45.940846 函数式编程[14685:901848] [consumer 3]
2019-04-29 10:00:45.940870 函数式编程[14685:901848] produce consumer return 200 OK 3
2019-04-29 10:00:45.940888 函数式编程[14685:901848] produce 4
2019-04-29 10:00:45.940905 函数式编程[14685:901848] [consumer 4]
2019-04-29 10:00:45.940930 函数式编程[14685:901848] produce consumer return 200 OK 4
2019-04-29 10:00:45.940949 函数式编程[14685:901848] produce 5
2019-04-29 10:00:45.941146 函数式编程[14685:901848] [consumer 5]
2019-04-29 10:00:45.941176 函数式编程[14685:901848] produce consumer return 200 OK 5
2019-04-29 10:00:45.941194 函数式编程[14685:901848] end

虽然结果差不多但其实机制是完全不一样的,单纯的Block无法模拟出协程的效果,只是在回调上比较相似。

从上面可以看出:
1.协程中yield有挂起的效果(相当于暂时return,被触发时再次回到这个位置),单纯使用Block的话是无法挂起的;

2.yield有承上启下的上下文效果,Block没有上下文的效果,只能通过捕获变量乘上;

3.yield是一种机制(有函数变为generator使其具备迭代的能力),Block是一个对象,这个对象不具备generator的迭代能力;

苹果原生并未支持协程,想去实现的话也不是很容易,阿里最近开源了coobjc,有时间去看一下,协程的思想在编程中还是很重要的。

19.04.30

对象自动释放的时机

这里只讨论ARC环境下的机制,对象在其引用计数为0时会dealloc自动释放,这个自动释放的时机是在什么时候?

这里分为了两种情况,一种是没有Autoreleasepool的作用下,另外一种是在Autoreleasepool的作用下。

虽然分为了两种情况,其实内部的实现都是依靠了AutoreleasePoolPage这个对象。这个对象使用了双向链表的形式链接而成,拥有一个指向父page的指针和一个指向子page的指针。AutoreleasePoolPage以栈的结构管理add进来的对象地址,它有一个next指针指向add进来对象地址的下一内存空间,因为每个AutoreleasePoolPage对象的容量是有限的,所以当next指向栈顶的时候意味着这个AutoreleasePoolPage已经装满了,这时会创建一个新的AutoreleasePoolPage,通过双向指针建立链接。

AutoreleasePoolPage对象使用下面两个函数对add来的对象地址进行管理:

objc_autoreleasePoolPush
objc_autoreleasePoolPop

objc_autoreleasePoolPush执行的时候会在栈中加入一个标记,表示此刻进行了Push操作,后续add进来的对象地址都在这个标记之后;
objc_autoreleasePoolPop执行的时候也就是要进行一次自动释放的时机,当前的AutoreleasePoolPage对象会从next的上一个地址空间一直找到最近的标记位置,中间可能跨越N个AutoreleasePoolPage对象,找到标记位置之后对从next上一位到标记位之间所有的内存地址发送一次release消息。

回到最开始的地方,是否依赖Autoreleasepool:
第一种,没有Autoreleasepool作用的情况下:runloop在每次事件迭代开始时插入objc_autoreleasePoolPush,在事件迭代完成时进行objc_autoreleasePoolPop。事件迭代是指一系列相关联的souce处理完成之后,可能是一个方法完成之后,也可能是多个关联方法完成之后,所以它并不是“及时”地进行了释放,而这种例子也很常见,比如for循环中创建对象会导致内存不停地增加,需要一段时间后才会降下来;

第二种,我们直接使用Autoreleasepool:相当于我们自己调用了objc_autoreleasePoolPush和objc_autoreleasePoolPop的方法。下面是Autoreleasepool的结构:

struct __AtAutoreleasePool {
    __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
    
    __AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
    void * atautoreleasepoolobj;
};

可以看到使我们自己调用了push和pop包裹了我们的代码,这样的好处是可以使对象得到“及时”的释放,不单纯依赖于runloop的事件迭代。

出于对比和好奇我写了下面的代码:

//一个弱引用的属性
@property (nonatomic,weak) NSString *weakSelfStr;

//调用方法,是否打开autoreleasepool
- (NSString *)testAtuorelease {
//    @autoreleasepool {
        self.weakSelfStr = [NSString stringWithFormat:@"%@",@"测试数据"];
//    }
    return self.weakSelfStr;
}

//最后的执行
NSString *result = [self testAtuorelease];
NSLog(@"%@",result);

当我没打开autoreleasepool的时候毫无意外地NSLog输出了这个对象,当我打开时已经是空对象了,可以看出手动调用autoreleasepool可以更加及时符合我们预期地管理对象。

关于这里能不能直接回答@autoreleasepool执行到大括号结束的时候对其内部的对象进行了一次release消息的发送,我觉得其实也是可以的,这个是比较直观的感受,不过能回答到objc_autoreleasePoolPop才算是对内部的机制有个粗略的了解。

19.5.1

放假也来坚持一下,做一些方面的小总结。

消除if...else if和switch...case...
if...else if...是工程项目维护的杀手,曾经接受过一个项目在分支节点判断时嵌套5层if...else if...尽管写了注释但还是不忍直视

如何在项目中消除if...else if...,我觉得首先自己要清楚什么时候要用到if...else if...

我的感受是 有穷尽的节点上分支小于5个才去使用if...else if... 有穷尽指的包括当下和未来,你都能确定这个分支是有穷尽的,小于5个的话是个人的习惯,这个倒无所谓。

无穷尽也不是说非常多的分支,是指我们不可预期地会多会少,总之是会产生变动的状态。

消除这种不可预见性的分支(也就是便于后期的扩展),我觉得问题可以分为两种,一种是分支状态-视图类型,一种是分支状态-对象类型。

分支状态-视图类型主要是指对于分支的判断影响到视图的状态。例如我有一个字段status,取值从0-5,后期可能会扩展到n,每个值都会对应不同的控件状态,这种status不同产生的分支我觉得使用VM模型来弱化比较好。这里我使用的是“弱化”而不是消除,因为if...else...的判断从V中拿到了VM中。解决这类问题我觉得也是MVVM设计架构的优势,我在M中记录了status,在VM中判断出某一个控件最终的属性,在V中直接对接这个属性值。在这个过程中VM起到了逻辑处理的作用,解放了M和V,虽然我在VM中也进行了if...else...判断,但在整个结构上还是十分地清晰。

分支状态-对象类型是重点关注的部分:

1.反射模式,反射模式是解决这个问题的基石。OC是一门动态的语言,这为反射模式提供了便利的条件。具体到代码上便是利用字符串到类名的映射,我们只需提供出字符串的名称,实例化的对象在运行时才被真正确定。这种适合小型节点的分支且不需要用到实例化子类属性的时候;

2.多态性+基类+反射。这种方式也需要反射的支撑(子类需要字符串的映射),解决了单纯的反射模式无法使用实例化子类属性的问题。通过抽象化基类,提取公共的属性和方法,使得我们可以在编译期间就用到子类的具体属性和方法,而同时也不必指明具体的子类;

3.简单工厂模式+多态性+基类+命名规则+反射。这种方式主要解决更大范围的节点判断或者更加复杂的产品化节点。简单工厂模式负责产品的生产,反射负责保证简单工厂的封闭原则,多态+基类负责值和方法的对接,命名规则则是解决A类到B类的映射关系。这里有一个非常好的例子,参阅之前梳理的动态生产cell

19.5.2

从代码习惯和规范上谈代理和block

一般来说代理能做的事情block也能做,反之亦然,这里只从习惯和规范上说下代理和block和选择。

代理:代理更适合用在事件流上。什么是事件流?就是一个事件从发生到结束会有一系列的点组成,比如我们经常用到的UITextfield控件,它的状态从begin到changed到end,这就是一个事件流,有许许多多的状态点组成一个完整的事件响应。这里用代理模式配合着良好的动词搭配,会使整个事件流呈现的非常清晰易懂。

block:block更适合于事件点上。事件点指的是这个事件只有一个或两个点构成,比如我的日期选择控件,有取消和选择完成两个回调,每个回调都只有一个简单的事件,取消的时候我把视图隐藏初始化数据即可,选择完成后我只需要把我的值回调出去即可,这里显然用block更加简便。但这里用代理其实也是可以的,这只是个习惯并不是说一个硬性的规定。

代理相比于block来说我觉得更加清晰易懂,逻辑顺序比较朝一个方向,对于工程的维护比block要好一点,尤其是做framework封装一些接口的时候,代理更能给使用者直观的感受;block则属于便捷轻巧类型,这点优势也是非常足的,在一些工具类的接口中是非常理想的选择。

19.5.3

防抖
防抖指的是一定时间内多次触发的方法只执行最后一次,防止方法被高频调用,也可以说方法在一定时间后才执行,如果在这个时间段中继续触发则重新计时。

防抖主要用在方法会被高频触发的时候,包括本地的方法和调用后台接口的方法,一些本地的方法比如UIScrollerView的代理,在滚动时会高频率回调,如果我们在这个回调中同步做一些耗时哪怕会消耗0.5秒的时间都可能会使页面产生卡顿;调用后台接口的话客户端也需要对一些高频调用的方法进行防抖操作,避免服务器吃不消。

防抖的操作其实很多时候是在回调函数中起到一个预防机制,因为很少有主动高频调用一个函数的时候,所以一般来说在一些回调中对调用方法做防抖是比较常见的操作。

苹果并未提供方法防抖的API,这需要自己造一个轮子。这个轮子其实很简单,根据防抖的场景我们可以简单地做一个从调用开始到确定执行的桥接,在这个桥接中我们主要做一个任务时间和频率上的控制。

(函数防抖)[https://www.jianshu.com/p/7df3bf3845e9]

单纯的防抖可能因为时间段设置的过长、连续高频率地调用使得方法根本无法执行(当然这是在极端条件下),所以也需要考虑到有一个时间点作为出口,如果加上时间的限制必须执行,看起来像是融合进了节流的思想,整个轮子造出来似乎是利用了节流的方案侧重于防抖的设计。

其实很多东西在设计的时候你不必过多地在意刻板的概念理论,在设计东西的时候你可能会打破一些规则,但最终设计出来的东西对你的工程有用方便这个就是好的设计,理论结合实际永远是最重要的,橘生淮南则为橘,生于淮北则为枳。

19.5.5

最近的面试有感

前一段出去面试过,这一段也面试了别人,说下感想吧。

首先简历上的工作时间,我朋友跟我说你可以把工作年限写长点,写个5年,这样面试的机会很多。但我觉得刻意去造经验年限一是诚信问题,二是如果面试官是个大牛会给对方产生强烈的反差,5年经验这不懂那不理解,印象会非常差。其实从我去面试的和面试别人的情况来看,有一部分还是会去做这方面的事情,毕竟你要找工作首先要有面试邀约才行,但是如果面对大牛的话,就像上面说的,反差很大印象会很不好。

然后是对于问题的筛选。面试者与面试官本来就是建立在不对等的条件下,对于问题的筛选就很重要。不要觉得你把别人问懵逼了自己就很厉害,其实换换位置对方很大程度上也能把你问懵逼。我主要问三种方面,第一种是基本的知识,iOS方面的,尽可能地是大家都会用到的,如果简历中有写比较擅长的比如音视频,就可以从音视频来交流,如果没有写,就可以问一些常见的,比如字符串用copy和strong修饰的区别这类的;第二种是扩展性的,不限于iOS方面的但也应该略知一二,比如一些设计模式,结合具体的场景你来做一个解决方案;第三种是目前我们自己项目中用到的比较专项的东西,比如屏幕录制。第一第二是比较重要的,第三种无所谓。毕竟每个人专项是不一样的,有的人擅长图像处理,你非要抓着自己项目中的屏幕录制让他立马做一个解决方案,这就强人所难没有意义。

最后是对对方回答的一个梳理,如果自己是面试者,就对自己的回答做一个系统性的整理。作为面试官,如果对方对你的问题能回答70%+,我觉得已经不错了,他可以进入到下一环节了。不对等的条件下,面试官要做到的是尽可能从面试者擅长的方向入手,从基础到深入,然后进行一些扩展,了解他整体擅长的知识面。面试的意义并不是问你会不会这个会不会那个,而是发现面试者的学习能力和思维能力是否能胜任。而作为面试者,注重细节和扩展是非常重要的,不要觉得OSI模型没什么意义,三次握手工程中也体现不出来,工厂模式我不用照样实现功能,基础的东西反应了基本是否扎实( - - 我的基本就不扎实有一两次面试就栽在这上面),不了解的话首先会对你整体的能力有所质疑;扩展的东西能反应出这个面试者身上的亮点,我知道用装饰模式去解决某些场景的问题,这就是一个亮点,很多时候扩展的地方答得好会留下一个好的印象。

我个人来说比较喜欢面试,尤其是技术方面的,所以即使不跳槽我可能偶尔也会去一些中意的公司面试,通过面试可以让你保持一个“清醒”的状态,会发现很多自身上的不足。在一个环境呆久了就会浑浑噩噩,有些挑战也是很不错的。

19.05.06

角色

这个概念在前端用的并不是很多,我现在的项目是ERP系统,在后台管理系统上对角色会很常见。

在我们的项目里,引入角色的概念是为了解决用户权限的问题,我觉得这种方式是OOP思想的一个非常好的体现,写一些自己的感想。

很多管理类的项目都需要解决一个问题,即用户权限的问题。如果把一个具体的用户当做成一个实体,那么他所拥有的权限是相当于一些属性和方法,这些属性和方法可以是初始化出来的,也可以是后期加入的。当我们只有用户这个类时,你需要每次都为新的用户分配权限,即使很多用户他的权限可能是一样的,这就好比你有若干个类,每个类中都用一些相同的属性和方法,这在设计上肯定是有问题的。那么我们就需要抽象进行改进,抽象出相同的属性和方法,然后对属性和方法进行归类,引入角色这个概念。

角色代表了权限的分配,每种角色对应了不同的权限,相当于每个角色类有不同的属性和方法,用户类和角色类的关系是以一对多或者多对一的形式存在的,角色与权限也可能是一对多或者多对多形式存在的。这样通过角色的桥接,用户与权限实现了解耦。

那么再复杂点,用户越来越多,怎么去管理?继续抽象,出现用户组这个概念。用户组描述了一组具有相同角色的用户集合,当新用户加入时我们只为他找到合适的用户组即可,而不需要把他的角色全部分配一遍。

这里把权限比作属性和方法也有不恰当的地方,只是为了方便描述其耦合性,权限这里也应该以类为单位。同时,角色也不单单相当于权限和用户的抽象,更起到了一个桥接解耦的模型作用,有点像VM作为M和V的桥梁。

这里有点抽象搭桥实现解耦的感觉,暂时就把角色这种叫做桥梁模型吧,桥梁模型在处理复杂的多对多关系时有解耦的作用,也可以为了实现解耦去做一个桥梁模型,同时,在一些需要复用的对象中桥梁模型可以将不可复用的代码抽离到自身,保证对象的复用性。

19.05.07

POST与GET

HTTP中的两种数据传输方式POST和GET,也是我们最常用到的。除此之外还有六种请求方式:OPTIONS、HEAD、PUT、DELETE、TRACE、CONNECT。

之所以POST和GET比较常用可能是由于这两种能完全模拟实现另外六种的效果。

POST和GET的区别,这里有个因果关系,很多地方在说POST和GET的区别的时候总是因果倒置。

先说下原因:这两种方式没有太大区别,为什么要这样说呢?因为GET也能模拟出POST的请求效果,POST也能模拟出GET的请求效果,这在技术上是完全可行的。那为什么要区分GET和POST呢?我觉得就是将用法归类,是一个规则性的操作,就好像货车除了拉货也能拉一车人,但我们通常不会这样去做,货车的用途就是拉货。规定了POST和GET的使用范围,使得一般情况下GET只用作向指定的资源请求数据,POST只用作向指定资源提交数据。

结果:由于上面功能性的划定,才会有很多POST和GET表现不同的结果,例如GET请求会被浏览器缓存,参数会直接明文显示在URL上且URL长度会被限制等等,这些不同并不是GET和POST本质上的不同,而是由于功能上区分开之后表现形式的不同。

19.05.08

基本类型 == 0 的判断

最近线上的一个BUG,模型里面NSInteger类型字段做了一个 == 0的判断,后台这边处理的时候出现了一点问题,返回给我一个空值,在AFN中我将数据空值又过滤了一遍,最后等于是这个字段未初始化,其实这里并不等于0,是个空值,结果因为默认值是0所以走了不该走的分支。

由于OC数据类型和Java的差异,很多在Java中的空值在OC中十分不友好,如果不加处理崩溃是常有的事情,在一些数据处理框架中有些帮我们处理了空值,有些是直接过滤掉了空值。OC对象类型的空值因为会引起崩溃所以这部分重视的比较多,基本对象类型的空值基本上很少关注。

当没有为Int / NSInteger 这些基本类型赋值的时候,它默认为0,这就导致了一个问题,你不能以 == 0去作为判断条件,== 0 在这里起到了两个作用,一个是真实是0,另外一个是初始默认为0,后面一个可能是你不想出现的。

所以在对Int / NSInteger 这些基本类型做if判断的时候,尽量避免使用==0这个操作,自己可以做一些初始化操作为-1,同时后台也可以在数据上初始化一些默认值去避免这个情况。

19.5.09

依赖于runloop的方法要考虑当前线程是否是主线程

子线程的runloop默认是不开启的,这就会导致一些依赖于runloop的方法在子线程中会失效,例如下面延迟执行这个方法:

[self performSelector:@selector(test) withObject:nil afterDelay:1];

在主线程中是没有任何问题的,但在子线程中由于runloop默认关闭,导致延迟调用失效。

自己的代码还好但一些第三方的框架中回调回来的方法很可能还是在某个子线程中,如果我们这个时候不对线程做判断直接调用一些依赖于runloop的方法就会失效,例如延迟执行和NSTimer。可以选择回到主线程中去操作或者开启子线程的runloop。

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];

有一个有意思的方法:

[self performSelector:@selector(test) withObject:nil afterDelay:1 inModes:@[NSDefaultRunLoopMode]];

这个延迟执行的方法中有一个Mode参数,明天做些小测试。

19.05.10

NSDefaultRunLoopMode 让滚动视图“空闲”下来做些事情

接着昨天留的尾巴,系统提供了API让我们可以把方法放到指定的Mode中去执行,这就有了向runloop的Mode添加source的能力,考虑到滑动视图会切换Mode的场景,似乎可以做一些事情。

昨天想把cell上图片赋值的方法加到NSDefaultRunLoopMode中,这样就能使滑动的时候方法不执行,停下来的时候去执行,后来想想这样也是有问题的,停下来的时候可能已经有若干个方法开始执行,会造成CPU和内存的峰值,并且由于cell是复用的,方法可能并不在一个线程中执行,这就使得最后赋值的image不可预期。

所以暂时不考虑UI上的操作,我们还可以做一些数据上的操作,将耗时的当前又用不到的任务放到这里去操作,做一个数据预加载效果:

1.为了能精准地向空闲的runloop中加入source,我们需要一个观察者,当runloop的Mode状态为NSDefaultRunLoopMode时我们开始处理事件;

2.事件可能有多个,如果一次提交的话很可能吃不消,所以我们可以把事件放到数组容器中,在每次runloop当前的Mode为NSDefaultRunLoopMode时去执行一个任务;

3.当任务数组中的任务处理完毕时移除观察者,整个流程告一段落;

上面的这种方式在一些异步绘制或者预加载的框架中比较常见,主要处理cell行高的缓存、下个页面的预加载等等,我觉得这种方式不一定非体现在滑动视图中,普通的页面如果runloop处在NSDefaultRunLoopMode下我们也可以认为runloop处在了空闲状态(大部分情况下),这时我们也可以做一些数据或者逻辑上的操作,比如整理一下用户的操作行为、缓存一下页面数据等等。

上面这种方式目前在工程中也就第三方框架有所体现,例如AsyncDisplayKit,自己并未做一些实际的操作。不过整理整理这些思想也好,多一条解决问题的思路。

19.05.11

排序对相册资源的影响

先记录一下这个BUG,未找到真实原因。
在之前的屏幕录制中就发现了这个BUG,表现在我取出的一组资源如果未排序,在一些机型一些系统版本上会出现资源不对的情况,比如我的相册中最后一个资源是video类型,获取到相册资源的数组,但数组中最后一个资源时却无法拿到URL(此时真实资源类型是图片)。

之前由于在12和12的系统上必复现,所以当时考虑到时系统版本问题,12和12以上可能对API实现有所修改没有向下兼容。最近有个用户反映苹果6在11的系统也会出现这种取出最后一个资源不正确的情况,虽然我并未复现出来但这里明显已经是个坑了,不同的版本和机型在获取相册资源时似乎有些不同,虽然未找到真实原因,但目前看来如果指定了排序方式似乎能消除这些差异,如果出现了相册资源获取的一些bug,可以尝试下指定排序方式。

19.05.12

anchorPoint

anchorPoint是layer上的一个属性,表示layer的“中心”,默认状态下和postion属性是重合的。
anchorPoint影响layer的frame,你也可以说layer的frame由anchorPoint、position和bounds决定。
anchorPoint默认值为(0.5,0.5),取值范围为(0,0)~(1,1)。
position这个属性和anchorPoint相比较来说有点像frame和bounds的味道,position相对于父视图,而anchorPoint相对于自身。

由于这个属性平时很少用到,所以一些同学甚至不知道这个东西的存在。虽然它的存在感很低,但有些操作时必须要靠anchorPoint完成的,比如围绕某一点旋转、layer的frame调整。

anchorPoint设置了layer自身的中心,所以一些基于中心的操作就需要考虑anchorPoint。旋转是我们经常做的操作,默认的旋转(改变transform角度)是围绕自身的中心点也就是(0.5,0.5)操作的,如果想围绕某一个例如左上角,就需要更改anchorPoint为(0,0),由于layer的frame受anchorPoint的影响,所以改变anchorPoint之后要重新设置frame,不然视图位置会产生变化。这个操作中我们改了anchorPoint和frame,bounds未产生变化,所以最后做了“妥协”的属性是position。

这里有个要注意的地方,就是约束对frame的影响,由于约束的存在frame会失效,所以如果是基于更改anchorPoint的操作建议使用frame的属性。

19.05.13

显式动画与隐式动画

这两种动画模式并没有严格的概念定义。

显式动画:一般来说显式动画就是我们主动做的一些动画效果,比如基于基础动画和关键帧动画的效果,我们指定了一系列的动画效果(action)使之呈现出来,这个效果是我们主动创造主动想要的。

隐式动画:隐式动画是我们没有创造动画action且并不是主动想要的动画效果。iOS中具有animatable能力的属性均能产生动画效果,自然也能产生隐式动画效果,所以我们会看到更改layer的一个属性可能会产生一个从原始值到最终值平滑过渡的动画。隐式动画默认持续0.25秒。

一般来说,隐式动画并不是我们想要的,在一些复合动画场景会使得页面动画冗杂,而对于性能敏感的地方隐式动画无意也增加了性能的消耗。UIView默认是关闭了隐式动画的效果,除非你主动使用动画模块API比如UIview animation块,被block包裹的内容是可以出现动画效果的。

上面说了iOS中具有animatable能力的属性均能产生动画效果,那么这个动画流程是如何进行的呢?这里从UIView默认关闭掉隐式动画来说。

layer有一个delegate的属性指向UIView,当你改变了一个可动画的属性之后,layer会在view中寻找是否实现了CALayerDelegate协议里这个方法:

actionForLayer:forKey

这个方法中可以返回nil或者[NSNull null],返回nil则继续向下寻找,返回[NSNull null]则查找到此结束。由于这个方法是第一级的查找,所以我们在此可以直接禁用掉动画效果,即返回[NSNull null]。

如果返回nil或者delegate不存在或者没有实现actionForLayer:forKey的方法,那么这个图层会继续检查包含属性名称对应行为映射的actions字典,这个action字典我理解的就是一些动画属性键值对,像下面关键帧动画设置一样:

 [CAKeyframeAnimation animationWithKeyPath:@"position"];

如果action字典中没有对应的一些属性,那么图层接着在它的style字典接着搜索属性名。这里的style字典我的理解是效果样式字典,就是上一步中具体对应的某一个属性要产生哪些效果,这个属性的key由第二步传入(这里的理解并不一定对,style字典并没有单独去尝试过);

如果在style里面也没有设置对应的属性,最后图层将默认方法

defaultActionForKey:

通过层层的查找最终会返回给图层对象nil,[NSNull null],或者一个具体的CAAction对象。
返回nil则图层会产生隐式动画;
返回[NSNull null]则图层不出现动画;
返回CAAction则图层会生成CAAnimation对象并加到自己身上做一个自定义动画。

19.05.14

寄宿图contents

通常来说为一个view赋值图片我们会选用UIImageView给image赋值,layer的contents其实也可以达到这个效果。

layer的contents属性它接收了一个CGImage对象,虽然它将接收的类型定为id,但如果接收的对象不是CGImage类型将不会有任何显示。

通过contents的方式我们可以做一些性能上的优化。

直接给多个layer的contents属性一个CGImage对象,它们会共用这个对象而不会产生拷贝。

为了尽可能避免drawRect带来的内存和性能上的影响,我们可以将layer的代理事件移交,在layer创建默认的寄宿图之前实现下面的方法:

-(void)displayLayer:(CALayerCALayer *)layer;

在这个方法中我们直接对layer进行的绘制而没有产生空白的寄宿图。

19.05.15

简单的编译流程

先说一下编译工具:LLVM

LLVM:LLVM是一个解释代码的工具,是一个模块化、可重用的编译器、工具链技术的集合。OC和Swift使用LLVM来编译代码。

LLVM将编译分为三个模块,前端,优化,后端。

前端:LLVM的前端可以使用各种模块去编译特定的语言,例如clang用来编译C,C++和OC。前端主要做了对语法语义的检查(Xcode中实时提示我们某个类型或者方法不正确),生成中间代码。

优化:对前端生成的中间代码进行优化。

后端:将优化后的代码变成汇编语言,优化汇编语言,将每一个独立的文件link在一起,合并成一个可执行文件。

总结一下简单的编译流程:

注:具体的编译步骤可能会受开发者自身设置的影响,在Build Phases选项卡下的排序会影响编译步骤。

1.梳理工程结构,生成辅助文件:将整个工程的文件目录、脚本、依赖等梳理成结构文件,并生成一个.app包;
2.如果Build Phases中有脚本、依赖设置会进行脚本、依赖的配置,拷贝依赖库到.app;
3.LLVM编译每个文件为Mach-O可执行文件;
4.link每个可执行文件,合并为一个可执行文件,并放入到.app中;
5.如果有storyboardh或xib文件,则编译storyboard和xib文件,link多个编译后的文件合并成一个文件,放入.app包;
6.编译 Asset 文件;
7.生成app包;
8.签名;
9.生成ipa文件包;

19.05.16

app启动流程

  1. 加载dyld(动态链接器);
  2. 通过dyld加载动态库和依赖库;
  3. Rebase,修复内部指针指向;
  4. Bind,修复外部指针指向;
  5. Objc初始化设置;
  6. 其他初始化设置;

注:修复指针指向是因为由于ASLR或者CodeSign导致地址随机化起始地址不确定;
Objc初始化设置主要进行了类的注册、方法表的映射、分类的插入等;
其他初始化设置主要进行了 load 方法,将数据写入堆栈;

优化:这几个部分中我们能做的优化比较少,主要从第5和第6步来说,尽量减少load的调用,使用initialize代替;减少类的个数;减少分类的个数等等。

19.05.17

LRU与LFU

LRU和LFU这两个缓存淘汰算法老是弄混,梳理一下。

LRU:淘汰访问时间最远的元素,基于访问时间(新数据和命中的老数据会放在队列最前面);

LFU:淘汰访问次数最少的元素,基于访问次数(命中次数最多的元素会放在队列最前面);

LRU缺点:批量查询冷数据会对现有热数据造成污染;

LFU缺点:新数据新容易被替换掉;

19.05.18

优化缓存策略算法

今天又看了些关于缓存策略的算法,发现昨天总结的有问题改正了下。

LRU和LFU两者都有自身的缺点,相比较来说LRU优化起来更简单易用,关于LRU有很多种优化方案,比较出名的是LRU-K。

LRU-K利用了队列分级的思想,K代表命中次数,缓存同时维护两组队列,一组队列为没达到K次命中的队列,另外一组则就是达到K次命中的LRU队列。K相当于权重,在新数据进来时会被放进未达到K次命中的队列中,直到这个新数据达到K次命中时才会放进LRU队列中,这样可以很好地避免大量新数据对缓存数据的污染。

19.05.19

装饰模式和建造者模式对比

装饰模式:动态地给对象增加职责;用在不改变原有对象结构的基础上新增一些职责的场景;(常用的分类就是很好的体现)

建造者模式:将对象的构建和表示分离,通过建造者指挥创建不同的对象表示;用在基础构建部件不变但顺序会产生变动的场景;

当职责变成基础部件时,装饰模式其实也可以往建造者模式上扩展,例如我们一个页面要从上到下展现很多种布局的场景,装饰和建造者都是可以达到这个效果的,那么他们的区别在哪呢?

我觉得主要有下面的区别:
1.装饰模式更多地用在功能性的扩展上,建造者模式更多地用在部件的组装上;

2.装饰模式是不注重顺序的,建造者模式则对顺序有着要求,不同的顺序出来的表示效果是不同的;

3.从代码层面上看,装饰模式的装饰组件和被装饰组件继承了同一对象,装饰过程在“外部”随机顺序实现,建造者模式则抽离出一个指挥者,由指挥者“内部”去按某个顺序实现;

*最后再加一个小对比,建造者模式和工厂模式:

工厂模式更注重产品的生产过程,我如何去生产出来这个产品;而建造者模式更注重产品的加工顺序,每道工序其实已经准备好了,我只需要一个指挥者来告诉我先用哪道工序后用哪道工序。

19.05.20

随便写写

下午比较忙所以没做什么总结,晚上随便写点最近的事情。

最近很多事情很烦,工作上、生活上很多事情都积攒到了一起,多的令人焦虑。去年从杭州来成都时说走就走的劲儿越来越少,越来越趋向于安稳。但这肯定是不好的,在一个安稳的地方呆惯了会让人丧失斗志。

关于公平,也想随便写点。虽然说没有绝对的公平,但是只要你想也能尽可能地营造这个环境,如果一个集体里面利益只是少数人分配,那么这个集体没有任何凝聚力,所以公平真的很重要,如果遇到不公平的事情,站出来的人是勇敢的人,打破既得利益的分配是很大的事情,至少在这个集体中,这个行为会打醒很多浑浑噩噩的人。

最近对自己价值的认识也产生了偏差,主要是在面试别人的过程中,其实想一想大可不必,你做了多少做了什么可能别人看不出来但你心里清楚,做好自己的事情,只要还在做这一行就好好学习,点点滴滴日积月累,问心无愧。

最近因为一些事情的变化使得自身很烦很焦虑,说到底还是自身没有足够的硬实力和软实力,还是要继续努力,每多一点努力就多了一份资本和勇气。

这篇已经有点太长了,明天准备重新开个篇幅继续记录。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,082评论 1 32
  • Web测试基础 I. 如何开展Web测试 Web测试的对象 Web的页面元素 Web的业务逻辑 Web的数据行为 ...
    厲铆兄阅读 4,388评论 4 62
  • Author :Jeffrey由于原文找不到,特意找了一个转载的: 转载地址 引言 HTTP是一个属于应用层的面向...
    夜殇丶夜逝阅读 865评论 1 10
  • 引言 HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年...
    RuanRuanRuan阅读 590评论 0 5
  • 总结一下本地 Vue 项目部署到 阿里云服务器 的步骤和遇到的坑。1、服务器购买 服务器购买可以选择国内和国外的服...
    贵在随心阅读 285评论 0 0