indexPath 为什么indexof返回0NIL,求解

ios7 indexPathForCell 的坑(真是一个大大的坑) - _嵌入式开发_ - 博客园
笔者在编写APP 有一个功能点击cell上一个button,修改cell的在tableview中的位置
在ios8上没有问题。
在ios7上总是崩溃
以下是崩溃后提示:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0.& The number of rows contained in an existing section after the update (11) must be equal to the number of rows contained in that section before the update (11), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
开始怀疑是删除或插入indexpath 有问题 。google了下,解决方法各式各样。
后来断点调试发现在ios7上
[self.tableviewindexPathForCell:myCell] 返回了nil
在ios8中使用了
- (void)selecttop:(id)sender{
& & if ([sender isKindOfClass:[UIButtonclass]]) {
& & & & UIView *view =
& & & & AppListCell *myCell = (AppListCell *)view.superview.superview;
& & & & NSIndexPath * cellPath = [self.tableviewindexPathForCell:myCell];
google下是ios7 上cell上面还多了一个UITableViewWrapperView 具体可以参见:
stackoverflaw 中&/questions//uitableview-indexpathforcell-ios6-v-ios7
有一个很好的处理方法
- (void)selecttop:(id)sender{
& & if ([sender isKindOfClass:[UIButtonclass]]) {
& & & & UIView *view =
& & & & while (![view isKindOfClass:[UITableViewCellclass]]) {
& & & & & & view = [view superview];
& & & & AppListCell *myCell = (AppListCell *)
& & & & NSIndexPath * cellPath = [self.tableviewindexPathForCell:myCell];
真的是个坑!!!
据说ios6上也是两个superview 就可以获取到!!!!用户名:yifangyou
文章数:170
评论数:90
访问量:220616
注册日期:
阅读量:1297
阅读量:3317
阅读量:455673
阅读量:1140285
51CTO推荐博文
&今天想试验解析xml文件,在项目下建了一个xml文件,代码如下
NSString*&path&=&[[NSBundle&mainBundle]&pathForResource:@&sample&&ofType:@&xml&];&NSLog(@&path=%@&,path);&NSError&*error=&NSString*&fileText&=&[NSString&stringWithContentsOfFile:path&encoding:NSUTF8StringEncoding&error:&error];&NSLog(@&%@&,error);&NSLog(@&path:%d&,&fileText:%d&,[path&retainCount],[fileText&retainCount]);&
结果在 stringWithContentsOfFile出现错误&The operation couldn&t be completed&
追查发现是pathForResource返回nil,我查了半天没有找到解决办法,我尝试找Default.png,发现能够找到在&/Users/admin/Library/Application Support/iPhone Simulator/6.0/Applications/FFB75F5F-86B7-4B74-9CEF-9F76DE56764C/BaseAppKit.app/&目录下,而在这个目录下确没有sample.xml,我忽然想到了可能需要在项目配置文件里告诉编译器把sample.xml拷贝到BaseAppKit.app/下,
果然我找到了添加资源的地方:
单击项目-》Build phases-》若是没有&copy files&就需要添加:
添加文件:
添加完后再运行就找到文件了
&本文出自 “” 博客,请务必保留此出处
了这篇文章
类别:┆阅读(0)┆评论(0){"debug":false,"apiRoot":"","paySDK":"/api/js","wechatConfigAPI":"/api/wechat/jssdkconfig","name":"production","instance":"column","tokens":{"X-XSRF-TOKEN":null,"X-UDID":null,"Authorization":"oauth c3cef7c66aa9e6a1e3160e20"}}
{"database":{"Post":{"":{"title":"危险的UITableView","author":"mrpeak","content":"如果把我们所做的UI做个简单分类,大致上可以分为列表界面和非列表界面。对于列表类UI,我们可以选择UITableView或者UICollectionView来实现。UICollectionView出现之前,UITableView几乎是唯一的选择,这每日可见人人都用的UITableView里隐藏着容易忽视的危险。同步VS异步同步和异步是基础的编程概念,也是贯穿于我们日常的两种代码书写方式。理解sync和async不仅仅在于明白代码执行顺序上的差异,更重要的是理解这两种方式的差异对我们代码健壮性的影响。同步的代码书写方式很直观,也是大部分初学者潜意识所选择的方式。我们只需要把心中的思路按部就班的转换成代码,就形成了一段同步的逻辑,比如下面一段同步代码:self.arr = @[].mutableC\nfor (int i = 0; i & 1000; i ++) {\n
[self.arr addObject:@(i)];\n}\n同步强调的流程是:我此刻拥有哪些数据,此刻对这些数据进行一些计算,进而利用计算结果在此刻产生更多的行为。同步意味着在当下一步一步按顺序的完成逻辑。当我们代码越写越多,手感变好之后,我们会写更多的异步代码。异步在执行的时间上和同步刚好相反,异步强调的是代码当下并不执行,而是等待未来某个时机到来之后再发生。比如下面一段异步代码:self.arr = @[].mutableC\n//async\n[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {\n
for (int i = 0; i & 1000; i ++) {\n
[wself.arr addObject:@(i)];\n
}\n} failure:nil];\n对于_arr的修改操作,是在网络请求完成之后再执行的。异步的好处在于表达能力更灵活更强,我们对于跨越某个时间段的流程,有了更好的表达方式。这几年很受技术圈热捧的Reactive Programming,精髓之一就在于异步。Wild beast is dangerous,异步的缺点也很明显,由于跨域了一定的时间区域,在异步操作真正发生的时候,我们程序所依赖的状态很有可能在这一时间跨度内发生了意料之外的变化,进而导致奇奇怪怪的bug。具体到上面这段代码,很有可能在执行[_arr addObject:@(i)];的时候,self.arr已经被某处代码改为nil了。我在之前介绍中也提到了这点,赋值操作会随着时间的变化而危险起来,而Functional Programming恰好可以帮助我们解决状态维护在时间维度上的问题,这也是为什么异步的响应式编程总是和无状态的函数式编程结对出现,双剑合并成FRP。我们再来看看UITableView中的同步与异步。UITableView标题中所说的危险之处正是在于异步。更具体点来说,是reloadData这个调用中所包含的异步操作。先来看看执行reloadData都发生了什么。当我们reloadData的时候,我们本意是刷新UITableView,随后会进入一系列UITableViewDataSource和UITableViewDelegate的回调,其中有些是和reloadData同步发生的,有些则是异步发生的。我们熟悉的下面两个回调是同步的:- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath\n{\n
return 20;\n}\n\n- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section\n{\n
return _arr.\n}\n而另一个最常使用的回调则是异步的:- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath\n{\n
NSNumber* content = _arr[indexPath.row];\n
//...\n}\n经过上面的分析,我们不难UITableView的危险之处在于哪了,在于异步执行cellForRowAtIndexPath的时候,我们所依赖的状态可能会发生变化,上面代码中的_arr如果元素被修改过,极有可能发生数组越界的异常。当列表界面数据不怎么变化的时候,几乎感知不到这种异常的存在,因为reloadData返回之后,下一次loop就开始执行异步的操作了。但是当列表界面的数据有可能经常变化的时候,尤其是在多线程的场景下,就会出现偶现的bug了。实际上,所有在函数内部处理外部状态的场景,我们都需要假设状态是不安全的,有可能被修改过了,使用前尽可能的检查各种边界条件,这样的代码才足够robust,当然啦,能不依赖外部状态是最好不过了。如何解决解决的方式五花八门,我相信很多人都有自己的独门秘技,不过关键应该都在于消除异步带来的状态不稳定。方式一:最直观的,我们可以在执行_arr[indexPath.row];的时候,做下长度检查,如果越界则返回空:- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath\n{\n
if (indexPath.row & self.arr.count - 1) {\n
return [UITableViewCell new];\n
NSNumber* content = _arr[indexPath.row];\n
//...\n}\n这种方式粗暴有效,可以避免crash。方式二:或者我们可以采用前面文章中提到的,控制刷新事件的产生频率,建立一个Queue以一定的时间间隔来调用reloadData。事实上这是一种很常见的界面优化机制,对于一些刷新频率可能很高的列表界面,比如微信的会话列表界面,如果很长时间没有登录了,打开App时,堆积了很久的离线消息会在短时间内,导致大量的界面刷新请求,频繁的调用reloadData还会造成界面的卡顿,所以此时建立一个FIFO的Queue,以一定的间隔来刷新界面就很有必要了,这种做法代码量会多一些,但体验更好更安全,具体代码我就不展示了,实现起来也不难。总结这篇文章虽然是分析UITableView,但本意其实是想和大家分享异步代码中所存在的隐患。无论是系统API调用,还是平常做业务时所写的异步流程,异步的思想随处可见,我们要极其小心当中可能存在的状态维护上的坑。欢迎关注公众号:MrPeakTech","updated":"T06:10:27.000Z","canComment":false,"commentPermission":"anyone","commentCount":9,"likeCount":18,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T14:10:27+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-53ff9c141d0b3c442d440d4ee81d1042_r.png","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":9,"likesCount":18},"":{"title":"iOS代码设计中的开放与封闭","author":"mrpeak","content":"我们至今所写的 iOS 代码都是遵循 OOP 这种编程范式,以对象来临摹和表达我们对于世界的理解。在设计类的时候,恪守 SOLID 五个原则会让我们的代码更易拓展和维护。SOLID 中的 O 代表的是 Open/closed principle,这篇文章所要探讨的不仅仅是类设计中的 Open 和 Closed,而是要站在更广阔的视角来看待代码中的开放与封闭。前言我们作为代码工作者,不能仅仅满足于写出能运行的代码,还是注意时刻提高自身的姿势水平。具体来说,就是加强对于「内功心法」的学习,逐步提升写代码的抽象和设计能力。程序员是理工教的一大分支,我们向来以严密的逻辑推导能力为立身之本,我们很容易发现文科生思维中存在的逻辑不连贯,不缜密,不严格,我们擅长以 if, else, for, switch 等精巧的关键字来阐述逻辑和流程。用代码来表达的流程看上去确实很酷,很科学,很真理,可在数学家眼里,我们大部分程序员所写的代码其实「漏洞百出」,和「严密」二字几乎不怎么沾边,看起来并不比文科生高明多少。问题出在哪呢?姿势水平还不够。Open vs Closed我们先以 Open/closed principle 为切入点,对于代码的开放和封闭来建立初步的印象。Wikipedia 定义如下:In , the open/closed principle states “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.这一原则要求我们设计的功能单元,对于功能拓展是开放的,而对于代码修改则是封闭的。不知道大家对于这种抽象的描述作何感想,Peak君初次看到的时候,脑中只感觉一团云雾缭绕,怎么能让马儿跑,又不用吃草?这段玄之又玄的描述,体现到代码中后,不过就是一些日常所用的语言技巧了,我们可以从多个角度去理解和实现代码的开放性和封闭性。继承,简单的继承关系就可以体现 open/closed principle,如果我们设计好一个父类,这个父类在设计之初就已经有了清晰完备的功能定义,并向天起誓以后绝不修改这个父类一行代码,那么我们可以说这个父类已经 closed 了。想要拓展功能怎么办?新建一个子类继承自这个父类,在子类中添加我们所需要的新功能,这样就做到了 open。一言蔽之,父类对于代码修改是封闭的,而对于子类的功能拓展是 open 的。实际工程中,多少人能忍得住不修改父类呢?多态,多态配合接口使用也能体现 open/closed principle,我们在设计功能单元的时候,只定义接口,而不规定具体实现的细节。在类或者模块交付的时候,我们继续向天起誓以后绝不修改接口中的定义,那么接口就是 closed 了。但是后期我们可能需要修改具体实现的细节,需要拓展功能,于是我们替换一个实现了该接口的另一个类,这个新的类实现对于代码修改是 open 的。简而言之,接口对于代码修改是封闭的,实现对于代码修改是开放的。这也是为什么,我们在写 iOS 代码的时候,需要大量运用 protocol。这么看来,开放和封闭的定义还是很清晰的,二者针对的对象不同就可以合理共存。不过我们为什么既要封闭又要开放呢?因为封闭的事物是静态的,稳定的,安全的,不写一行代码就不会有 bug 不是吗?可是我们所做的每一个工程都是处于变化的状态,每一个新 feature 都是为了迎合不断变化的市场需求,所以 open 是不可避免的,怎么办呢?让 open 与 closed 并存,让稳定的部分不变,在 closed 的代码基础之上去做拓展,去 open 新的代码。Algebraic data types聊完了我们熟悉的继承和多态,下面我们进入一个稍微陌生一些的领地:Algebraic data types。Algebraic data types 是纯函数式编程语言 Haskell 中的一种类型定义,这是一个看上去简单,实际上令初学者极其费解的技术概念。之所以费解,是由于它主要应用在数据模型的定义,和我们平常写业务所用的 int, float 这种 data type 完全不是一回事。Algebraic data types 可以简单的理解为一些 data type 的集合,这里的 data type 就是我们传统意义上的数据类型,比如 bool, int, double 等等,在这个 data type 的集合之上,Algebraic data types 提供一些特定的代数操作,可以对 data type 集合里的每个 data type 执行逻辑。代数操作通常为两种:sum 和 product。很抽象是不是?到底有什么用?我们对应到 iOS 中的代码来理解下。比如我们日常所用的 BOOL 类型:BOOL isValid = \nisValid =\nisValid 的值要么是 true 要么是 false,是二选一的关系,所以 isValid 的值有两种可能性,即 true 和 false 相加,所以 BOOL 类型可以理解成一种 sum type。再看 CGPoint :struct CGPoint {\n
CGF\n};\nx 和 y 同时存在于 CGPoint 这个类型当中,不是二选一,而是一种类似于组合同时存在的关系,我们把 CGPoint 这种由两个子 type 所共同构成的 data type 称之为 product type。你可能发现了,所谓的 sum type 和 product type 就是对 data type 集合中的元素进行 and 或者 or 操作,从而拼装出各种可能的组合。OOP 下的 data type(比如我们自定义的 class)强调的是对于 property 和 function 的封装,而 Algebraic data types 完全换了一个视角,看重的是 data type 的组合方式。当我们以递归的方式使用 Algebraic data types 来描述各种 data 的时候,就开启了一扇新世界的大门。sum type 和 product type 都是 Algebraic data types。按照这种规则定义的 data type 到底有什么用处?好处有很多,其中之一和这篇文章的主题相关。Algebraic data types 有个重要的特性:Algebraic data types 对于自身 data type 集合中的每个 type 的处理是以穷举的方式,而且 data type 集合中的一旦定义好之后是不允许修改的,closed!这一点和我们在 OOP 下自定义的 Model Class 非常不同,Class 是允许被继承来拓展功能的,而 Algebraic data types 一旦定义好就已经 closed 了。比如 isValid 如果定义包含 true 和 false 之后,是不允许添加 half-true 的,同时所有对于 isValid 的操作要穷举 true 和 false 两种可能性。Algebraic data types 的 closed 和 exhaustive 特性可以让代码更加稳定,当然这种特性需要语言层面的支持,Objective C 并没有相关的特性,但我们可以在代码设计中借鉴其思想。我们在平时写业务的时候,经常需要设计各种各样的 model 类。Facebook 在 2016 年开源了一个专门用来管理和生成 model 的 framework,叫做 。这个库功能强大而且全面,其中之一就是生成符合 Algebraic data types 特性的 model。以如下代码为例,描述的是一个具有多种类型的消息 model:@interface MessageContent : NSObject &NSCopying, NSCoding&\n\n+ (instancetype)imageWithPhoto:(Photo *)\n\n+ (instancetype)stickerWithStickerId:(NSInteger)stickerId;\n\n+ (instancetype)textWithBody:(NSString *)\n\n- (void)matchImage:(MessageContentImageMatchHandler)imageMatchHandler sticker:(MessageContentStickerMatchHandler)stickerMatchHandler text:(MessageContentTextMatchHandler)textMatchH\n\n@end\nMessageContent 有三种可能的类型,image, sticker, text。MessageContent 提供的 match 方法以穷举的方式来处理所有可能的场景,对于 MessageContent 的使用者来说,一定不会漏处理任何一种可能性,强制 model 的使用者考虑所有的场景。这种做法的好处是代码一旦生成就极其稳定可靠,不允许修改,closed。缺点也很明显,一旦业务要求我们增加一种新的 type,比如 MessageContent 为 voice 的语音消息,会难以下手,因为一旦修改就必须改变 match 方法签名,以穷举的方式新增一种 type 处理,代码的改动牵涉面必然很广。所以你看,到底是设计成 closed 还是 open 的,其实是一次根据业务场景的取舍,在变与不变之间做权衡。这里介绍 Algebraic data types 目的在于说明,我们在做代码设计的时候,closed 和 exhaustive 的设计方式会让我们的代码更加可靠和稳定。Optional in Swift刚开始学习 Swift 的时候,不知道大家有没有好奇过为什么要引入 optional 这样一个新类型,optional 使用的场景也非常之多,有很多的文档去介绍在不同的语法下 optional 如何使用,可为什么要 optional 呢?和我们用 Objective C 时判断是否为 nil 有什么区别呢?我们先看下面一段函数:- (User*)getLuckyUser {\n
//perform some calculation...\n
return _\n}\n这段很常见的代码没有考虑一种场景,就是 _user 为 nil 的情况。你可能会说函数返回 nil ,函数的调用方自己去判断就可以了。当然如果返回 nil,在 Objective C 的 runtime 里,给 nil 对象发送消息也是安全的,这种安全只是表示不会 crash,但有可能原本应该执行的逻辑就没有继续下去了,从这一角度去看,nil 对象是对业务不安全的。而且我们把这种 nil 的 case 所造成的影响延迟到了 run time 。更合理的做法是在编译时就考虑 nil 这种 case。optional 正是为此而生,如果我们定义返回值为 optional,那么 optional 的使用方就一定要考虑值不存在的场景,如果漏处理了为 nil 的场景,就会编译器报错,这样不光不会 crash,而且对业务逻辑来说也是安全的。感觉灵敏的同学可能发现了,optional 类型和上面提到的 Algebraic data types 中的 sum type 非常相像,它表达的也是一种 or 的关系,即值要么存在,要么为 nil。当我们使用 Algebraic data types 来描述 data 的时候,语言本身会强制我们做 exhaustive checking,去考虑 data 的所有可能性。这是另一个 Swift 比 Objective C 更安全的有力证据,Swift 吸收了函数式编程语言中的很多优秀特性。总结就是,当我们使用 optional 来写业务的时候,Swift 会强制我们去考虑 data 的各种可能性,这样写出来的函数,其逻辑就是完整的,全面的。总结还有不少能体现 open 和 closed 设计思想的例子,比如 java 中的 final 关键字,又比如设计模式中的 Visitor Pattern,大家也可以联想下类似的例子。我个人比较喜欢写这类随意遐想的文章,畅想不同技术概念之间在设计思想上的关联,加以总结和巩固。好啦,啰啰嗦嗦说了一堆抽象的概念,读到此处还没有放弃的朋友们辛苦了,为你们的耐心,干杯!欢迎关注公众号:MrPeakTech","updated":"T02:18:28.000Z","canComment":false,"commentPermission":"anyone","commentCount":2,"likeCount":12,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T10:18:28+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-53ff9c141d0b3c442d440d4ee81d1042_r.png","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":2,"likesCount":12},"":{"title":"iOS 创建对象的姿势","author":"mrpeak","content":"在写 iOS 代码的时候,怎么样去 new 一个新对象出来,都有一些讲究在里面。使用不同的姿势去创建对象,对后期维护所造成的影响会存在细微的差别。init 创建在之前一篇分析 iOS 代码耦合的文章中,提到过当我们给一个对象的 property 赋值的时候,通过 init 方法传入参数来初始化 property 会让我们的代码更可靠。有些人在定义带 property 的 class 的时候,会这样定义:@interface User : NSObject\n@property (nonatomic, strong) NSNumber*
userID;\n@end\n使用的时候如下:User* user = [[User alloc] init];\nuser.userID = @1000;\n尤其是在定义 model 的时候,很容易写出这种,先 init,而后挨个给 property 赋值的代码。这种代码的问题在于 property 对于外部是可写的,property 处于随时可能变化的状态。之前不少篇文章中都强调过 immutable 的重要性,同样对于一个 class,我们也应该优先考虑设计成 immutable 的。initWith 创建如果将 property 都设置成 readonly 的,或者不暴露 property,property 的赋值都通过 initWith 的方式来初始化,就可以得到一个具备 immutable 的 class 定义了,具体到上面的例子代码如下://User.h\n@interface User : NSObject\n@property (nonatomic, strong, readonly) NSNumber*
userID;\n- (instancetype)initWithUserID:(NSNumber*)\n@end\n\n//User.m\n@implementation User\n- (instancetype)initWithUserID:(NSNumber*)uid {\n
self = [super init];\n
if (!self) {\\n
_userID =\\n}\n@end\nuserID 在 .h 文件当中是 readonly 的,userID 只有一次被赋值的机会,即在 User 的 initWith 方法中。这种方式的好处是一旦 User 对象创建完毕之后,就处于 immutable 的状态,property 都是不可修改的,安全可靠。Designated initializerApple 为了方便开发者使用 init 方法,引入了一种名为 designated initializer 的 pattern。主要用来管理当一个 class 拥有多个 property 需要赋值的场景。比如上面我们的 User 类:@interface User : NSObject\n@property (nonatomic, strong, readonly) NSNumber*
userID;\n@property (nonatomic, strong, readonly) NSString*
userN\n@property (nonatomic, strong, readonly) NSString*
\n@end\n 有些场景需要初始化 userID 和 userName,而有些场景只需要初始化 userID 和 signature,所以我们需要提供多个 initWith 方法给不同的场景使用。为了管理 initWith 方法,Apple 将 init 方法分为两种类型:designated initializer 和 convenience initializer (又叫 secondary initializer) 。designated initializer 只有一个,它会为 class 当中每个 property 都提供一个初始值,是最完整的 initWith 方法。convenience initializer 则可以有很多个,它可以选择只初始化部分的 property。convenience initializer 最后到会调用到 designated initializer,所以 designated initializer 也可以叫做 final initializer。无论我们定义何种类型的 class,给 class 中的每个 property 都赋予一个初始值是个很好的习惯,可以避免掉一些意外的 bug 产生,这也是 designated initializer 的重要职责。在实际的项目当中,一个 class 的 property 数目可能会随着业务的增长而增加,最后的结果就是会生成越来越多的 convenience initializer。上述的 User 类,如果是 3 个 property,极端的情况下最多可以有 7 个 init 方法。Peak君在阅读代码的时候,也确实看到过有些 class 定义了一连串整整齐齐摆放的 init 方法,代码虽然看着规范,但显得啰嗦,而且每次需要肉眼搜索适合的 init 方法。其实我们还可以用另一种姿势来 init 我们的对象。Builder pattern最初是在学习 Android 的时候,发现这个 builder pattern 也可以用来构建对象,而且可以很好的解决 init 方法过多难以管理的问题。先来看下如何实现,顾名思义,builder pattern 使用另一个名为 builder 的类来创建我们的目标对象,还是上面的例子,代码如下://UserBuilder.h\n@interface UserBuilder : NSObject\n@property (nonatomic, strong, readonly) NSNumber*
userID;\n@property (nonatomic, strong, readonly) NSString*
userN\n@property (nonatomic, strong, readonly) NSString*
\n\n- (UserBuilder*)userID:(NSNumber*)userID;\n- (UserBuilder*)userName:(NSString*)userN\n- (UserBuilder*)signature:(NSString*)\n@end\n\n//UserBuilder.m\n@implementation UserBuilder\n- (UserBuilder*)userID:(NSNumber*)userID {\n
_userID = userID;\\n}\n- (UserBuilder*)userName:(NSString*)userName {\n
_userName = userN\\n}\n- (UserBuilder*)signature:(NSString*)signature {\n
_signature =\\n}\n@end\n接下来 User 的 init 方法从 Builder 中获取 property 的初始值://User.h\n@interface User : NSObject\n@property (nonatomic, strong, readonly) NSNumber*
userID;\n@property (nonatomic, strong, readonly) NSString*
userN\n@property (nonatomic, strong, readonly) NSString*
\n\n- (instancetype)initWithUserBuilder:(UserBuilder*)\n@end\n\n//User.m\n@implementation User\n- (instancetype)initWithUserBuilder:(UserBuilder*)builder {\n
self = [super init];\n
if (!self) {\\n
_userID = builder.userID;\n
_userName = builder.userN\n
_signature = builder.\n\\n}\n@end\n如果要创建 User 对象,则按照这种方式:UserBuilder* builder = [[[[UserBuilder new] userName:@\"peak\"] userID:@1000] signature:@\"roll\"];\nUser* user = [[User alloc] initWithUserBuilder:builder];\n这样我们避免了书写多个 init 方法,同样 User 对象也是 immutable 的,也做到了只在 init 方法中做一次赋值操作,每个场景都可以按照自己的需求初始化部分 property,当然最后我们需要在 initWithUserBuilder 中为每一个 property 赋值, initWithUserBuilder
扮演的角色类似于 designated initializer。追求代码美感的同学可能发现了, UserBuilder 的创建语法很丑陋,多个 [ ] 套嵌使用。为了让代码更好看一些,我们也可以使用 block 来创建:User* user = [User userWithBlock:^(UserBuilder* builder) {\n
builder.userName = @\"peak\";\n
builder.userID = @1000;\n
builder.signature = YES;\n}];\nbuilder pattern 在 Android 平台使用的比较多,我在 iOS 平台上鲜少有看到使用的场景。builder pattern 的不足之处也比较明显,需要另外定义一个 builder 类,多写一些代码(property 基本都重复写了一遍)。个人觉得,在 property 数量较多,初始化的场景也比较多的时候,在 iOS 上使用 builder pattern 也会是个不错的方案。designated initializer vs builder pattern,这二者之间的不同其实很好的体现了语言本身的差异性。学习过 java 的同学就能明白,在 java 的世界中,一切都是可以被封装成对象的,使用 java 的时候,经常要定义各式各样的辅助类来完成某个任务,好处是封装度高,类职责划分粒度小,缺点是类太多,有时候会为了封装而封装,某些场景代码反而不够直观。经读者反馈,原来这篇文章的主题已经被写过了。看过之后发现比我写的更全面,推荐大家阅读,。总结简单梳理了下创建对象的不同姿势,希望对大家有些帮助。欢迎关注公众号:MrPeakTech","updated":"T02:13:25.000Z","canComment":false,"commentPermission":"anyone","commentCount":7,"likeCount":15,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T10:13:25+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-7427eab84d434a01d0e3_r.png","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":7,"likesCount":15},"":{"title":"Pattern Matching 的意义","author":"mrpeak","content":"一个语言特性往往具备多种 API 表现形式,理解特性比硬记 API 更有助于我们掌握一门新语言。Swift 作为现代编程语言的集大成者,具备很多优秀的特性来帮助开发者快速高效的编写代码,Pattern Matching 就是其中之一。程序员 vs 编程语言我们所写的代码,或者说一个 App 所具备的功能,最终都是程序员和编程语言共同作用的成果。我们使用某种编程语言写代码的时候,会受语言设计者所制定的各种规则制约,这些制约背后都隐含着数十年行业经验的积累,和设计者高明的设计技巧。理解这些规则所带来的好处和设计值的良苦用心,能在大大小小的方面,让我们的代码更加可靠和稳定。前段时间,「Clean Code」的作者,Bob 大叔,在博客里吐槽了 Swift 这类新型编程语言可能会将开发者带入「Dark Path」。文章举了不少例子,大意是说以 Swift 为代表的新语言正在尝试替开发者做更多的事情, 编程语言做的越多,开发者自然就做的越少,看上去是件好事,毕竟开发者更容易犯错,但 Bob 大叔认为规则是死的,人是活的,程序员应该在代码的编写中承担更大的责任,而且有些生硬的规则会反过来会给代码的表达产生负面效果。这个观点显得有些偏激,也引起了不小的争论,对错姑且不论,对于我们开发者来说,至少要搞明白 Swift 这类新语言所包含的规则背后的意义,才能既合理的使用规则又不被规则所制,写出漂亮的代码。Bob 大叔博客地址:Pattern MatchingPattern Matching 早在 Scala, Haskell 这些语言中就存在,作为编程语言的特性,旨在替程序员分担一部分的工作。顾名思义,Pattern Matching 可以替我们解决类型匹配的的问题,这简单的一句话,在一些不同的场景下,有着不同的表现形式,表现形式一多,又会让简单的概念理解起来变得复杂。对于复杂概念的理解,我们最好能用一句话做高度精炼简洁的概括,以不变应万变。Pattern Matching 简单来说,就是编程语言替我们程序员节省了一件事,这件事可以用两个单词来描述:Check 和 Extract。Check 是指检查条件是否满足,类型是否匹配。这么说有些抽象,我们可以把 Check 想象成一个函数,这个函数接收两个参数,参数一是源数据,参数二是目标数据,返回值是一个 Bool 值,Bool 值表示两个参数之间的某种关系是否成立,至于这个关系具体指什么,就因场景而异了。我们可以用如下一个函数来表达:func check(targetData: [Type1], sourceData: [Type2]) -& Bool
//check relation, return true or false.\n}\n源数据和目标数据可以是不同的类型,只要他们之间满足某种关系,关系的定义取决于 check 函数内部的实现。系统默认替我们实现了一些关系,也允许我们通过操作符重载的方式自定义关系。看几个简单的例子:var result: String? = getResult()\nswitch result {\ncase .none:
print(\"no result\")\ncase let r:
print(\"result is \\(r)\")\n}\n上面是 Pattern Matching 在 switch 当中的应用。源数据是 result,type 为 optional。目标数据是 .none 和 r,type 为具体的 String。二者之间的关系是个等式关系,这个关系默认由系统提供。这个关系说白了,无非是一个 if,else 语句,来判断 result 到底是 .none 还是具体的值。当然我们也可以直接写 if,else 来替代 Pattern Matching,在 Objective C 中,没有 Pattern Matching,我们确实是用 if,else 来做 check 的。但明显使用 Pattern Matching 我们可以写更少的代码,同时让代码更容易阅读,表达更清晰,这也是 Swift 中引入 Pattern Matching 的原意所在。Pattern Matching 除了做 check 之外,很多时候也被用来做 extract。Extract 是提取数据的意思,Pattern Matching 配合其他关键字就可以完成数据的 extract。比如上面的代码中,switch 配合 case let r: 就将 result 中所包含的值提取到了 r 中。严格来说第一步还是 check,check 关系满足之后,再做 extract。Pattern Matching 虽然表现形式繁多,但最后都会落实到 check 和 extract 这两个单词之上,大家可以尝试去发掘下其他 Swift 语境下 Pattern Matching 是如何做 check 和 extract 的。深入了解过 Pattern Matching 的同学应该知道,其实 Pattern Matching 本质上就是一个特殊的操作符:~=。我们可以把这个操作符等同于我们上面提到的 check 函数。当我们针对自己定义的数据类型做 ~= 的操作符重载之后,就可以实现自定义规则的 Pattern Matching 了。再换句话来描述 Pattern Matching:检查两个数据之间是否满足某种关系。Pattern Matching 并不是 Swift 所独有,但在 Swift 语境下,主要是通过 case 这个关键字来体现。在 Objective C 的语境下,case 是和 switch 搭配使用,和 if 的含义接近,而在 Swift 中 case 关键字具备了 Pattern Matching 的含义之后,case 关键字可以在更多的场景下来使用,可以和更多其他的关键字配合,以延伸 Pattern Matching 的使用场景。除了 switch case 之外,我们还可以用 for case,while case,if case,guard case,无论关键字如何搭配,其本意都是在做 Pattern Matching。这也是为什么初次接触 Swift 会感觉语法太过灵活且不易掌握的原因。总结希望这篇文章能帮助一些朋友简单点理解 Pattern Matching 的意义所在。另一个目的是介绍一个学习小技巧:对于新知识点的学习,记 API 或者编程语言的各种表现形式,不如去理解其背后的设计思想。不过总结和提炼思想,往往需要花费更多的时间和精力,回报是对知识的掌握更深刻和牢固。欢迎关注公众号:MrPeakTech","updated":"T02:59:29.000Z","canComment":false,"commentPermission":"anyone","commentCount":4,"likeCount":6,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T10:59:29+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-53ff9c141d0b3c442d440d4ee81d1042_r.png","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":4,"likesCount":6},"":{"title":"iOS 编程中的 Type System","author":"mrpeak","content":"接之前一篇 Pattern Matching 的文章,Type System 是另一项编程语言,或者说编译器所提供的便利。Pattern Matching 可以让我们少写代码,而 Type System 可以让我们少犯错误,减少 Type 相关的各种 bug。一般来说,我们写代码时为了降低 bug 率,一是依赖于程序员自身的经验积累,二是靠编译器做各种静态检查,type system 则是属于静态检查这一类。Swift 较之 Objective C 的 type system 有了很大的改进,下面文章中主要是介绍 Swift 相关的一些特性。在开始之前,先聊下如何靠程序员经验来降低 bug。Bug 第六感从我自身的体验推断,我相信大部分程序员在写代码的时候,对于代码是否存在 bug 是有一定感知的。只不过有些新入行的朋友,在写代码的时候操之过急,或者由于和产品经理讨论吃了败仗心情不佳,coding 时目标变成了写能 work 的代码,而不是写高质量的代码。facebook 面试有一个环节叫 whiteboard coding,要求程序员能在白板上写出几乎是 「bug free」的代码,这听起来有点耸人听闻,写代码没有 Xcode 提示就罢了,bug free 更是难上加难了。写一段几乎没有 bug 的代码到底有多难呢?说难不难,说易不易。写代码时,慢一点,再慢一点。好好的想下代码有可能出错的地方在哪,想清楚了 bug 就少。一般来说,要减少 bug 量,一是靠程序员自身修养,二是靠编译器提供的静态检查。Type System 属于第二类,在深入之前,先简单聊下如何靠自身修养降低 bug 率,提升 bug 感知的第六感。少写 Bug 的简易准则要提升程序员的自身修养来降低 bug 率,是个大话题,而且多和自身的知识积累有关,需要长年累月的学习和养成。本文的目的不在于此,所以只介绍一个小技巧来养成感知 bug 的好习惯。我们可以粗略的将我们所写代码分为 data 和 behavior,behavior 围绕 data 执行各种逻辑。一个函数可以看做是一个 behavior,而函数本身又由若干 data 和 behavior 所构成。很多时候,代码有 bug,是因为 data 出现了预料之外的变化。有一个简易准则可以减少这类 bug:只要遇到 data,就做 aggressive check。具体到一个自定义的函数,函数会包含哪些 data 呢?细心理一理没几个。函数入参内部临时变量依赖的外部变量返回的最终结果这几类 data 是我们在一个函数中最经常遇到的,只要我们对他们做好检查就可保平安。做哪些检查呢?最常见的也就那么几样,比如是否为 0,为 nil,数组元素 count 为 0,如果期待正数则是否为负数,数组是否越界,多线程是否安全等。做下总结就可以完成大部分的可靠性检查。简而言之,只要是使用 data 的时候,就围绕 data 做好应该的检查,做到这点,写一个几乎没有 bug 的函数就不怎么难了。这个原则更精准的表达是:在任何场景下,无论是定义变量还是使用变量,都对变量的各种可能性做检查和保护。Type System回到我们的正题 Type System,Type System 是由编程语言和 type 相关的各种规则所构成。它的用处也简单,可以帮助我们减少和 type 相关的 bug。编程语言大多都有自己的 Type System,Objective C 和 Swift 都有。在开始讨论 Type System 之前,要明确 Type 的定义。Type 就像自然语言里的名词,动词,介词等等,可以规范我们的表达。在编程语言中,type 则是一种避免代码表达错误的约束。Type 不仅仅包括诸如 int,float,bool 这类 primitive type,对象的 class type,还包括 function,block 等不那么明显的 type。变量,常量,函数等等都(且一定)具备 type 信息,有些一眼能看出,有些要靠推断。Static vs Dynamic有些 type 信息是交由程序员去推断和维护的,有些则是留给编译器去管理的。前者的 type 约束是在 runtime 检查的,偏向「dynamic」,后者则在 compile 的时候就做了 check,偏向「static」。很多技术文章都会讨论编程语言的 dynamic 和 static 属性,我们要分清楚 dynamic 和 static 其实是个宽泛的说法,他们可能包含不同的语义和场景。dynamic 和 static 既可以用来讨论 type system,又可以用来形容函数调用机制。比如我们认为 Swift 是 statically typed,但 Objective C 的 runtime 和 message 机制又显然是 dynamic 的,这两种场景下 static 和 dynamic 说的其实不是一回事。回到 type system 的场景,讨论下语言是 statically typed 还是 dynamically typed。还是要进一步看场景,在 Objective C 中,type 信息既可以是 static 的,也可以是 dynamic 的,看我们如何使用了,比如下面的代码中 type 信息是 static 的:int i = 0;\ni = @“test”; //compile warning\n因为 type 的上下文信息是完整的,编译器可以做类型判断。而如下代码中 type 信息则是 dynamic 的:id obj = [NSData new];\nobj = [NSObject new];\n由于 id 可以指向任意对象类型,id 可以在不同的时间点里指向不同的类型,编译器此时无法根据类型信息作出判断,是否存在类型使用错误的。所以我们会说像 Objective C 这类编程语言在 type system 上,是同时具备 static 和 dynamic 属性的,关键还是看具体的使用场景。但 Swift 却是货真价实的,纯粹的 statically typed 编程语言,不具备任何 dynamically typed 的属性。比如在 Swift 中,如下代码是无法通过编译的:var i\n编译器会提示:Type annotation missing in pattern,也就是缺少 type 信息。要声明一个变量,我们可以通过如下两种方式来提供 type 信息:var i = 0
//方式一,implicit typing\nvar i: Int
//方式二,explicit typing\n方式一是通过赋值来做 type inference,方式二是通过显式的提供 type 信息。Swift 在 type 的使用上非常苛刻,当之无愧为 statically typed。显然,static type 比 dynamic type 更安全,编译器可以帮我们做类型检查,这也是为什么 Swift 比 Objective C 在 type safety 上更优秀的原因。当然,dynamic type 并非全无好处,初期开发起来速度会快于 static type,而且省去了编译时的 type 检查,每次编译速度更快。缺点是一旦出现 runtime 中的类型错误,要花更多的时间去调试,要写更多的 test case,准备更多的文档。这种缺陷在较大规模的项目上会更明显,Swift 选择 static type 策略应该也有这方面的考虑。Type Inference类型推断(type inference)也是 type system 当中的一个常见概念。不少编程语言比如 Swift 都有 type inference 的功能。type inference 有什么用处呢?statically typed 的编程语言决定了变量都必须具备类型信息,意味着我们每次使用变量的时候都需要显式的声明 type 信息,比如在 Objective C中,这样会显得有些繁琐和啰嗦,一旦有了 type inference,我们可以在代码中省略掉很多关于 type 累赘的表述。我们看如下代码:var i = 0\n这行代码中,有两个实体有 type 信息,变量 i 和常量 0,0 默认的 type 信息是 int,i 的 type 信息没有显示的声明出来,但在
Swift 中,由于 0 被赋值给了 i,所以可以通过 type inference 推断出 i 的 type 信息也是 int。这种类型推断会发生在很多程序员意识不到的角落,这种具备传染特性的 type 信息可以层层叠叠,一级一级的输送到更多的其他变量实体。编译器就是通过这种传染的特性来做 type inference 的。在 Swift 中,type inference 配合 static type 让代码既精炼又安全。Optional Type前面提到 type 信息本质上是一种约束,可以避免 type 的使用错误。我们在编写代码时,经常遇到的一种 bug 是对于空对象或者说对象为 nil 情况,漏写了为空的判断。Swift 通过引入 optional type 来强制开发者考虑 nil 的场景,更妙的是,当「是否为 nil 」成为 type 信息之后,编译器也可以一起来帮助检查 nil 的使用场景。看下 optional type 的定义就一清二楚了:public enum Optional&Wrapped&
\ncase none
\ncase some(Wrapped)\n}\n通过 enum type 来定义 optional type,以表达是否为 nil 的含义。这也是 Swift 为什么要引入 optional type 的根本原因,让编译器以类型检查的方式,来帮助开发者分析是否存在漏判 nil 的场景。Generic Type再次强调下,type 本质上是一种约束。当我们定义 int i 时,int 就成为了变量 i 的一种约束。我们可以把这种约束进一步强化,比如引入 generic type(泛型)。generic type 有两个主要特性,其一是允许开发者在后期再指定 type 的值,其二是可以把 type 约束施加到指定的代码范围里。理解这两个特性,是我们掌握 generic type 各种表现形式的基础。generic type 可以让我们写出更加符合 type safety 的代码,Objective C 和 Swift 都支持定义 generic type,只不过 Swift 中 generic 的概念更加广泛,应用面也大很多,在一些显式的和隐式的地方都存在 generic 的身影。比如前面提到的 optional,其实也是个 generic type。generic 可以作用于很多其他的复杂 type,比如 optional 就是 generic 作用于 enum 的结果,除了 enum 之外,还有 struct,class,function 等都可以和 generic 搭配使用。我们再看一个 Swift 自带的例子,Array:public struct Array&Element& : RandomAccessCollection, MutableCollection {
public mutating func popLast() -& Element?
...\n}\n只需要在 struct 名字后面以 &xxx& 的形式,就可以在 struct 的作用域内部声明一个新的 xxx type(xxx 在 Array 的 extension 中也是可见的),xxx 可以在使用时再确定具体指代什么 type。使用 Array 的时候,我们也不必显示的指明 xxx 代表什么,可以依赖前面提到的 type inference:var arr = [Date()]\narr.append(UILabel()) //compile error\n上面第二行会报错,这是 Swift 和 Objective C 的差异之处,在 Objective C 中,我们可以在 Array 中放入不同类型的对象,而在 Swift 中,一旦 Array 中的元素类型被 type inference 确定,就不能放入其他类型的对象了。generic type 和 type inference 配合的场景在 Swift 当中经常出现。Named Type vs Compound Typenamed type 指的是我们传统意义上所理解的 data type,例如 int,float,string,自定义的 class 等等。在 Swift 中,设计者引入了 compound type 的概念,可以把 compound type 理解成 named type 的某种集合,比如 function 和 tuple,他们往往都包含多个 named type。我们知道在 Swift 中,function 是一等公民,可以作为变量声明,参数,返回值等等,要理解并运用这一点,需要在思维上做转换,把 function 也看做一种 data type(compound type),在原先使用 named type 的位置,我们几乎都可以使用 compound type。compound type 增强了语言的表达力,但其灵活性在一些场景下,也会一定程度的降低代码的可阅读性。compound type 可以包其他 compound type,可以一层层的套嵌,这种 nested compound type 有时候会让代码看上去没那么直观,比如下面一段 Swift 代码:func someFunc(f: (Int)-&(Int, ()-&(Int))) {}\n上面的函数里,function 和 tuple 作为 compound type 存在套嵌,代码本身虽然不长,要一眼把其中包含的 type 都识别出来不那么容易。compound type 的使用可能是不少从 Objective C 转向 Swift 的同学初期感觉难以适应的原因之一。总结上述所提到的概念都是和 type system 相关的基础知识,虽然基础,却十分重要。对 type system 建立完整全面的认识,多利用语言本身的 type 制约来避免 bug,可以让我们对自己代码的安全性有更好的把握,对于代码质量的提升也有极大的帮助。欢迎关注公众号:MrPeakTech","updated":"T02:38:11.000Z","canComment":false,"commentPermission":"anyone","commentCount":0,"likeCount":11,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T10:38:11+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-53ff9c141d0b3c442d440d4ee81d1042_r.png","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":0,"likesCount":11},"":{"title":"闲聊 Hash 算法","author":"mrpeak","content":"最近读了一篇好文:【微信高并发资金交易系统设计方案——百亿红包背后的技术支撑】,其中关于高并发性能问题的解决方案中,有应用 hash 算法的思想。想起公众号后台里断断续续有读者提起算法方面的问题,觉得可以写篇文章聊聊算法中的 hash 算法。顺道科普下算法与数据结构的重要性。开讲前,先跑题闲聊下程序员的技术功底。我常说每个程序员都有自己独特的技术视野和知识盲区,不同程序员之间很难因为某些知识点储备不一样而分个高低好坏。但我们工作当中,又能明显感觉不同团队成员之间的技术水平存在差异,到底差在哪呢?很多人调侃批量生产的培训程序员,那这些人和四年的大学本科之间又有多少距离?仅仅是时间吗?差在基本功,基本功有很多项,数据结构与算法就是其中之一。虽然是基本功,却是最难储备和最易忽视的。行业越浮躁,变化越快,开发平台越便捷,高级 API 越多,基本功的重要性就越容易被忽视。即使能意识到基础薄弱,肯下定决心腾出几个月时间恶补基本功不是件容易的事,尤其是参加工作后,琐事繁多,一时热血下定的决心能坚持一周都属不易。后台偶尔有人问及程序员如何进阶的问题,以我这些年所经验,回过头来夯实下基础,对大部分人都会有奇效。数据结构与算法的学习难度经常被夸大,不少人甚至谈算法色变,尤其无法忍受在面试当中问及算法问题。其实多点儿耐心,多投入些时间,学习算法并不难。至少学习基础的算法并不难,理解算法和去 leetcode 刷题是两回事,刷题所涉及的算法多需要技巧,基础的算法知识和其他计算机知识一样,不需要特别「聪明」的大脑,大多数人都能学会。Peak 君没刷过题,但对算法方面的知识也比较有自信。数据结构和算法是相辅相成的,基础的其实就那么些:时间复杂度的概念,List,Array,Stack,Queue,Tree 等。Graph 实际应用中较少遇到,可以不做深入了解,但 BFS,DFS,Dijkstra 还是应该知道。基础的算法需要能达到手写的程度,比如排序至少能写出两种时间复杂度为 N*logN 的算法。理解这些比去 leetcode 刷题重要,学习难度也并不高。学习这些的意义在于掌握解决问题的基础思路,形成计算机思维,比如 divide and conque,recursive 等常规思想。再回到本文重点 hash 算法。关于 hash 算法的实现原理和关键概念,网络上已有不少好文加以介绍。本文不做原理层面的解释,只谈应用。对实现感兴趣的可以搜索关键字:hash,load factor,扩容,hash 冲突解决等。Objective C 中对于 hash 的应用主要封装在两个数据类当中:NSDictionary 和 NSSet。这点大家都知道,hash 算法能以空间换时间,在 NSDictionary 和 NSSet 中,判断一个元素是否存在只需要 O(1) 的时间复杂度。这一特点也使得在一些需要快速存取元素的场景,比如 Cache 设计,也能看到 NSDictionary 的身影。当然 hash 的应用远不止如此,做的应用越多,解决问题越深入,碰到 hash 算法的概率也会更高。「The Algorithm Design Manual」一书中提到,雅虎的 Chief Scientist ,Udi Manber 曾说过,在 yahoo 所应用的算法中,最重要的三个是:hash,hash 和 hash。其重要性不言而喻。书中还举了一个很有趣的应用例子,请听题:一场拍卖会中,物品是价高者得,如果每个人只有一次出价机会,同时提交自己的价格后,最后一起公布,出价最高则胜出。这种形式存在作弊的可能,如果有出价者能 hack 进后台,然后将自己的价格改为最高价 + 1,则能以最低的代价获得胜利。如何杜绝这种作弊呢?三分钟思考时间,一,二,三。参与者都提交自身出价的 hash 值就可以了,即使有人能黑进后台也无法得知明文价格,等到公布之时,再对比原出价与 hash 值是否对应即可。是不是很巧妙?看到这,可能有朋友想到了 MD5,SHA。是的,上面的做法,和我们在 server 端存储密码的 MD5 值而非明文,是同一种思想,殊途同归。hash 算法包含有多种解决问题的思路,这里可以归纳为【通过 hash,生成不可逆的信息摘要】。书里还有关于 hash 应用的其他有趣的场景(比如论文内容抄袭检测),都值得一读。回到微信红包的例子,后台工程师为了防止抢红包时,用户的流量都涌进同个服务器,在同个 DB 上读写而导致的性能下降,采用了通过 hash 算法来分流的策略。每个红包创建的时候分配一个 ID,通过算法将 ID 映射到不同的逻辑服务器,一气呵成的解决方案。这里体现的是 hash 算法的另一种思想:【hash 能以 O(1)的复杂度将内容映射到位置】。这种应用 hash 的思路非常常见,还有不少例子。去年写过一篇多线程文章【正确使用多线程同步锁@synchronized()】,当时阅读 OC 源码的时候也看到了 hash 的身影。@synchronized(token) 中的 token 通过 hash 算法存储到了一份手动维护的 cache 中,cache 的 key 使用的是 token 的内存地址。@synchronized 使用多了之后,如何快速的通过 token 取出对应的锁,对多线程的性能至关重要。hash 算法恰能以 O(1)的时间复杂度,以 token 为 key 取出对应的锁,和上面红包的例子本质上是同一种思想。即内容与位置之间的快速映射关系。我还见到过很多例子,多多少少都有 hash 算法的影子。大家说 hash 算法是不是很重要?数据结构与算法是不是要学?不懂算法,有时候看别人代码就真如「过眼云烟」了,观其形而不知其意。别人赏雪,心中所念是:「都城十日雪,庭户皓已盈」,你只能一句「我靠!好美!」以抒胸臆,岂不煞风景?之前有几位读者在后台留言,求算法书推荐。算法方面的好书有不少,不过大多是英文的,除去一些专业术语外,多是一些简单的词汇,阅读难度不算大,维坚持二字。推荐一本现今还留有印象的:《编程珠玑》,英文名《Programming Pearls》,不是大部头,建议啃英文原版。最近有些忙了,不过至少还是会坚持一周一到两篇,和大家分享些技术心得和职场感悟。坚持做一件事不容易,写公众号这事,怎么都得挺住。写年终总结时,就多了件可以吹嘘的事 :)","updated":"T02:34:02.000Z","canComment":false,"commentPermission":"anyone","commentCount":3,"likeCount":23,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T10:34:02+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-e7f04eab476eea78c61c83e_r.png","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":3,"likesCount":23},"":{"title":"技术文章的阅读姿势","author":"mrpeak","content":"阅读技术文章可以说是我们程序员的日常之一,Peak 君每天也会进行定量的阅读。特写一篇小文分享下心得,介绍下过去几年,在纠正阅读习惯上所做的一些努力和取得的成果,或许可以帮助一些朋友,节省少许阅读时间,提升一点学习效率。差不多两年前,我开始搭建 Android 相关的知识体系。最开始的想法是从基础知识的积累开始,正好这几年社区的技术分享盛行,「掘金」、「开发者头条」、「简书」等渠道上每天都有大量的新文章发布,文章主题五花八门,内容深浅不一,看上去都很不错。可坚持读了几天之后,深感自己踏进了错误的方向,读过的比较有代表性的一篇文章是:「关于 Service 你所需要知道的一切」,文章内容的质量远低于标题所勾起的预期,无非是把官方文档里的几个知识点做下摘录,有些地方反而因为摘录不全导致逻辑不连贯,顺藤摸瓜找到出处之后,发现官方文档才是我的救赎。最初学习 iOS 的时候,也走过类似的弯路,急于在短时间内掌握要点,急于能尽可能快的开始写代码,所以略过了看上去冗长繁琐的官方文档,转而搜索各类总结文章以求速成,结果是所有心存侥幸投机取巧跳过的知识点,都会在某一天变成一个个费时费力去填的坑。对于基础知识框架的搭建,没有比官方文档更好的起点了。当然官方文档太过全面,无法在一开始就通读一遍。正确的做法是,在计划深入某一块知识域之前,先读一遍与之相关的官方文档,如果还有疑惑再去其他渠道搜索相关知识点,做进一步的深入发掘,这就涉及到下面有关「精读」的话题。从这一角度来说,上面提到的三个渠道,对于基础知识的积累,阅读价值并不高,很有可能,整篇读下来都是些零零碎碎已知的知识点,收效甚微,绝大部分文章都是标题惊艳,内容单薄。当然不排除偶尔会遇到一些高质量的深度好文,但这些好文大多是转载的,是有自己的发布渠道的。按我以往经验观之,好文是稀缺资源,数量稀少且发布渠道稳定,比如一些类似 bugly 这样的大厂对外渠道,国内外一些优秀的博客写作者,或者是偶尔在微博上被大量转发的文章。这些渠道都可以被分门别类的装进浏览器的收藏夹,无需每次去「掘金」这类的渠道做大海捞针式的搜索。阅读行为一般可以划分为两类:泛读、精读。技术类的文章阅读也不外如是,不同点在于,技术阅读应该重精读,轻泛读。技术知识的价值能否得以体现,关键在于最后是否在阅读者的记忆里得以沉淀。泛读行为很难形成有效且深刻的记忆,但偏偏泛读较之精读要轻松很多,所以很多初学者习惯性的去做大量的泛读行为,看标题感兴趣就点进去浏览,一天下来能读好几篇,最后形成收获颇丰的错觉,这其实是一种潜意识下的偷懒行为。这种阅读行为中收获的知识,别说难以在实际项目中去应用,就是在面试中聊聊都是不能。这条弯路 Peak 君也走过。在精选、形成完自己特有的阅读渠道之后,我们应该调整自己的阅读行为,尽量强迫自己去做精读,去深入挖掘、消化自己认可的深度好文。一篇技术好文,一天能消化干净算得上奢侈了,有些文章需要陆陆续续读上一个星期也是可能的。精读一篇文章确实会比较辛苦,但你想想,写文章的人更辛苦,不光要理解文中所谈的各个知识点,还需要做串联归纳以成体系,精读一篇深度好文就是一次和某个领域的专家做深入交流的机会,只是草草的浏览一遍而错过一次宝贵的加深知识域的机会岂不可惜。如果文章主题和当前自己的关注点切合,读起来越是费力的文章,其价值也越高。简而言之,技术类文章阅读,是宜少不宜多,宜静不宜泛。相信不少人阅读技术文章时,都有过类似的体验:在文中发现一个陌生的术语,转而 google,搜出更多的术语或者相关文章,于是切换到新的环境中去继续阅读行为,有时会如此反复跳跃几次,最后浏览器上的 tab 越来越多,感觉文章根本读不完。这是由于技术的知识体系往往是个树形的结构,单个术语下都有其相关的知识域,可以一层又一层牵扯出更多的子术语。在阅读文章遭遇这种树形结构的时候,要能抑制住自己不停探索的欲望,对于技术术语的学习只做适度延伸,最终的目的还是在于完成根部文章的阅读。Peak 君的阅读习惯是只做一到两层的延伸,比如刚开始学习 ReactiveCocoa 的时候,接触到函数式编程,了解函数式编程又挖出了 pure function,pure function 又包含若干其他的概念,可以持续的深入下去,但到 pure function 这一层之后其实就可以适可而止了,可以回过头来完成原先的阅读任务。要克制对于知识的求索欲,有时也不容易。现实是,我们精力有限,无法在每个领域都成为专家,看到优质深入的分享好文时,很容易产生知识的焦虑感,迫使自己去阅读并无太大交集的话题,这反而容易造成时间和精力上的浪费。不同技术人员之间,在知识的储备量上其实不具备可比性,真正需要在意的是学习和解决问题的能力。再者是对于阅读时间段的选择。程序员工作时很容易被打断,产品,设计,测试随时都有可能找上门,做深度阅读时一旦中断,阅读效果会大打折扣。我们应该根据自身的情况,尽量选择没人打扰的时间段来做阅读,可以是早上刚到公司,或者别人午睡时,总之越安静,越没人找越好。最后一点,可以从慌不择食的技术文章阅读时间里,多抠出一些来,完完整整的阅读一些大部头的英文原版技术书,这才是知识学习的正餐,别人写的零散文章更适合作为饭后甜点。比如 Peak 君经常推荐的 【TCP/IP 协议详解】,这种经典书籍,即使耗费一年的时间去阅读,也远远强过读一年别人所写五花八门的技术文章。说了这么多,提炼下摘要:对于基础知识的阅读,要重官方文档,切莫心急动手,看完文档形成知识体系后再写代码不迟。减少泛读行为,避免漫无目的的随意浏览技术文章。注重精读,一天一篇不算少,一周一篇也正常。重阅读质量而非数量,挑选每天安静且不易被打断的时间点来阅读,尽量多啃原版书。一点小心得,全文完。欢迎关注公众号:MrPeakTech","updated":"T03:11:17.000Z","canComment":false,"commentPermission":"anyone","commentCount":3,"likeCount":16,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T11:11:17+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-e7f04eab476eea78c61c83e_r.png","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":3,"likesCount":16},"":{"title":"TCP/IP 系列之初印象","author":"mrpeak","content":"如果有人问我推荐技术类书籍,我个人首选一定是【TCP/IP 详解】原版。这种大部头的经典之作里所包含的知识,说是浩如烟海也不为过,每次重新翻起总会有一些新的体悟和收益。经典的好处在于,它描述的一般是通用的计算机问题,可以帮助读者形成通用的计算机思维,而不局限于某一领域或某一场景,常看常新。当然这本书的厚度也到了让人望而生畏的程度,如果着急短时间内啃完,多半会由于无法完成目标的沮丧半途而废。我在前面的文章中也提到过,获取知识的时候容易陷入「越看越饿」的焦虑感,这种情形下应该放慢节奏,指定长期的阅读计划,放眼于较长时间跨度的收益。书和文章并不是越多越好,看了多少,能消化多少,是个人的事情,没有比较的意义。知识营养,点滴皆是财富,积跬步而至千里方为正途。一般来说,看一手技术书籍比看技术文章更有意义。但好的技术文章,如果能做到高屋建瓴,提纲挈领,对于初学者入门也是具备价值的。直接啃英文原版,没有交流和正向反馈,最后能否读完十分考验读者的意志力。我打算写一个 TCP/IP 的系列文章,目的在于以通俗易懂的语言和比喻,让读者能预先形成一个初步的知识大纲,之后再能有针对性的逐个知识模块去攻破。即使最后无法通读【TCP/IP 详解】全本,也能对网络协议栈形成大致完整的框架理解,以后在项目中遇到问题,至少知道如何下手,从哪个方向去深入突破。感兴趣的读者在阅读这个系列文章之时,最好能同时翻阅下【TCP/IP 详解】,疗效更佳。打算写这个系列的另一个原因,是我在平时阅读英文书籍的过程中,深感中西方思维方式的差别,会让一些原本简单清晰的概念模型变得晦涩费解,一些关键点如果无法攻克,会阻碍进一步学习的兴趣。另外,我会以 iOS 程序员的视角,尽量结合一些实际应用中的具体场景来讲解,不至于太过枯燥,感觉不到学习的意义所在。这个系列会在后续的时间里,夹杂着其他文章一起写,可能会耗时较长,又或者会一直写下去。我现在感觉公众号越来越像「读者群」,留下来坚持读文章的多半和我「品性相投」,不论我写技术还是写心得都会坚持阅读。于我,写文章是一件乐事,于读者,看文章多少有些收获。一场双方都觉得愉快的聊天岂不是妙事一件。接下来进入正题。TCP/IP 初印象当我们聊 TCP/IP 的时候,一般是在说整个网络协议栈。也就是大家熟知的 OSI 七层模型。我们先抛开七层协议和三次握手这些生硬的知识点,简单聊聊网络协议栈到底是做什么的。网络协议要解决的问题很简单,打个比方,就是让身处不同地方的两个人,能完成聊天通讯。无论我们谈及哪一方面的网络知识点,最后都不会脱离这个最基本的场景模型。首先我们需要分清楚流和包的概念,这也是不少初学者容易混淆的问题。当我们以 iPhone 上的 App 客户端,向我们的 Server 发数据的时候,我们可以用 Charles、MITMProxy 这类工具抓包,但我们同时也知道 TCP 是基于流的协议,这二者的区别和联系在哪呢?0 和 1 是计算机世界的基础粒子,大量的 0 和 1 组合在一起就形成了一个流(Stream),客户端向服务器发送数据的时候,说白了就是一堆 0 和 1 的组合。一次完整的 http 会话是建立在一个 TCP 连接之上,这个 TCP 连接的生命周期内所有发送的数据最后可以看做是一个流。而在这个流里,我们可以按照某种规则把它切割成一个个的包(packet)。比如 TCP 三次握手里就包含了 SYN,SYN+ACK,ACK 三个包,而这三个包,不过是整个 TCP Stream 最开始的部分数据而已。所以简单来说,一个 TCP 连接里,是既有流的概念,又有包的存在,有些问题场景下会谈论流,另一些则会说起包,端看具体的场景如何。在开始学习 TCP/IP 之前,最好能在脑中形成一幅 Gif 动态图像,客户端和服务器之间有两根管道,一根上行(从客户端到服务器),一根下行(从服务器到客户端),管道里流动着无数的 0 和 1,有时候管道里是满的,有时候管道里则空空如也,每次发送数据,都会有大量的 0 和 1 从一端涌向另一端。有了这个基本的抽象理解,就可以进一步填补一些图像细节。比如 0 和 1 是以什么形式流动的呢?这个在不同时代里,所用的物理硬件不同,原理自然也不同,现在大多用的是光纤,光纤里的 0 和 1 流动可以用下图表示:物理层离应用层较远,深入学习的意义无法立竿见影,遇到实在费解的,可以先跳跃式的阅读,但有些基础的细节还是需要明白的。比如,我们知道光速大致是 300000 km/s ,而光在光纤中的物理传播速度大概是光速的
2/3 ,这个知识点可以帮助我们大概估算一个 TCP 连接中的 RTT 值。RTT 即 Round Trip Time,表示一个包从发送方发出,到收到接收方的反馈,一共耗时多少。RTT 值反应的网络延时的状况,在很多场景下分析问题都有意义,比如 TCP 重发机制中的 RTO 就跟 RTT 有关,后面的文章我们会细说。一些知识点往往是环环相扣的,跳过的内容过多,后面学习起来会更费解。延迟再说下延迟的概念,延迟和带宽是我们谈及网络状况的两个重要指标。延迟看上去很好理解,延迟对应的英文术语是 Latency,不少人会觉得延迟主要跟通讯双方的物理距离相关,距离越远,自然延迟越高。其实网络延迟严格来说,是由两部分组成的,其一是 Transmission Delay,另一个是 Propagation Delay。这里出现了一个新单词 Delay,Delay 和 Lantency 的含义在中文语境里很难区分,但在英文语境下,二者的使用却是有明显的场景差别的,这也是为什么读英文原版很重要,可以让被描述的问题细节更清晰。先说 Transmission Delay。计算机世界里的 0 和 1, 最后要能在光纤中传输,需要在 Physical Layer 将数字信号转化为物理信号,这个转化也是存在速度瓶颈的,我们用 Rate (bits/seconds) 来描述这个转化的速度,Rate 表示每一秒钟里,硬件设备能将多少 bits 转化为光信号放入光纤中,那么 Transmission Delay 就可以用如下公式表示:Transmission Delay = M
M 表示有多少 bits。Transmission Delay 描述的是硬件转化信号的延迟,这一步里信号还没有正式进入光纤中传播,这种延迟和通讯双方的物理距离是没有关系的。再来是 Propagation Delay。这才是大部分人所理解的传播延迟,和距离直接相关。也可以用如下公式表示:Propagation Delay = L / SL 表示两地的物理距离,S 表示光纤中的传播速度,即为
光速。最后我们才是所说的网络延迟:Latency = Transmission Delay + Propagation Delay像这种知识细节,如果不去看书,是很难形成准确认知的,这种细节丰满的认知不是快餐式技术文章可得,需要静下心来,花时间慢慢啃,一旦形成之后,就牢固的沉淀在记忆里,这样才算真正掌握了这方面的知识。管道容量再说个有意思的知识点。我们经常将网络通道比作自来水管道,这种比喻其实很贴切,网络管道和自来水管道一样,也是存在容量限制的。也就是说,光在光纤中传播的时候,会像自来水一样,填满整个光纤,光纤也存在管道容量的限制。有时候,光纤管道会变得很拥挤,一旦拥挤起来,有些包就要排队等待,延迟就会增加,丢包率也会随之上升,整个网络状况就会变差,这也是为什么 TCP 要做 Flow Control(流控),Flow Control 也是个很经典且有趣的问题,后面会专门写文章介绍。网络管道的容量也是可以通过上面提到的元素,用公式计算的:BD = Rate * Propagation Delay大家可以思考下这个公式为什么会是这样。好啦,序篇就到这,后面的文章里,我会逐个讲解一些关键且重要的知识点。一同进步吧!欢迎关注公众号:MrPeakTech","updated":"T11:53:40.000Z","canComment":false,"commentPermission":"anyone","commentCount":5,"likeCount":20,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T19:53:40+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":5,"likesCount":20},"":{"title":"TCP/IP 系列之 Header 篇","author":"mrpeak","content":"这是 TCP/IP 系列的第二篇,阅读目标是建立对网络包结构的初步认识。上一篇里,我们提到一次完整的 TCP 会话其实是一个字节流,只不过我们可以按照一定的规则来切割这个字节流,从而划分出一个个的网络包。我们经常说抓包分析网络故障,这个抓包里所指的包在不同的语境下其实意义并不相同,首先我们得对包的构成形成具象的认知。再看七层协议刚毕业那会,大家都喜欢在简历上写「精通 TCP/IP 协议」,面试被问起时就必答三次握手和 OSI 七层模型,再多就说不出来了。问七层模型是什么就机械式的从上至下背诵一遍,这离精通还差了一本【TCP/IP 详解】。我在完整的学习过一遍网络协议栈之后,深感所谓的七层模型是偏工业的说法,看到七层协议图之后,其实很难明白这七层是如何一层层互相构成的,更符合大脑感官的是另一种认知形式,是一种洋葱形的结构,层层叠叠互相包裹,可以用下图表示:左边是教材上的结构,右边是我所说的洋葱式的结构。如果以一个 HTTP 请求为例,右图中 Application 部分就代表我们用 Charles 抓包时所感知的部分,这一部分要最后转化为光信号,在光纤中传输,还需要经过一层层的转化,这个转化过程说白了,就是在每一层加上一个 header。Application 层(HTTP)的数据在经过传输层(TCP Layer)的时候,会加上 TCP 的 header,成为一个 TCP Segment。传输层(TCP)的 Segment 在经过网络层(IP Layer)的时候,会加上 IP 的 header,成为一个 IP Packet。网络层的 IP Packet 在经过链路层(Link Layer)的时候,会加上Link Layer 的 header,成为一个 Frame。最后 Frame 会在物理层,将数字信号转化为物理信号传输。这里值得特别注意的是,在每一层,有不同的英文术语来对应包的概念,比如在 TCP 层的包叫做 Segment,在 IP 层的叫做 Packet,在链路层的叫做 Frame,另外和 TCP 位于同一层的 UDP 包我们一般叫做 Datagram,不同协议层里术语并不一样,好处是,在交流的时候,我们选择不同的英文单词就能预先确立是在那一层讨论协议。而 Segment、Packet、Frame、Datagram 等翻译成中文的时候,都是译为「包」,大家说读英文原版资料是不是更好,这些术语我们需要特别记忆,可以对照下图: +-------------+-------------------------+\n | Application |
HTTP Packet
|\n +-------------+-------------------------+\n | Transport
TCP Segment
|\n +-------------+-------------------------+\n | Network
|\n +-------------+-------------------------+\n | Link
| \n +-------------+-------------------------+
光纤\n | Physical
| ====================& \n +-------------+-------------------------+\n不过有些场景下,我们也会用 Packet 来泛指每一层的包,但是用每一层自己的术语会更准确和专业,这些行话和习惯我们也需要了解。我们可以用一个公式来表示每一层协议的构成:Packet = Protocol Header + Payload每一层的包都可以用这个公式来表示。Payload 指传入这一层的数据内容,比如:TCP Segment = TCP header + HTTP data有了这个认知之后,对于每一层协议的学习,最后就落实到每一层 header 的学习上了,学习 TCP 就是研究 TCP header 的构成,header 里的每一个 bit 位都有特别的用处,来实现协议层对于网络传输的控制。这也是为什么我经常会说,所谓的网络协议学习就是 header 学习,这也是本文标题的含义所在。深入 Header【TCP/IP 详解】大致有 1000 页,通读的过程会很漫长且枯燥,知道每一层协议都是关于 header 的设计之后,大家其实可以先跳跃式的阅读,先学习感兴趣的部分,有了收获知识的正向反馈之后,在回过头来填补更多的知识细节。比如大家一般都对 TCP 协议比较感兴趣(确实也是最有意思的部分,后面的文章也重点分析),那么可以先跳到第十二章:Chapter 12
TCP: The Transmission Control Protocol(Preliminaries)或者第十三章,真正明白所谓的三次握手:Chapter 13
TCP Connection Management由于每一层的设计都是独立的,所以先学习传输层并不会有什么障碍,这也是分层架构的意义所在,各层各司其职,互不依赖具体的实现。我们的学习行为,大致上可以分为两类,理解(理解思想)和记忆(强行记忆)。对于 header 的学习,除了理解 header 每个 bit 的意义之外,还需要一些记忆行为,对于一些关键信息的强制性记忆,有助于我们形成更深刻的认知。我们以 TCP 的 header 为例:上图是一个 TCP header,以下是一些需要「死记硬背」的信息点:一个 TCP Header 一般有 20 个字节,如果启用了 options,header的长度可以达到 60 个字节。上图中每一行是 4 个 bytes,32 个 bits。我先带大家学习下前 5 行,每一行是 4 Bytes,五行刚好是 20 个 bytes。计算机世界中,通常会以 bit,byte,word(4 个 byte)等不同粒度来描述信息,header 的学习一般是以 4 个字节为一个单位来展示的。第一行,由 Source port 和 Destination port 构成,二者各占 2 个字节,刚好一起占据第一行的 4 个字节。这两个字段分别表示 TCP 连接中的,发送方端口号和接收方的端口号,既然一个 port 只占 2 个 bytes,那么端口值的范围自然就是 0~65535 啦。第二行,Sequence number,表示发送方的序列号。这个序列号表示的是什么呢?一个 TCP 流是有无数个 0 和 1 构成,这些 0 和 1 以 8 个 bit 为单位,可以分割成一个个的 byte,TCP 是可靠传输协议,每一个 byte 都是有标号的,因为我们需要追踪每个 byte 是否被成功传输了,每个 byte 的标号就是我们这里的 sequence number。假设我们建立 TCP 连接的时候,发一个 sync 包,我们就以 0 标记 sync 包的第一个字节,那么 sync 包中的 Sequence 值就是 0。实际应用中,处于安全考虑,TCP 流的第一个 Sequence number 一般不会是 0,而是一个随机数。Sequence number 占据 4 个字节,也就是 2 的 32 次方,这个数字并不算大,每个包都会用掉一些,如果达到最大值之后,就取余从 0 重新开始。第三行,Acknowledge number,表示接收方 ack 的序列号。接收方收到发送方一个的 TCP 包之后,取出其中的 sequence number,在下一个接收方自己要发送的包中,设置 ack 比特位为 1,同时设置 acknowledge number 为 sequence number + 1。所以接收方的 acknowledge number 表示的是,接收方期待接收的下一个包起始字节的标号,大家可以仔细理解下这一句话。所以 acknowledge number 和 sequence number 是配对使用的。第四行,这一行尤其重要,出于篇幅的考虑,其中细节会在后续的文章中讲解。这里简单提下从 CWR 到 FIN 的 8 个 bit,这 8 个 bit 里每一位都是一个标记位,用来标记当前 TCP 包的特殊含义。比如我们所说的三次握手,第一个 sync 包,就是将 SYN 位置为 1。第二个 syn + ack 包就是将 header 的 ACK 和 SYN 位都置为 1。第三个 ack 包即将 ACK 位置 1。剩余的几个 bit 位暂时不展开讲了,大家可以自己看书先学习下。第五行,这一行只有两个字段,即 Checksum 和 Urgent pointer。checksum 是个通用的计算机概念,做完整性校验之用,在很多协议(IP,UDP,ICMP)中都有应用,这个值有包的发送方去计算,之后由包的接收方取出来校验。Urgent pointer 为两个字节的偏移量,加上当前包的 sequence number,用来标记某一个范围内的 bytes 为特殊用途数据。怎么样、其实没有多少信息量对不对?这么跟着理解一遍 header 中的每一个 bytes 之后,是不是加深了对 TCP 的理解呢?同理,学习完 TCP 的 header 之后,大家可以再去把 IP 的 header,frame 的 header 都搜索出来,对照关键字段去理解学习,最后再配合【TCP/IP 详解】一书阅读效果更好。Tcpdump 实战上面是理论部分,可能有些枯燥,大家可以在理解之后,使用 tcpdump 抓包实战下,进一步加深理解。我们就来抓包,基于上面 tcp header 的学习,抓下三次握手的包 :)我们可以用如下命令来抓三次握手的包:sudo tcpdump -i en0 “tcp[tcpflags] & (tcp-syn|tcp-ack) != 0”输出结果为:18:18:45.687476 IP 192.168.3.7.65284 & 59.37.116.101.https: Flags [S], seq , win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val
ecr 0,sackOK,eol], length 018:18:45.719744 IP 59.37.116.101.https & 192.168.3.7.65284: Flags [S.], seq , ack , win 14280, options [mss 1412,sackOK,TS val
ecr ,nop,wscale 8], length 018:18:45.719986 IP 192.168.3.7.65284 & 59.37.116.101.https: Flags [.], ack 1, win 4112, options [nop,nop,TS val
ecr ], length 0你能根据上面 tcp header 的学习,理解上面 tcpdump 命令的含义吗?tcpflags 指的是 header 中的哪些位呢?对于 tcpdump 的使用还不太了解的同学,可以翻阅我之前的一篇介绍文章。当然 TCP 里还包含着很多有趣的知识点,大家可以先行阅读,后面我会逐步讲解,比如 TCP 的 ARQ 机制,Flow Control 等。总结这次新启的 TCP/IP 系列文章对我来说,会是一次耗时费力,旷日持久的旅途。希望能够慢火细熬,徐徐烹制,将这道倾注心力、营养丰盛的网络协议佳肴制作完成,与君共享。欢迎关注公众号:MrPeakTech","updated":"T11:56:32.000Z","canComment":false,"commentPermission":"anyone","commentCount":4,"likeCount":35,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T19:56:32+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":4,"likesCount":35},"":{"title":"今年第一个独立 App,TKeyboard,也是第一个开源项目","author":"mrpeak","content":"在咖啡馆耗费了数个周末之后,终于将这个灵光一闪的点子变成了一个实实在在可以运行的 app。同时意味着年初制定的第一个小目标达成,完成了 2017年的第一个独立 app。在app上架的同时,我决定将代码全部开源,除了兑现去年年底的承诺之外,我相信代码本身的价值,要高于 App 的功能,开源能带来更多知识的碰撞和增长。去年开源给我带来了不少乐趣,希望今年能有更好的成绩。整个项目涉及到一些比较实用的技术点,或能惠之于人。应用场景这款应用名为:TKeyboard。有一个 Mac 端和一个 iOS 端 App。简单来说,可以通过蓝牙,使用 Mac 的键盘输入内容到 iPhone 设备中。主要是为了解决 iPhone 设备输入不方便的问题,有 Mac 在身边的时候会比较方便,一时脑洞的小应用。涉及知识点这两个 App 解决的用户场景比较完整,代码方面涉及到一些较为实用的技术点:Mac 端开发,从 iOS 端切入 Mac 端开发其实难度比大部分人预想的都要小,主要是 UI Framework 需要做些学习,用 xib 配合 autolayout 其实很方便,就是做动画会稍微麻烦一些。iOS 端的话,主要是各种 Extension 的开发学习,现阶段实现的是 Keyboard Extension,后期这个项目计划实现更多的 Extension 功能,最终的目标是成为一个 Mac 端和 iOS 端的多功能同步应用。Extension 开发的重要性,我曾经专门写文介绍过,不再赘述。另外是蓝牙通讯这一块,iOS 端和 Mac 端共享一套代码。蓝牙这块网络上技术文章比较少,完整的开源项目几乎找不到。我在结合官方 demo 和自己踩坑的基础之上,基本实现了一套完整的蓝牙通讯功能。最后还有一个简单的网络协议设计,用于 iOS 和 Mac 端做通讯之用,理论上使用 protobuf 更合理,但这是个人项目,处于写代码的乐趣,就自己动手 DIY 了一个,感兴趣的同学也可以自己设计。全部的代码大约耗时数个周末。其实项目去年底就已经启动了,中间因为各种琐碎事情耽搁了,2017 开年战胜拖延症,终于完成了第一版本代码,算是给去年底做热血规划的自己一个交代。开源计划TKeyboard 包含 Mac 端 和 iOS 端两个项目,涵盖一些实用知识,决定开源也算是对 iOS 技术社区做一些微薄的贡献。其中代码可以随意免费使用,但绝不容忍换个皮肤重新上架 App Store 的低素质行为。后续打算TKeyboard 的最终目标是一个 Mac 与 iOS 的同步应用,这是一个庞大的工程量,不知道最后会完成为什么样的形态,且做且珍惜。欢迎大家提意见修改。iPhone 版目前售价 $3,Mac 版免费。因为代码开源,实际和完全免费没差别。如果觉得代码或者 App 本身对你有帮助,可以考虑去 App Store 下载,赞助 Peak 君一杯咖啡。iOS 版 TKeyboard 下载地址:Mac 版 TKeyboard 下载地址:Github 地址:E)","updated":"T00:21:22.000Z","canComment":false,"commentPermission":"anyone","commentCount":8,"likeCount":53,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T08:21:22+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-e7f04eab476eea78c61c83e_r.png","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":8,"likesCount":53},"":{"title":"TCP/IP 系列之重

我要回帖

更多关于 c indexof 返回值 的文章

 

随机推荐