From 1aa384ad8fc439c039e313a878b4953da076541f Mon Sep 17 00:00:00 2001 From: panjunqing Date: Wed, 20 Dec 2023 12:47:36 +0800 Subject: [PATCH] Remove some outdated articles --- .../2015-01-07-ios-performance-tips.markdown | 104 ----------- _posts/2015-01-08-weixin-questions.markdown | 43 ----- .../2015-01-09-read-svpulltorefresh.markdown | 63 ------- ...015-01-14-read-tttattributedlabel.markdown | 86 --------- _posts/2015-02-01-unique-random.markdown | 37 ---- _posts/2015-02-03-read-chats.markdown | 60 ------- ...03-01-new-feature-in-swift1-dot-2.markdown | 108 ----------- ...08-introduction-of-lcevaframewrok.markdown | 57 ------ ...2015-03-22-abount-string-in-swift.markdown | 54 ------ _posts/2015-04-06-gvuserdefault.markdown | 53 ------ ...2015-04-10-optimization-of-boohee.markdown | 167 ------------------ .../2015-04-25-look-into-catranstion.markdown | 38 ---- ...015-04-30-create-your-pod-library.markdown | 51 ------ ...2015-08-06-review-of-thirdparties.markdown | 128 -------------- _posts/2016-01-18-api-blue-print.markdown | 59 ------- _posts/2016-01-25-mantle-design.markdown | 132 -------------- _posts/2016-02-01-article-review.markdown | 2 +- _posts/2019-08-08-game-review.md | 5 +- _posts/2019-08-21-energy-efficiency.md | 46 ----- _posts/2021-04-09-clover-upgrade-to-bigsur.md | 40 ----- _posts/2022-02-02-woop-en.md | 2 +- _posts/2022-02-02-woop.md | 2 +- 22 files changed, 4 insertions(+), 1333 deletions(-) delete mode 100644 _posts/2015-01-07-ios-performance-tips.markdown delete mode 100644 _posts/2015-01-08-weixin-questions.markdown delete mode 100644 _posts/2015-01-09-read-svpulltorefresh.markdown delete mode 100644 _posts/2015-01-14-read-tttattributedlabel.markdown delete mode 100644 _posts/2015-02-01-unique-random.markdown delete mode 100644 _posts/2015-02-03-read-chats.markdown delete mode 100644 _posts/2015-03-01-new-feature-in-swift1-dot-2.markdown delete mode 100644 _posts/2015-03-08-introduction-of-lcevaframewrok.markdown delete mode 100644 _posts/2015-03-22-abount-string-in-swift.markdown delete mode 100644 _posts/2015-04-06-gvuserdefault.markdown delete mode 100644 _posts/2015-04-10-optimization-of-boohee.markdown delete mode 100644 _posts/2015-04-25-look-into-catranstion.markdown delete mode 100644 _posts/2015-04-30-create-your-pod-library.markdown delete mode 100644 _posts/2015-08-06-review-of-thirdparties.markdown delete mode 100644 _posts/2016-01-18-api-blue-print.markdown delete mode 100644 _posts/2016-01-25-mantle-design.markdown delete mode 100644 _posts/2019-08-21-energy-efficiency.md delete mode 100644 _posts/2021-04-09-clover-upgrade-to-bigsur.md diff --git a/_posts/2015-01-07-ios-performance-tips.markdown b/_posts/2015-01-07-ios-performance-tips.markdown deleted file mode 100644 index 46ca4aa8..00000000 --- a/_posts/2015-01-07-ios-performance-tips.markdown +++ /dev/null @@ -1,104 +0,0 @@ ---- -title: iOS App性能提升的技巧 -date: 2015-01-07 13:18:43 +0800 -layout: post -current: post -cover: assets/images/welcome.jpg -navigation: True -tags: [Optimization] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - -从[25 iOS App Performance Tips & Tricks](http://www.raywenderlich.com/31166/25-ios-app-performance-tips-tricks)翻译了部分提高app性能的技巧 - -## 中阶性能提升建议 - -### 9)复用和延迟加载 -更多的视图意味着更多的绘制,这些最终意味着更多CPU和内存的开销。在通过UIScrollView展示很多视图时开销尤为明显。 - -因此,效仿UITableView和UICollectionView的思路,并不在一开始就创建所有视图,在需要的展示时候创建,并不显示的视图加入重用队里里。 - -通过这个方法我们显示新的视图时,只需要设置复用视图的属性,而避免了新建视图会引起的内存分配和初始化开销。 - -延迟加载的方法也可以用在其它情景。比如你需要通过点击按钮来展示一个视图,至少有两种实现形式: - -1. 在页面加载时创建并隐藏。需要时再显示出来。 -2. 直到点击按钮时才创建并显示。 - -这两种方法各有优缺点。第一种会长期占用内存空间,但是显示速度快。第二种刚好相反,不占空间但展示慢。具体采用哪种方式可以根据应用场景权衡。 - -### 10)缓存、缓存、缓存 -一项普遍的的开发经验就是"缓存那些有必要缓存的东西",即缓存那些不太会改变但是经常访问的数据。 -具体可以缓存什么数据呢?比如服务器返回的数据,图片,甚至是计算后的值如UITableView的高度。 -NSURLConnection已经根据HTTP请求头将资源存储到本地或内存中,你甚至可以人为设置NSURLRequest,让它只访问缓存的数据。 - -```objc - + (NSMutableURLRequest *)imageRequestWithURL:(NSURL *)url { - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; - - request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; // 这一选项会总是返回缓存的图片 - request.HTTPShouldHandleCookies = NO; - request.HTTPShouldUsePipelining = YES; - [request addValue:@"image/*" forHTTPHeaderField:@"Accept"]; - - return request; - } -``` - -注意,你可以用NSURLConnection来进行URL请求,同样AFNetworking也可以。而且你不需要改变什么代码,因为它已经做得足够好。 - -如果你想了解更多关于HTTP缓存的知识,可以涉猎NSURLCache,NSURLConnection等相关知识,确保浏览了NSHipster网站上的[这篇文章](http://nshipster.com/nsurlcache/) -如果你需要缓存不包含在HTTp请求中的数据,可以涉猎NSCache。NSCache和NSDictionary表现的很像,但是当系统需要更多空间资源时,它会被释放。Matt Thompson在NSHipster写了一篇[很赞的文章](http://nshipster.com/nscache/)可以阅读一下。 -想要了解更多关于HTTP缓存的知识,推荐阅读Google的[《 best-practices document on HTTP caching》](https://developers.google.com/speed/docs/insights/LeverageBrowserCaching)。 - -### 11)考虑图形绘制 -在iOS上有好几种方式可以实现漂亮的按钮。可以用全尺寸图片或可伸缩图片显示,再复杂点用CALayer,CoreGraphics甚至OpenGL来绘制。 - -这些效果的实现难度不同和当然性能也不同。有篇很棒的文章讲述[iOS图形性能](http://robots.thoughtbot.com/designing-for-ios-graphics-performance),很值得阅读。同时,苹果UIKit项目组的成员Andy Matuschak也针对这篇文章给出了深度的分析。 - -简而言之,使用图片是最快的,因为iOS省去了绘制图形的过程,直接将图片渲染到屏幕上。问题是你需要把所有的图片都放入bundle中,这增加了app的大小。所以使用可伸缩尺寸的图片来绘制按钮更好。iOS会帮你绘制那些重复的纹理。你也不用针对不同尺寸的元素(如按钮)生成不同的图片。 - -毕竟通过使用图片你可能会丧失图片微调的能力,当你需要对不同的场景对视图进行微调时,你依旧需要处理大量的图片,不能通过代码直接调整。这会是一个繁复的过程。 - -所以,在绘制性能和app大小上,你需要根据自己的需要进行折中。 - - - -## 高阶性能提升建议 - -### 22)提升程序启动速度 -启动速度提升主要的核心在于避免阻塞主线程,异步处理繁重的任务,如网络请求、数据库操作或者解析数据。同时避免加载臃肿的XIB文件,XIB文件在主线程中加载,而storyboard不存在这个问题,有需要可以尽可能考虑storyboard。 - ->注意,手机在通过Xcode调试时,watchdog并不会运行,所以要测试真正的启动速度时应断开连接。 - -### 23)使用Autorelease Pool -NSAutoreleasePool是用于释放对象的程序块。通常情况下由UIKit来调用。但是在有些场景中我们需要认为创建。 - -比如当你需要创建大量的临时对象时,内存使用量会因此飙升直到未来某时刻统一被UIKit释放。这就意味这些临时对象存在的时间比需要的时间长。所以我们需要手动及时地释放这些对象。 - -```objc - NSArray *urls = <# An array of file URLs #>; - for (NSURL *url in urls) { - @autoreleasepool { - NSError *error; - NSString *fileContents = [NSString stringWithContentsOfURL:url - encoding:NSUTF8StringEncoding error:&error]; - /* 处理数据或其它创建操作 */ - } - } -``` - -### 24)选择性图片缓存 -有两种常见的方式来加载app bundle中的图片。`imageNamed`和`imageWithContentsOfFile`。前者会先从系统缓存中寻找图片对象,如果没有则创建新的对象并缓存,适用于频繁使用的图片。后者没有缓存机制,适合不常使用的资源消耗较大的图片。 - -### 25)避免使用Date Formatter -在大量需要用到NSDateFormatter的情景下,可使用类似单例的形式初始化,重复使用一个NSDateFormatter对象。如果想要进一步提高速度,则可以采用[C语言的方式](http://sam.roon.io/how-to-drastically-improve-your-app-with-an-afternoon-and-instruments)。还有一种更好的方式就是使用Unix timestamps。一个标示时间的浮点数,纪录了自1970年1月一日到现在的时间间隔。通过NSDate很容易的实现时间转换。 - -```objc - - (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp { - return [NSDate dateWithTimeIntervalSince1970:timestamp]; - } -``` \ No newline at end of file diff --git a/_posts/2015-01-08-weixin-questions.markdown b/_posts/2015-01-08-weixin-questions.markdown deleted file mode 100644 index e8ca7a3a..00000000 --- a/_posts/2015-01-08-weixin-questions.markdown +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: 微信三问三答 -date: 2015-01-08 19:48:38 +0800 -layout: post -current: post -cover: assets/images/welcome.jpg -navigation: True -tags: [Source code] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - -这是一道知乎上引起广泛讨论的问题,具体查看[这里](http://www.zhihu.com/question/27345775)。 - -### 朋友圈为什么不调整到首屏,为什么路径这么深? -从定位上来说。微信的核心是即时通讯,朋友圈是其衍生品。层次低一级。 - -从内容上来说。一个普通人的朋友数量有限,且朋友圈的鸡汤、代购一经过滤,有价值的内容留存很少。 -和微博、知乎这种内容主导的平台完全不同,占据第一入口会极其浪费。 - -对腾讯而言,能为它带来利益具有战略意义的是购物、游戏、钱包。这些没有放到第一入口已算克制了。朋友圈还要啥自行车。 - -从使用习惯上来说,放得深并没有影响使用热情。所以也就没有必要再放出来了吧。 - -### 朋友圈的基本数据结构设计是怎么样的?既能做到完美阅读权限设置,又能兼顾性能? - -自己对数据库认识有限,所以只能说些粗鄙的想法。 - -每个用户都有自己的朋友圈队列,这里存放的是有权限阅读的内容id。 -用户在发一条朋友圈的时候,服务器会筛选出拥有阅读权限的用户(或者客户端可以进行一部分筛选),然后把内容id添加入每一个有权阅读用户的朋友圈队列。这里会存在大量写入,不知道会有何瓶颈,好在每个用户相对独立且实时性要求不是那么高。 -用户在刷新朋友圈时只需要从自己的队列中拿到id并索引出相应的内容即可。 - -另外评论中的查看权限,我觉得可以在客户端本地屏蔽处理,只是会耗费一些额外流量。 - -### 如果你是微信的产品经理,你会最优先做哪一个功能的改进?简述理由。 - -这一条我很赞同的一个观点,就是我们不能拿自己的喜好去忖度一个产品。所以,还是要让用户、让数据说话。 - -可以根据访问量调整功能入口的层级。可以深入拓展和开发使用频率高的功能。还有一些用户看不到的,代码层面上的质量或性能问题,产品经理也应当有所重视。 - -就我个人而言,我比较倾向于完善收藏功能。能够便于自己整理收藏的图文,最好能在电脑端操作,并导出到相应的平台。 \ No newline at end of file diff --git a/_posts/2015-01-09-read-svpulltorefresh.markdown b/_posts/2015-01-09-read-svpulltorefresh.markdown deleted file mode 100644 index e9f44fb1..00000000 --- a/_posts/2015-01-09-read-svpulltorefresh.markdown +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: iOS源码阅读之SVPullToRefresh -date: 2015-01-09 20:30:49 +0800 -layout: post -current: post -cover: assets/images/welcome.jpg -navigation: True -tags: [Source code] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - -`SVPullToRefresh`是基于`UIScrollView`的扩展,动态添加了下拉刷新的视图。下面来简单看一下添加视图的实现过程。 - -首先在类别中声明一个`SVPullToRefreshView`的property。 - -```objc -@interface UIScrollView (SVPullToRefresh) -... -@property (nonatomic, strong, readonly) SVPullToRefreshView *pullToRefreshView; -... -@end -``` - -然而Category并不支持为类添加属性和成员变量,所以需要通过关联对象的方法来动态添加。我们发现在.m文件中引入了`#import `头文件,并用@dynamic来声明pullToRefreshView,@dynamic的作用是告诉编译器手动实现setter和getter方法。关于objective-C的其它保留字可以参考[唐巧的博文](http://blog.devtang.com/blog/2013/04/29/the-missing-objc-keywords/)。 - -```objc -#import - -static char UIScrollViewPullToRefreshView; - -@implementation UIScrollView (SVPullToRefresh) -... - -@dynamic pullToRefreshView; - -- (void)setPullToRefreshView:(SVPullToRefreshView *)pullToRefreshView { -[self willChangeValueForKey:@"SVPullToRefreshView"]; -objc_setAssociatedObject(self, &UIScrollViewPullToRefreshView, -pullToRefreshView, -OBJC_ASSOCIATION_ASSIGN); -[self didChangeValueForKey:@"SVPullToRefreshView"]; -} - -- (SVPullToRefreshView *)pullToRefreshView { -return objc_getAssociatedObject(self, &UIScrollViewPullToRefreshView); -} - -... -@end -``` - -核心部分的setter方法中,使用了`objc_setAssociatedObject `,其作用就是为一个对象动态添加没有声明的变量。以下是[SO](http://stackoverflow.com/questions/5909412/what-is-objc-setassociatedobject-and-in-what-cases-should-it-be-used)上的详细解释。 - ->objc_setAssociatedObject adds a key value store to each Objective-C object. It lets you store additional state for the object, not reflected in its instance variables. - ->It's really convenient when you want to store things belonging to an object outside of the main implementation. One of the main use cases is in categories where you cannot add instance variables. Here you use objc_setAssociatedObject to attach your additional variables to the self object. - ->When using the right association policy your objects will be released when the main object is deallocated. - -这样一个自己实现的下拉刷新视图就可以动态添加到`UIScrollView`上。之后`SVPullToRefreshView `以弱引用保有其所在的scrollView变量,通过KVO侦听`UIScrollView`的`contentOffset`的变化,实现了自己的一套状态逻辑,最终达到我们所见的效果。具体实现细节还请参考[源码](https://github.com/samvermette/SVPullToRefresh)。 \ No newline at end of file diff --git a/_posts/2015-01-14-read-tttattributedlabel.markdown b/_posts/2015-01-14-read-tttattributedlabel.markdown deleted file mode 100644 index 8c3d9c21..00000000 --- a/_posts/2015-01-14-read-tttattributedlabel.markdown +++ /dev/null @@ -1,86 +0,0 @@ ---- -title: iOS源码阅读之TTTAttributedLabel -date: 2015-01-14 21:49:27 +0800 -layout: post -current: post -cover: assets/images/welcome.jpg -navigation: True -tags: [Source code] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - -[TTTAttributedLabel](https://github.com/TTTAttributedLabel/TTTAttributedLabel)是一个功能更为丰富的UILabel,支持`AttributedString`,识别特殊文本(如地址,电话,邮箱以及超链接等),并可以自定义这些文本的点击响应事件。为了了解其链接点击的实现方式,我决定对源码一窥究竟。 - -先看一下官方给出的给文字添加链接的参考代码。步骤很简单,在自己的Delegate实现中处理URL。 - -```objc -label.enabledTextCheckingTypes = NSTextCheckingTypeLink; -label.delegate = self; - -label.text = @"Fork me on GitHub! (http://github.com/mattt/TTTAttributedLabel/)"; - -NSRange range = [label.text rangeOfString:@"me"]; -[label addLinkToURL:[NSURL URLWithString:@"http://github.com/mattt/"] withRange:range]; -``` - - -`TTTAttributedLabel`的最终实现是基于`Core Text`,而`Core Text`中的字符串都是用`NSAttributedString`来表示。所以在调用`addLinkToURL:withRange`时自然需要在`NSAttributedString`字符串的相应区间添加属性,以区分URL链接,比如设成蓝色。源码可以看到作者是构造`NSTextCheckingResult`来存储range和URL的,这个是为了和自动识别出来的特殊文本统一格式,一并存储到名为`links`这个数组中,作为点击识别时起到重要数据源。 - -```objc -- (void)addLinksWithTextCheckingResults:(NSArray *)results attributes:(NSDictionary *)attributes { - NSMutableArray *mutableLinks = [NSMutableArray arrayWithArray:self.links]; - if (attributes) { - NSMutableAttributedString *mutableAttributedString = [self.attributedText mutableCopy]; - for (NSTextCheckingResult *result in results) { - [mutableAttributedString addAttributes:attributes range:result.range]; - } - - self.attributedText = mutableAttributedString; - [self setNeedsDisplay]; - } - [mutableLinks addObjectsFromArray:results]; - self.links = [NSArray arrayWithArray:mutableLinks]; -} -``` - -`TTTAttributedLabel`使用`Core Text`完全重写了UILabel的`drawTextInRect`方法。实现`touchesBegan`,`touchesMoved`,`touchesEnded`来处理点击响应。 -在触发`touchesBegan`事件后,获取点击字符位置的关键函数是`- (CFIndex)characterIndexAtPoint:(CGPoint)p`,其核心函数依旧是`Core Text`中的方法。 - -```objc -//首先创建一个framesetter对象,CTFramesetter是用来管理文体字体和绘制区域的一个重要的类。 -CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)self.renderedAttributedText); - -//创建一个绘制区域,这个方法应该已经将每个字的位置排好 -CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, (CFIndex)[self.attributedText length]), path, NULL); -//之后获取这个区域的每一行 -CFArrayRef lines = CTFrameGetLines(frame); -//遍历每一行,根据触摸点的位置来判断点击文字的索引值。这个索引值也就是字符在字符串中的位置了。 -CGPoint relativePoint = CGPointMake(p.x - lineOrigin.x, p.y - lineOrigin.y); -idx = CTLineGetStringIndexForPosition(line, relativePoint); -``` - -获取了字符位置之后,遍历links数组,找到符合区域的链接。 - -```objc -- (NSTextCheckingResult *)linkAtCharacterIndex:(CFIndex)idx { - NSEnumerator *enumerator = [self.links reverseObjectEnumerator]; - NSTextCheckingResult *result = nil; - while ((result = [enumerator nextObject])) { - if (NSLocationInRange((NSUInteger)idx, result.range)) { - return result; - } - } - return nil; -} -``` - -最后通过Delegate发送相应的链接给处理函数,就实现了最终的调用。这篇文章省略了很多实现的细节,如想了解还需细看源码以及了解`Core Text`。 - - - -### 参考 - -关于`Core Text`的优秀国外优秀的[教程](http://www.raywenderlich.com/4147/core-text-tutorial-for-ios-making-a-magazine-app)。 \ No newline at end of file diff --git a/_posts/2015-02-01-unique-random.markdown b/_posts/2015-02-01-unique-random.markdown deleted file mode 100644 index f0caf368..00000000 --- a/_posts/2015-02-01-unique-random.markdown +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: Swift递归实现的不重复随机序列生成函数 -date: 2015-02-01 15:11:15 +0800 -layout: post -current: post -cover: assets/images/welcome.jpg -navigation: True -tags: [Swift recursion] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - -题目来自于《编程珠玑》第一章节的一道题:生成位于0~N-1之间的k个不同的随机序列的随机整数。算法保证在没有生成过数的区间进行随机生成操作,能够满足不重复需求。但是递归原因容易造成随机数的集中。 - -```swift -func createGenerator(count:Int)->(Int,Int)->[Int]{ - //http://stackoverflow.com/questions/24270693/nested-recursive-function-in-swift - var generator:(Int,Int)->[Int] = {_,_ in return []} // give it a no-op definition - var total = count - generator = {min,max in - if (total <= 0 || min>max) { - return [] - }else{ - total--; - var random = Int(arc4random_uniform(UInt32(max-min))) - var mid = min + random - return [mid]+generator(min, mid-1)+generator(mid+1, max) - } - } - - return generator -} - -createGenerator(10)(0,100) -``` \ No newline at end of file diff --git a/_posts/2015-02-03-read-chats.markdown b/_posts/2015-02-03-read-chats.markdown deleted file mode 100644 index e4ae5cd6..00000000 --- a/_posts/2015-02-03-read-chats.markdown +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: "阅读Chats小记" -date: 2015-02-03 14:38:39 +0800 -layout: post -current: post -cover: assets/images/welcome.jpg -navigation: True -tags: [Swift] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - -[Chats](https://github.com/acani/Chats)是一个用Swift语言写的聊天app的一个Demo,初学者能够从中了解如何构建一个实时通信app的雏形。Demo功能本相对简单,没有实现数据储存和网络连接。我之前有用Objective-C写过聊天应用的经验,打算用Swfit重写一遍。希望通过阅读这部代码能比较不同人的实现方式,找到可以借鉴的实现。 - -#### 生成气泡 - -```swift -let bubbleImage = bubbleImageMake() - -func bubbleImageMake() -> (incoming: UIImage, incomingHighlighed: UIImage, outgoing: UIImage, outgoingHighlighed: UIImage) { - let maskOutgoing = UIImage(named: "MessageBubble")! - let maskIncoming = UIImage(CGImage: maskOutgoing.CGImage, scale: 2, orientation: .LeftMirrored)! - - let capInsetsIncoming = UIEdgeInsets(top: 17, left: 26.5, bottom: 17.5, right: 21) - let capInsetsOutgoing = UIEdgeInsets(top: 17, left: 21, bottom: 17.5, right: 26.5) - - let incoming = coloredImage(maskIncoming, 229/255.0, 229/255.0, 234/255.0, 1).resizableImageWithCapInsets(capInsetsIncoming) - let incomingHighlighted = coloredImage(maskIncoming, 206/255.0, 206/255.0, 210/255.0, 1).resizableImageWithCapInsets(capInsetsIncoming) - let outgoing = coloredImage(maskOutgoing, 43/255.0, 119/255.0, 250/255.0, 1).resizableImageWithCapInsets(capInsetsOutgoing) - let outgoingHighlighted = coloredImage(maskOutgoing, 32/255.0, 96/255.0, 200/255.0, 1).resizableImageWithCapInsets(capInsetsOutgoing) - - return (incoming, incomingHighlighted, outgoing, outgoingHighlighted) -} - -func coloredImage(image: UIImage, red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) -> UIImage! { - let rect = CGRect(origin: CGPointZero, size: image.size) - UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale) - let context = UIGraphicsGetCurrentContext() - image.drawInRect(rect) - CGContextSetRGBFillColor(context, red, green, blue, alpha) - CGContextSetBlendMode(context, kCGBlendModeSourceAtop) - CGContextFillRect(context, rect) - let result = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext() - return result -} -``` - -#### 其它 - -```swift -//用类名来作为复用Cell的标识 -tableView.registerClass(MessageSentDateCell.self, forCellReuseIdentifier: NSStringFromClass(MessageSentDateCell)) - -//最简单的实现键盘消失的方法,有.Interactive和.OnDrag两种不同的形式。 -//如需点击时消失键盘,还需额外实现touch事件。 -tableView.keyboardDismissMode = .Interactive -``` \ No newline at end of file diff --git a/_posts/2015-03-01-new-feature-in-swift1-dot-2.markdown b/_posts/2015-03-01-new-feature-in-swift1-dot-2.markdown deleted file mode 100644 index 42c941ce..00000000 --- a/_posts/2015-03-01-new-feature-in-swift1-dot-2.markdown +++ /dev/null @@ -1,108 +0,0 @@ ---- -title: "Swift1.2中的新特性小记" -date: 2015-03-01 22:26:33 +0800 -layout: post -current: post -cover: assets/images/welcome.jpg -navigation: True -tags: [Swift] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - -## 引入集合概念 -集合是一个没有重复的无序元素集,以下是一些基本操作 - -```swift -var set1 = Set(["a", "b", "c", "d"]) //集合创建 -set1.insert("e") //集合插入 -set1.remove("a") //删除操作 -var set2 = Set(["b", "c", "f"]) -set1.subtract(set2) //集合差集 -//["e", "d"] -set1.union(set2) //集合并集 -//["b","e","f","d","c"] -``` - -## if let语句的优化 -之前由于optional特性的引入,在做判断时需要层层unwrap,使得代码嵌套严重,影响整洁。 - -```swift -if let name = validateName(nameTextField.text) { - if !name.isEmpty { - if let age = validateAge(ageTextField.text) { - if age > 13 { - - // 做相应操作 - } - } - } -} -``` - -在优化if let之后,能够将判断集中处理,代码瞬间精简许多 - -```swift -if let name = validateName(nameTextField.text), - age = validateAge(ageTextField.text) - where age > 13 && !name.isEmpty { - - // 做相应操作 -} -``` - -## as!强制类型转换 -强制类型转换体现了苹果对类型安全的重视,相比as?转换失败返回nil,as!转换失败会引发运行时错误。这就需要程序员在转换时引起注意。 - -```swift -class A{} -class B:A{} - -var a:A? = A() -var b:B? = a as? B //此时b为nil - -var c:B? = a as! B //运行时错误,程序崩溃 -``` - -另外在从objective-C到Swift基本类型转换时需要显示转换,反之不用 - -```swift -var nsstr1:NSString = NSString(string: "hello") -var str1:String = nsstr1 as String - -var nsarray:NSArray = NSArray() -var array:[AnyObject] = nsarray as [AnyObject] - -var nsdict:NSDictionary = NSDictionary() -var dict:Dictionary = nsdict as Dictionary -``` - -Xcode一般会给出类型转换的提示,但是由于beta版的不稳定,导致SourceKit崩溃而无法提示,导致编译过程中出现segment fault问题。以下是出错代码段。 - -```swift -class func changePassword(oldpassword: String, newpassword: String, doneAction:()->()) { - - API_USERS_CHANGE_PASSWORD.cancel() - var api: API_USERS_CHANGE_PASSWORD = API_USERS_CHANGE_PASSWORD() - - //以参数形式构造的字典在Xcode6.3 beta2中会导致SourceKit崩溃 - var req = ["old_password": oldpassword, "new_password": newpassword] as NSDictionary - //api.req是objective-C经bridge转换成代码,原objective-C中api.req接收NSDictionary的赋值,但是经bridge转换后,需要Dictionary值。 - api.req = req //api.req(Dictionary) = req(NSDictionary) 在赋值时没有编辑器错误警告,导致执行编译,最后出现编译不通过情况 - - //正确的写法如下,是不需要在通过NSDictionary中转,直接赋值即可 - api.req = ["old_password": oldpassword, "new_password": newpassword] - api.whenSucceed = { - [unowned api] in - doneAction() - } - api.whenFailed = { - println("修改密码失败") - } - api.send() -} -``` - -Xcode是自带升级Swfit代码的功能,但是并不完善,甚至有荒谬的转换。所以还需要人工去仔细排查。有时候单纯通过编译器检查并不能确保运行时正确。 \ No newline at end of file diff --git a/_posts/2015-03-08-introduction-of-lcevaframewrok.markdown b/_posts/2015-03-08-introduction-of-lcevaframewrok.markdown deleted file mode 100644 index fcf37a0d..00000000 --- a/_posts/2015-03-08-introduction-of-lcevaframewrok.markdown +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: "评测框架简述" -date: 2015-03-08 09:45:08 +0800 -layout: post -current: post -cover: assets/images/welcome.jpg -navigation: True -tags: [Module] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - - -### 动机 - -最近的开发需求是写一个评测的功能,根据用户回答一系列的问题,最终生成评测结果。题型包括选择题和输入题,但拥有各样的展示方式。一个答题页会放一道或两道题,根据选择的不同会跳转不同到题目。原先的实现方式是每一种答题页独立实现,相似的答题页和组件实现复用,也能基本满足需求。但是我想做一个拓展性更强的足以面对更多变化的框架,所以尝试性进行重构。 - -### 组件化 -组件化的概念就是将一个页面的视图进行拆分,通过布局文件使其能自由组合在一起。如同HTML通过标签铺陈一样,自上而下构建所解析的视图,并且拥有相应的属性。 - -```json - [ //JSON格式的Layout文件 - {"view":"TitleLabel","text": "您的性别和生日?"}, - {"view":"ChoiceView","questionid":"0"}, - {"view":"PickView","questionid":"1"}, - {"view":"BiChoiceView"}, - ] -``` - -HTML布局的高度是无限制的,可以上下滚动。而我们需要在限定的高度上进行布局,所以为了达到适配效果构建了一个BaseView。它有一个flexibleHeight的属性标明这个View是否固定高度,不固定视图的高度=屏幕高度-固定视图的高度和,最终会填满整个屏幕。这些限定是通过iOS的autoLayout实现的。 -![lceva screenshot]({{ site.url }}/assets/img/3-8/LCEvaStructDemo.png) - -### 组件层次 -偏于展示的视图`UILabel`,`UIImageView`通过`BaseView`进行了一层封装,使其可以自适应高度。`QuestionView`是问题类视图的基类,它在原有增加了`loadQuestion`,`routePage`等方法,来处理相关的加载问题和跳转位置等操作。`SelectableView`对选择题视图的封装,通过KVC和KVO来处理选项之间的变化。 -![lcevastcut screenshot]({{ site.url }}/assets/img/3-8/LCEvaStruct.png) - -### 题目结构 - -起初布局和题目是放在一个文件里,随着布局和题目属性的增加变得相当难以维护。所以将两者独立出来。以questionid来索引到相关的题目。题型的不同导致题目文件没有统一的标准,这部分的解析和加载就交给各自的视图去完成。以下代码中表示,编号为0的题目是一个二选一的题型,选择`爷们儿`或`软妹子`,并配有相应的图片和不同的跳转项。 - -```json - "0":{ - "type":"bichoice", - "text":["爷们儿","软妹子"], - "imgs":["male.png","female.png"], - ... - "nextpage":["page2","page3"], - "prevpage":"page0" - }, -``` - -### 总结 - -在准备初期我已经考虑过组件化的可行性,并花了2天时间成功搭起了框架。随着深入细节会出现一些未曾考虑过或者考虑不完善的状况,这时就需要及时调整和重构。编码量也比预期的要多出许多,之后也一定会有不小的调整。好在一切都在掌控范围内。当框架有血有肉后,就能呈现较好的效果,如图。 -![screenshot]({{ site.url }}/assets/img/3-8/demo.png) \ No newline at end of file diff --git a/_posts/2015-03-22-abount-string-in-swift.markdown b/_posts/2015-03-22-abount-string-in-swift.markdown deleted file mode 100644 index b4e5f498..00000000 --- a/_posts/2015-03-22-abount-string-in-swift.markdown +++ /dev/null @@ -1,54 +0,0 @@ ---- -title: Swift中的String -date: 2015-03-22 22:41:51 +0800 -layout: post -current: post -cover: assets/images/welcome.jpg -navigation: True -tags: [Swift String] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - - -在Swift的项目中涉及String操作时,会发现一些让人无所适从的变化`[NSString length]`方法不见了,`substringWithRange`的参数变成了`Range`等。 - - -由于UTF-8和UTF-16是可变长编码,以往对字符串的操作,在某些情况下不能得到正确的结果。比如我们调用一个emoji表情的`[NSString length]`,得到的长度却2,和我们期待的1不相符。还有相关的索引不准的问题。[NSString 与 Unicode](http://objccn.io/issue-9-1/)给出了这些编码的异同,是篇很好的文章。 - -苹果出于安全和稳定性的考虑,避免程序员编码上的疏漏,就像引入Optional一样,对String进行了以上的修改。在Swift引入`String.Index`,避免我们通过不准确的整型对字符串进行操作。`String.Index`是`String`的一个内建结构体。实现的successor和predecessor方法帮我们计算索引的正确位置。此外还有个私有的_position属性来标示Index的真正位置。 - -```swift -extension String : CollectionType{ - struct Index : BidirectionalIndexType, Comparable, Reflectable { - func successor() -> String.Index - func predecessor() -> String.Index - func getMirror() -> MirrorType - } -} -``` - -text和text2的startIndex都是0,但是它们调用successor方法的结果不同,结果a占用了一个字符,🎾占用两个。如果我们凭直观来处理,难免会出错。 -```swift -var text: String = “abc” -var text2: String = “🎾🏇🏈” -text.startIndex.successor() //return 1 -text2.startIndex.successor()//return 2 -``` - -每一个字符串的Index维持自身的一个环境,不能互用。advance是一个处理ForwardIndexType接口的函数,String实现了ForwardIndexType接口,能够自己计算增加的距离。text2计算出来的range,超过text的范围,会引起报错。 - -```swift -let startIndex = text2.startIndex.successor() -let range = Range(start: startIndex, end: advance(startIndex, 1)) -text2.substringWithRange(range) //return 🏇 -text.substringWithRange(range) // error -``` - -长度的计算是通过countElements函数,不知道具体实现方式。根据Swift的提示,猜测是以O(N)的时间复杂度,以ForwardIndexType的形式循环计算出长度。 -> Return the number of elements in x. -> O(1) if T.Index is RandomAccessIndexType; O(N) otherwise. - -虽然操作上稍显繁琐,但我们充分运用advance,countElements等方法,能够封装出方便以及更稳定的方法。 \ No newline at end of file diff --git a/_posts/2015-04-06-gvuserdefault.markdown b/_posts/2015-04-06-gvuserdefault.markdown deleted file mode 100644 index cb80233b..00000000 --- a/_posts/2015-04-06-gvuserdefault.markdown +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: GVUserDefaults的源码研究 -date: 2015-04-06 15:37:53 +0800 -layout: post -current: post -cover: assets/images/welcome.jpg -navigation: True -tags: [Source code GVUserDefaults] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - - -### 简介 -GVUserDefaults是一个封装NSUserDefaults以达到仅仅通过使用property的set和get方法就可以实现本地存储。GVUserDefaults可以通过category对property进行分类,非常方便管理和使用。比如在`GVUserDefaults+user.h`下声明的property是用户本地数据,而`GVUserDefaults+event.h`下的就是事件相关的本地数据。 - -### 概念梳理 -GVUserDefaults的代码不多,核心功能用C语言和runtime相关的函数实现。因为对这些概念和函数不太了解,所以先梳理下。 - -#### C语言函数 - -1. [char *strdup(const char *s)](http://linux.die.net/man/3/strdup)复制字符串到新开辟的内存空间并返回新位置的指针。 -2. [char *strsep(char **stringp, const char *delim)](http://linux.die.net/man/3/strsep)作用类似NSString的componentsSeparatedByString,但是只进行一个分割,返回值指向分隔符之前的字符串位置,参数stringp更新至分隔符后的字符串位置。 -3. [char *strstr(const char *haystack, const char *needle)](http://linux.die.net/man/3/strstr)找到haystack中第一次出现needle的字符串的指针位置。 - -#### objc/runtime相关概念 -1. [SEL](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html#//apple_ref/c/tdef/SEL)表示函数Selector的一个字符串指针,指代函数名。唯有通过sel_registerName注册的方法才能拥有Selector。编译器会根据源码编译时自动生成相应函数的Selector。在运行时,我们可以手动调用生成。 -2. [IMP](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html#//apple_ref/c/tag/IMP)是指向函数的指针,该函数接收self和Selector参数。 -3. [class_copyPropertyList](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html#//apple_ref/c/func/class_copyPropertyList),[property_getName](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html#//apple_ref/c/func/property_getName),[property_getAttributes](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html#//apple_ref/c/func/property_getAttributes)分别为获取类的property列表,名称和属性。属性由字符串组成,描述了property的声明时所有的特性。[官方文档](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101)给出了相关的格式和实例。 -4. [sel_registerName](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html#//apple_ref/c/func/sel_registerName)用C的字符串注册一个方法,返回该方法的Selector。 -5. [class_addMethod](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html#//apple_ref/c/func/class_addMethod)为一个类增加方法,提供Selector,IMP以及参数类型字符串作为参数。参数类型字符串标示着返回值和参数的数据类型。 - -### 实现流程 - -GVUserDefaults的核心方法是generateAccessorMethods,在初始化时调用。通过遍历当前类的property列表,与事先实现的存取方法(见下)匹配,动态添加property的setter和getter方法。为了能够知道每个Selector对应存储的Key,通过一个叫mapping的字典以setter和getter的字符串作为Key,property的名称作为Value来进行索引。 - -如下为一个整型的setter和getter的方法实现。同样类型的property指向相同的IMP,通过SEL名称找到相关的Key以实现存取。 -```objc -static void integerSetter(GVUserDefaults *self, SEL _cmd, int value) { -NSString *key = [self defaultsKeyForSelector:_cmd]; -[self.userDefaults setInteger:value forKey:key]; -} - -static int integerGetter(GVUserDefaults *self, SEL _cmd) { -NSString *key = [self defaultsKeyForSelector:_cmd]; -return (int)[self.userDefaults integerForKey:key]; -} -``` - -### 小结 -GVUserDefaults的源码非常简短,却实现了一个令人眼前一亮的功能。自此我们便可以方便存取,再也不用多写琐碎的代码和记那些搞不灵清的KeyValue。这篇文章对源码进行了简短的剖析。当然读万卷书不如看源码,希望通过这篇文章的索引可以为对runtime陌生的同志提供帮助。 diff --git a/_posts/2015-04-10-optimization-of-boohee.markdown b/_posts/2015-04-10-optimization-of-boohee.markdown deleted file mode 100644 index 658864fa..00000000 --- a/_posts/2015-04-10-optimization-of-boohee.markdown +++ /dev/null @@ -1,167 +0,0 @@ ---- -title: 薄荷TimeLine的优化 -date: 2015-04-10 17:24:47 +0800 -layout: post -current: post -cover: assets/images/welcome.jpg -navigation: True -tags: [TimeLine Optimize] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - - -## 简介 - ->薄荷APP是国内最受欢迎的健康减肥APP,是由上海薄荷信息科技有限公司创立,是中国领先的体重管营商。薄荷科技建立了中国最大最活跃的在线减肥平台已服务上百万的减肥用户。 - -Timeline是薄荷app主要的展示页面,由于历史原因,一直卡顿不理想。我从一开始就希望能改进这块性能及UI,给用户提供极致的体验。对于性能的优化也一直是我感兴趣的方向之一。现在终于有机会接触这块功能,便饶有兴致得进行一番研究并归纳总结,以便为之后的版本打好基础。 - -### 工具 -*Xcode,Instruments-Time Profiler,KMCGeigerCounter,My Eye* - -## 案例 - -### 1.无意义的IO消耗 -这个问题的发现一度让我非常震惊。代码中充斥着类似这样的调用: - -```objc -[[SQUtils getValueFromSettingPlist:@“leftSpace.width”] floatValue]; -``` -这个方法的作用是读取配置文件中的属性信息,包括Timeline和Cell的背景色,字体大小和颜色,间距等信息。方法本身没有做任何缓存,每次取值都需要经过读取、解析、拆分和取值的过程。 -```objc -+ (id) getValueFromSettingPlist:(NSString*)key{ - NSString *localizedPath = [[NSBundle mainBundle] pathForResource:kSQScrollSettingsFilename ofType:@“plist”]; - NSData *plistData = [NSData dataWithContentsOfFile:localizedPath]; - id plist = [NSPropertyListSerialization propertyListFromData:plistData mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&plistError]; - NSArray* keys = [key componentsSeparatedByString:@“.”]; - NSDictionary* dict = [NRSimplePlist valuePlist:kSQScrollSettingsFilename withKey:[keys objectAtIndex:0]]; - return dict[keys.lastObject]; -} -``` -这一方法本身没有什么问题,但是在Timeline的场景下,频繁创建Cell时IO操作的频率简直令人发质。更甚者颜色的ARGB属性都分次读取,我的下巴都要掉下来了。 -```objc -SQRGBACOLOR( -[[SQUtils getValueFromSettingPlist:@“CellBackground.color-red”] floatValue], -[[SQUtils getValueFromSettingPlist:@“CellBackground.color-green”] floatValue], -[[SQUtils getValueFromSettingPlist:@“CellBackground.color-blue”] floatValue], -[[SQUtils getValueFromSettingPlist:@“CellBackground.color-alpha”] floatValue]); -``` - -如果没有特殊的需求,这些属性完全可以硬编码到源文件里。如果需求方期望保留配置文件以便将来在线动态更改,那么应当进行缓存`plist`对象,避免频繁IO操作。再进一步,可以使用单例,使各UIColor、UIFont的对象只构造一次,降低了构造的频率。 - -### 2.异步处理耗时操作 - -在TimeLine的正文中,往往会有可跳转的超链接或标签。传统的UILabel并不支持这些属性,可以通过NIAttributedLabel、TTTAttributedLabel等第三方库实现。这些库都是用官方名为`NSDataDetector`的类实现链接解析操作。解析是个比较耗时的操作,因此如NIAttributedLabel提供了异步处理的操作,解析完成后更新相关试图。 - -```objc -- (void)_deferLinkDetection { - if (!self.detectingLinks) { - self.detectingLinks = YES; - - NSString* string = [self.mutableAttributedString.string copy]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSArray* matches = [self _matchesFromAttributedString:string]; - self.detectingLinks = NO; - - dispatch_async(dispatch_get_main_queue(), ^{ - self.detectedlinkLocations = matches; - self.linksHaveBeenDetected = YES; - - [self attributedTextDidChange]; - }); - }); - } -} -``` - -我们的代码被没有利用好这样的异步功能,同步解析为降低滑动的流畅性又作出了应有的“贡献”。 - -### 3.缓存中无谓的性能消耗 -缓存本来是性能提升的主要手段,但是不当的实现不能充分发挥缓存的性能优势。 -```objc -//消耗占比 118*0.81/2352 = 4% -- (void)cacheCells:(UITableViewCell*)cell{ - if (!_cellCaches) { - _cellCaches = [[NSMutableArray alloc] init]; - } - - if ([_cellCaches indexOfObject:cell] == NSNotFound) { - [_cellCaches addObject:cell]; - } - - if ([self.cellCaches count] > 20) { - [self.cellCaches removeObjectAtIndex:0]; - } -} -``` - -此处缓存的容量为20,超出之后就会删除最前排的元素。这样的实现在TimeLine下拉的场景中,必然会频繁创建新元素,每缓存一次都会调用`removeObjectAtIndex`方法。而该方法的弊端如官方文档所述*To fill the gap, all elements beyond index are moved by subtracting 1 from their index*.不停移动数组中元素,从而导致无谓的消耗。 - -```objc -//消耗占比 129*0.81/3494 = 3% -- (void)cacheCells:(UITableViewCell*)cell { - static NSInteger index = 0; - if (!_cellCaches) { - _cellCaches = [[NSMutableArray alloc] initWithCapacity:20]; -//Edited on 2015-4-18,之前没有加初始化 - for (int i=0;i<20;i++){ - cellCaches[i] = [NSNull null]; - } - } - - if ([_cellCaches indexOfObject:cell] == NSNotFound) { - _cellCaches[index] = cell; - index = index == 19 ? 0 : ++index; - } -} -``` - -我使用了一个会循环移动的索引,指向当前存储得位置,来避免`removeObjectAtIndex`带来的的开销。由对比结果可以看出,尽管降低1%,但仍有3%的时间被消耗。从分析工具中可以看到,是被踢出缓存的对象在调用`cxx_destruct`方法进行内存释放。相关知识点可以参考[这篇文章](http://blog.sunnyxx.com/2014/04/02/objc_dig_arc_dealloc/)。 - -### 4.cell的复用 - -在iOS开发实践中通常对UITableView的cell采取复用机制。因为对象的创建通常伴随着一定的开销,当cell过于复杂时尤甚。在原来的实现中,我们可以看到复用代码的使用。但是由于同时使用自定义的cache(第三点所讲),导致实际上`dequeueReusableCellWithIdentifier`是没有任何意义的。自定义的cell的配置是在初始时进行,并不支持复用。如果不是在cache中就得重新创建。因而,在不断下拉的过程中,实际上是都是在创建新的cell。 -```objc - ONESQScrollCellViewMixVotesWithDestroyButton *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; - - ONESQCellModelExtra* model = [self.cells objectAtIndex:indexPath.row]; - if (model) { - cell = (ONESQScrollCellViewMixVotesWithDestroyButton*)[self loadCellFromCache:model.index]; - } - -``` -所以,为了实现cell的复用,我必须重构cell使其可配置。在初始化时通过`buildContentView`创建Cell中的视图元素。新增的`configure`方法针对model实现视图的配置,包括UILabel高度的计算和ImageView宽高的设置和相关内容的更新等。cell可配置之后,就可以很自然地运用系统的复用机制。 - -```objc -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - static NSString *CellIdentifier = @“SQTimelineCell”; - ONESQScrollCellViewMixVotesWithDestroyButton *cell = (ONESQScrollCellViewMixVotesWithDestroyButton *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; - ONESQCellModelExtra* model = [self.cells objectAtIndex:indexPath.row]; - if (cell == nil) { - cell = [[ONESQScrollCellViewMixVotesWithDestroyButton alloc] init:model reuseIdentifier:CellIdentifier]; - cell.delegate = self; - } - [cell configure:model]; - return cell; -} -``` - -在实现了真正的cell复用后,第三点中的cache重要性就大大降低。最终被我弃用。 - -#### 复用所引发的问题 -复用后TimeLine的流畅性有了显著的提升。但是在快速滑动时,出现了莫名的崩溃。经过分析得出是由第二条中的detectingLinks引起的。detectingLinks针对富文本字符串进行分析,是一个异步过程。由于Timeline的快速滑动,复用的cell被新的字符串替代,而分析完成后需要根据分析结果对字符串颜色字体等进行渲染。由于前后字符串的不一致,最终出现了outOfRange的溢出异常。 - -### 5.弃用autoLayout -为了使cell能够适配不同尺寸屏幕,在重构过程中我使用了自己比较熟悉和依赖的autoLayout布局框架Masonry。然后自己没有认识到autoLayout的性能为题。autoLayout的本质是计算各视图之间的一元二次方程,而当view嵌套过多时,计算的时间是成指数级增长,[文章](http://pilky.me/36/)给出了相关的测试数据。 - -在遇到autoLayout的瓶颈后,立即转向最传统也最高效的setFrame布局方式。最后用KMCGeigerCounter对比两种实现。使用autoLayout的情形,丢帧率达到20%左右。而直接通过计算和setFrame的方式,丢帧率很少超过10%。性能的提升可见一斑。AutoLayout是把双刃剑,我们需要根据不同的场景来进行适当取舍。 - -### 总结 -经过一周左右的重构后,TimeLine终于可以流畅地呈现。这其中遇到的性能问题都可以尝试用缓存、异步处理、cell复用以及弃用低效的实现,这些都是很基础的方案。性能优化是一个权衡的过程,比如复用了cell就不再适用detectingLinks方法。比如为了性能弃用简单的autoLayout实现。 - -尝试使用优秀的高性能框架比如Facebook的AsyncDisplayKit,其异步渲染工作,能更容易应对复杂的场景,是开发者的福音。 - -优化是一个永不止步,除了性能还有UI优化、业务逻辑优化等多方面。有些能看出来,有些则不那么明显。希望在之后的编码过程中能进一步优化自己的代码。开发优质的产品。 \ No newline at end of file diff --git a/_posts/2015-04-25-look-into-catranstion.markdown b/_posts/2015-04-25-look-into-catranstion.markdown deleted file mode 100644 index fe7773b7..00000000 --- a/_posts/2015-04-25-look-into-catranstion.markdown +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: CATransition初探 -date: 2015-04-25 12:46:45 +0800 -layout: post -current: post -cover: assets/images/welcome.jpg -navigation: True -tags: [CATransition] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - - -本周的工作中有一个替换UINavigationBar标题字符串的小需求。为了使字符串的替换更自然,我找了利用CATransition实现动画自然过渡的方法,新字符串淡出旧字符串淡出。 - - -```objc -CATransition *animation = [CATransition animation]; -animation.duration = 0.5; -animation.type = kCATransitionFade; -animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; -[self.navigationController.navigationBar.layer addAnimation: animation forKey: @“changeTextTransition”]; -self.navigationController.navigationBar.topItem.title = title; -``` - -在我对iOS动画的局限的认知中,动画是通过时间函数或帧变化逐渐改变View的属性值,如frame,color以实现动画效果。然后CATransition的变化过程中,有新旧两个值的残影,用之前的思路是显然是无法走通的。出于好奇,对CATransition的实现原理进行了了解,下图为其在CAAnimation中的继承位置,和我们常用的CABasicAnimation和CAKeyframeAnimation不是一个分支。因此可以料想到实现上的差异。 -![screenshot]({{ site.url }}/assets/img/4-27/65cc0af7gw1dxlusbklpmj.jpg) -进一步了解其实现过程,根据国外友人在SO的[回答](http://stackoverflow.com/questions/2233692/how-does-catransition-work): - ->When you add the transition as an animation, an implicit CATransaction is begun. From that point on, all modifications to layer properties are going to be animated rather than immediately applied. The way the CATransition performs this animation to to take a snapshot of the view before the layer properties are changed, and a snapshot of what the view will look like after the layer properties are changed. It then uses a filter (on Mac this is Core Image, but on iPhone I’m guessing it’s just hard-coded math) to iterate between those two images over time. - -一个CATransition动画启动后的持续过程中,所有对这个layer所包含的属性值的操作都将以动画的方式进行。为了达到渐变效果,会先截取初始视图的快照,然后截取变化后最终视图的快照。通过算法在两个快照之间做变化的效果。两张快照的变化过程依旧遵循时间函数。 - -所以就最终解释了为什么在启动Animation后,通过设置NavigationBar的Title值,依旧能够实现淡入淡出效果了。 - - diff --git a/_posts/2015-04-30-create-your-pod-library.markdown b/_posts/2015-04-30-create-your-pod-library.markdown deleted file mode 100644 index 2a54a154..00000000 --- a/_posts/2015-04-30-create-your-pod-library.markdown +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: 创建你的CocoaPod库简明教程 -date: 2015-04-30 21:08:36 +0800 -layout: post -current: post -cover: assets/images/welcome.jpg -navigation: True -tags: [cocoapods] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - - -``` -pod lib create [pod name] -``` -基于模板创建Pod库,会询问一些基本问题,比如是否提供Example,是否提供测试等。执行后会生成一系列文件,其中Pod文件夹中放入你的代码和资源。Example中提供相应的样例代码。 - -在Example文件夹下`pod install或pod update`来引入或更新你的代码库。之后就是漫长的编码测试阶段。 -``` -coding—->debuging—>testing- -^ | -|——————<——————<——————————-| - -``` - -完成代码后,开始准备提交信息。在`.podspec`中完善你的库信息,包括项目描述以及存放代码的github地址。添加[Travis CI](https://travis-ci.org/profile)集成化测试(调用代码Xcode的测试框架)。测试通过的代码在github页面会显示`build passing`。 - -将代码发布到github上,打Tag并推送到远程仓库。Podspec文件中的version对应git中的tag,在更新时候需要记得统一。 -``` -git add -A && git commit -m “Release 0.0.1.” -git tag ‘0.0.1’ -git push —tags -``` - -通过`pod lib lint`检测你的Podspec信息是否正常。使用`—verbose`查看详细。`pod spec lint`会联网检测你的版本库和tag状态。 - -最后注册并发布到[CocoaPod](http://cocoadocs.org/),稍等片刻就可以在上面查到你的开源库了。 -``` -pod trunk register -pod trunk push -``` - - -#### 参考资料: -1. [Getting setup with Trunk](https://guides.cocoapods.org/making/getting-setup-with-trunk) -2. [Using Pod Lib Create](http://guides.cocoapods.org/making/using-pod-lib-create) -3. [Making a CocoaPod](http://guides.cocoapods.org/making/making-a-cocoapod.html) -4. [Specs and the Specs Repo](http://guides.cocoapods.org/making/specs-and-specs-repo.html) \ No newline at end of file diff --git a/_posts/2015-08-06-review-of-thirdparties.markdown b/_posts/2015-08-06-review-of-thirdparties.markdown deleted file mode 100644 index 8ee28431..00000000 --- a/_posts/2015-08-06-review-of-thirdparties.markdown +++ /dev/null @@ -1,128 +0,0 @@ ---- -title: 薄荷App开发中用到的第三方库 -date: 2015-08-06 10:12:43 +0800 -layout: post -current: post -cover: assets/images/welcome.jpg -navigation: True -tags: [third party depency] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - - -这篇文章简述了我们在重构薄荷App时所采用的第三方库,这些库是一款功能完整的App必要的组成部分。 -在这里只是简单讲述各个库的应用场景或实现原理,有些比较复杂的框架还请读者自行了解其设计思想和使用方法,恕不累述。 - - -### 界面开发的相关库 - -#### [Classy](https://github.com/cloudkite/Classy) -借鉴的CSS的方式,将UI相关的属性写在配置文件中,方便全局使用和修改。 - -原理:通过方法交换实现挂钩,在UIView进行didMoveToWindow调用时,采用NSInvocation和KVC的方式,改变UIView的属性。 - -不需要重启App达到实时更新的方式是利用dispatch_source_t侦听文件变化,继而调用相应的设置UIView的方法。以下代码在其他场景下有使用价值。 - -```objc - -+ (dispatch_source_t)watchForChangesToFilePath:(NSString *)filePath withCallback:(dispatch_block_t)callback -{ - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - int fileDescriptor = open([filePath UTF8String], O_EVTONLY); - - NSAssert(fileDescriptor > 0, @“Error could subscribe to events for file at path: %@“, filePath); - - __block dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fileDescriptor, - DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE | DISPATCH_VNODE_EXTEND, - queue); - dispatch_source_set_event_handler(source, ^{ - unsigned long flags = dispatch_source_get_data(source); - if ( flags ) { - dispatch_source_cancel(source); - callback(); - [self watchForChangesToFilePath:filePath withCallback:callback]; - } - }); - dispatch_source_set_cancel_handler(source, ^(void) { - close(fileDescriptor); - }); - dispatch_resume(source); - return source; -} - -``` - -另一块比较复杂的代码是解析类CSS文件,可以深入学习借鉴。 - -### [AsyncDisplayKit](https://github.com/facebook/AsyncDisplayKit) -Facebook推出的异步UI渲染的框架,保证了复杂的UITableView滑动流畅性。 - -### [PureLayout](https://github.com/smileyborg/PureLayout) -以简单友好的方式封装了Apple自己啰嗦的AutoLayout代码。自动布局的利器。 - -### [Pop](https://github.com/facebook/pop) -FaceBook出品的动画效果引擎。 - -### [MTStringAttributes](https://github.com/mysterioustrousers/MTStringAttributes) -对NSAttributedString的创建过程进行封装,以MarkUp的形式创建,之后采用Slash解析字符串,对相应的Tag施以预设的属性。 - -### [PXRotatorView](https://github.com/drinking/PXRotatorView) -自家封装ReactiveCocoa和iCarousel而成的轮播器组件。 - - -#### 其它常见的通用组件 -- CRToast -- iCarousel -- IDMPhotoBrowser -- DZNEmptyDataSet -- FSCalendar -- JSBadgeView -- MJRefresh - -### 数据存储及文件操作 - -### [Mantle](https://github.com/Mantle/Mantle) -在Model,NSDictionary和Json之间进行智能地转换,省去手工转换代码的枯燥劳动。 - -### [YTKKeyValueStore](https://github.com/yuantiku/YTKKeyValueStore)和[FMDB](https://github.com/ccgus/fmdb) -FMDB是对SQLite的一层封装,YTKKeyValueStore在FMDB基础上提供了KeyValue操作数据存储的方法。现今的移动客户端数据越来越倾向走网络,本地的数据采用KeyValue的形式满足绝大多数需求。 - -### [BHFileManager](https://github.com/drinking) -自家封装的进行文件创建,删除等操作的库。使用了[ZipArchive](https://github.com/ZipArchive/ZipArchive)进行文件压缩解压缩和[FCFileManager](https://github.com/fabiocaccamo/FCFileManager)进行基本的文件操作。 - -### [GVUserDefaults](https://github.com/gangverk/GVUserDefaults) -NSUserDefaults的拓展,以set和get的形式就可以使用持久化的对象,适合处理配置信息等一类信息量不大的数据,非常方便。 - -### 网络层 - -### [BHAPI]() -自家实现的网络层通信库,实现了一套json格式的API文档转ObjectiveC代码的机制,统一管理。底层基于MKNetworkKit,也方便换成AFNetWroking。 - -### [AFNetWorking](https://github.com/AFNetworking/AFNetworking) -路人皆知的网络通信层框架 - -### [SDWebImage](https://github.com/rs/SDWebImage) -路人皆知的图片异步加载的库。此外,我们还应用这个库的图片存储接口,进行其他图片的缓存。 - -### 其它工具库 - -### [ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa) -信号量的合并及串联,将异步代码从繁复的嵌套中脱离出来,以优雅的高内聚的方式执行。大大增加代码的可阅读性和可维护性。MVVM的设计思想,简化了传统MVC中Controller层的代码量,将一部分代码抽离到ViewModel层,和View层进行单向或双向的数据绑定。精简了业务代码,增加了复用性。RAC已经成为我们项目中,最不可或缺的库,当发现能采用如此优雅的代码解决问题时,你再也不想回到过去了。 - - -### [JSPatch](https://github.com/bang590/JSPatch) -使用JS方式更改线上app的行为,主要用以修复Bug。 - -原理:通过改变消息转发的目标,将原生方法指向JS方法,达到方法替换的目的。详细原理在作者的博客中有阐述,请移步[bang's blog](http://blog.cnbang.net/)。 - -### [Tweaks](https://github.com/facebook/Tweaks) -设置一些全局调控的开关,比如QA和Release环境切换。另一点也是其主要的应用,进行UI数据的实时的微调。不过在使用Reveal调节界面后,也极少会用到。 - -### [DateTools](https://github.com/MatthewYork/DateTools) -时间判断和比较上很方便的库。 - -### [BHRouter]() -自家实现的以URL的形式进行页面间的跳转,去除了ViewController之间的耦合。支持如Http等其它协议,使得在Native和WebView之间自如跳转。 diff --git a/_posts/2016-01-18-api-blue-print.markdown b/_posts/2016-01-18-api-blue-print.markdown deleted file mode 100644 index 38b83767..00000000 --- a/_posts/2016-01-18-api-blue-print.markdown +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: APIBlueprint To Swift Request -date: 2016-01-18 18:39:48 +0800 -layout: post -current: post -cover: assets/images/covers/IMG_4396.JPG -navigation: True -tags: [APIBluePrint Swift] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - -这是一个个人项目,目标是将描述Web请求的文档自动转换成用Swift实现的网络请求。前面一部分功能已由APIBluePrint完成,我编写的模块复杂将Json格式的文档描述文件转换成Swift代码。 - -### API Blueprint语言 -> API Blueprint is simple and accessible to everybody involved in the API lifecycle. Its syntax is concise yet expressive. With API Blueprint you can quickly design and prototype APIs to be created or document and test already deployed mission-critical APIs. - -#### Drafter -将API Blueprint的文档解析成JSON或YAML文件。AST的格式已经启用,以[Refract Parse Result](https://github.com/refractproject/refract-spec/blob/master/namespaces/parse-result-namespace.md)为主。 - -```shell -drafter -t refract -f json blueprint.apib -``` - -#### Drakov -Drakov是一个Mock Server,解析API描述文档为测试提供伪数据服务。 -```shell -drakov —f blueprint.apib -``` - - -#### Swift方面依赖的第三方库 -- Alamofire 用于作为网络请求的基础框架 -- SwiftyJSON 处理JSON时便利的工具 -- Coolie 将JSON转换成Swift中的Model工具 - -### 进度 - - -**已完成** -- 将JSON描述文件转换成便于使用的Swift对象 -- 用Coolie将HttpResponse解析成Model -- Request with Params - -**进行中** -- 基于Alamofire构建的基础Web请求服务 - -**待完成** -- Chain Request -- 增加代理层,处理特殊的转换请求 - - -### 资料索引 -- [ApiBluePrint语法](https://github.com/apiaryio/api-blueprint/blob/master/API%20Blueprint%20Specification.md) -- [ApiBluePrint示例](https://github.com/apiaryio/api-blueprint/tree/master/examples) -- [Drakov](https://www.npmjs.com/package/drakov) -- [Drafter](https://github.com/apiaryio/drafter) diff --git a/_posts/2016-01-25-mantle-design.markdown b/_posts/2016-01-25-mantle-design.markdown deleted file mode 100644 index 01ce1839..00000000 --- a/_posts/2016-01-25-mantle-design.markdown +++ /dev/null @@ -1,132 +0,0 @@ ---- -title: Mantle源码阅读笔记 -date: 2016-01-25 16:58:58 +0800 -layout: post -current: post -cover: assets/images/covers/IMG_5625.JPG -navigation: True -tags: [source code mantle] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - -Mantle是一个为`Cocoa`和`Cocoa Touch`的Model层提供`JSON`和`Object`之间转换的框架,因其提供了较其它框架更为丰富的功能而被广泛应用。下面逐一谈下主要特性的实现原理及我的收获。 - -#### 相同属性名的映射 - -Mantle通过`MTLModel`协议规定了一个可转换对象应当具有的行为,并在`MTLModel`类中给出了实现。`MTLModel`类虽然给出了协议的所有实现,但是本身并没有用到。只是通过KVC的方式提供了`NSDictionary`到`Model`的映射,但是处理得非常谨慎,考虑到了内存泄露的问题,可以参考以下代码。 - -```objc - -static BOOL MTLValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUpdate, NSError **error) { - // Mark this as being autoreleased, because validateValue may return - // a new object to be stored in this variable (and we don't want ARC to - // double-free or leak the old or new values). - __autoreleasing id validatedValue = value; - - @try { - if (![obj validateValue:&validatedValue forKey:key error:error]) return NO; - if (forceUpdate || value != validatedValue) { - [obj setValue:validatedValue forKey:key]; - } - return YES; - } @catch (NSException *ex) { - NSLog(@"*** Caught exception setting key \"%@\" : %@", key, ex); - // Fail fast in Debug builds. - #if DEBUG - @throw ex; - #else - if (error != NULL) { - *error = [NSError mtl_modelErrorWithException:ex]; - } - return NO; - #endif - } -} - -``` - -`MTLJSONSerializing`协议继承了`MTLModel`协议并加入了三个重要的拓展,大大增加了灵活性。 - -- JSONKeyPathsByPropertyKey 使得JSON中的Key和Property的名称不用一样,通过一个字典找到对应的名称即可。 -- JSONTransformerForKey 可以自定义Value的转换方式,比如一个JSON中的dateString可以和一个NSDate对象相互转换。 -- classForParsingJSONDictionary 将JSONDictionary转换成换指定的另一个类的对象。 - - - -#### property的类型 -Mantle通过定义`MTLPropertyStorage`这个枚举,将`property`分为三种类型。有一种没有用到(也可能是随着版本迭代而弃用了),实际上最后将`property`分为可储值和不可储值的。不可储值的`property`没有变量空间`ivar`,`readonly`属性的变量容易出现这种情况。下面的代码给出了具体的实现,思维的缜密性在于考虑到了子类可能覆盖父类的`property`属性,一个被覆盖的`readonly`的属性可能在其父类中存在变量空间。 - -```objc - -+ (MTLPropertyStorage)storageBehaviorForPropertyWithKey:(NSString *)propertyKey { - objc_property_t property = class_getProperty(self.class, propertyKey.UTF8String); - - if (property == NULL) return MTLPropertyStorageNone; - - mtl_propertyAttributes *attributes = mtl_copyPropertyAttributes(property); - @onExit { - free(attributes); - }; - - BOOL hasGetter = [self instancesRespondToSelector:attributes->getter]; - BOOL hasSetter = [self instancesRespondToSelector:attributes->setter]; - if (!attributes->dynamic && attributes->ivar == NULL && !hasGetter && !hasSetter) { - return MTLPropertyStorageNone; - } else if (attributes->readonly && attributes->ivar == NULL) { - if ([self isEqual:MTLModel.class]) { - return MTLPropertyStorageNone; - } else { - // Check superclass in case the subclass redeclares a property that - // falls through - return [self.superclass storageBehaviorForPropertyWithKey:propertyKey]; - } - } else { - return MTLPropertyStoragePermanent; - } -} - -``` - -#### 获取propertyKeys -获取`property`名称的方式自然是用到了`runtime`的特性,逐次遍历`class_copyPropertyList`得到的所有属性,然后通过上面提到的类型筛选出可以真正赋值的变量名集合。值得注意的是,为了避免相同类的每个对象都重复这个操作,`Mantle`动态为[self class]类对象添加属性,作为缓存。这里更新了自己知识上的盲点,在iOS中类的本质上也是个对象,描述对象的对象。所以动态添加成员变量也就成了可能。以下摘自[Stackoverflow](http://stackoverflow.com/questions/15609149/is-it-correct-to-use-objc-setassociatedobject-for-class-object): - -> Yes, a class object is a full-fledged object, so you can do anything to it that you can do with a regular object. -> However, it is clearer and simpler to use a global variable. -> p.s. Associating it with [self class] is not the same as using a global variable, because [self class] gives you the actual class of the current object, which may vary as this method is inherited by subclasses. Whereas with a global variable it would always be the same variable. - -#### Transformer - -NSValueTransformer在Mantle中对象转换中起了相当重要的作用。下面的代码是官方示例中的使用方法,是由“Property Key”+“JSONTransformer”组成的。 - -```objc -+ (NSValueTransformer *)updatedAtJSONTransformer { - return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *dateString, BOOL *success, NSError *__autoreleasing *error) { - return [self.dateFormatter dateFromString:dateString]; - } reverseBlock:^id(NSDate *date, BOOL *success, NSError *__autoreleasing *error) { - return [self.dateFormatter stringFromDate:date]; - }]; -} -``` - -`MTLJSONAdapter`在进行Modle和JSON之间转换的过程中,会通过runtime来构造所有可能的JSONTransformer,构造代码非常典型,如果不按上述的命名方式是无法匹配到的。此外由于Class对象没有`performSelector`方法,所以采用非常trick方式来调用JSONTransformer类方法。因为之前意识到类也是一个对象,那么类方法就是类对象的方法,运行时的那一套依旧适用,动态添加类变量或者类方法,想想还是挺有趣的。 - -```objc -for (NSString *key in [modelClass propertyKeys]) { - SEL selector = MTLSelectorWithKeyPattern(key, "JSONTransformer"); - if ([modelClass respondsToSelector:selector]) { - IMP imp = [modelClass methodForSelector:selector]; - NSValueTransformer * (*function)(id, SEL) = (__typeof__(function))imp; - NSValueTransformer *transformer = function(modelClass, selector); - if (transformer != nil) result[key] = transformer; - continue; - } -} -``` - -最后一个问题一个MTLModel的子类中的成员变量中有同样是MTLModel类型的,那该怎么办?当然最直接的想法是再手动写一个JSONTransformer。然而这次你不用费心,Mantle如果发现一个对象没有定义JSONTransformer,会去判断这个对象的类型,若符合MTLJSONSerializing协议则会创建一个JSONTransformer。该Transformer的实现仍然是用到MTLJSONAdapter的相互转换的特性。这里有一种MTLJSONAdapter递归调用的思想。以下是实现可以参考函数dictionaryTransformerWithModelClass。 - -这篇文章就写到这里,Mantle的源码一大特点就是判断逻辑非常多,这些都是为了确保转换的稳定性和尽可能多的可转换性,不亲自参与或者认真对待代码是无法想到那么的情况。在以后的编程过程中我也应当更充分地考虑各种边界条件,提高代码的稳定性。 - diff --git a/_posts/2016-02-01-article-review.markdown b/_posts/2016-02-01-article-review.markdown index 8aab0c6e..73a62064 100644 --- a/_posts/2016-02-01-article-review.markdown +++ b/_posts/2016-02-01-article-review.markdown @@ -1,5 +1,5 @@ --- -title: 观展记I +title: 观展记 date: 2016-02-01 22:34:36 +0800 layout: post current: post diff --git a/_posts/2019-08-08-game-review.md b/_posts/2019-08-08-game-review.md index e910cf3c..a81fa439 100644 --- a/_posts/2019-08-08-game-review.md +++ b/_posts/2019-08-08-game-review.md @@ -22,7 +22,4 @@ comments: true 《前进!奇诺比奥队长》老任家一如既往的高品质旋转箱庭游戏。姑且不说第一眼就会让人喜欢的人物和场景,高品质的丰富、巧妙、不会让人感到重复的关卡设计。单就音乐而言,既有充满童趣的,又有紧张幽森的,让人置身于任天堂的世界,最神奇的是其中有一段的旋律让我听出了窦唯《山河水》的味道,竟产生一种淡淡的怀旧感。解谜过程基本能乐在其中,不会产生太多焦虑。一些巧妙的机关会让人灰心一笑。有次站在桥上张望寻找宝石在哪里,突然桥就塌了,宝石出现在桥下,如果不是那一刻的迟疑,而是走过桥去,就永远也看不到宝石了,甚至有那么一点点庆幸。有次乘矿车时死活找不到第一颗宝石,此时突然灵机一动想到了电影《头号玩家》中开倒车的场景,回头一看,宝石果然在身后房。另外,最后通完三本书的bonus关卡是《马里奥奥德赛》风格的关卡,奇诺比奥队长穿着奥德赛中的迷彩探险服,仿佛一下子回到了奥德赛的世界。两个游戏世界就这么连接起来了,或者确切说他们本来就是一个世界的,任天堂的游戏世界。据说这款游戏的一些关卡设计是利用了《马里奥奥德赛》开发过程中的产物,可谓物尽其用,恰到好处。 -其他游戏诸如《荒野大镖客2》的苍茫孤独,《只狼》火星四溅的刀剑冲击,《街霸》的拳拳到肉,《UFC》的拳拳见血,《实况20-demo》传切轻盈了许多,《马里奥制造》的无限可能,还在逐一体验。目前《荒野大镖客2》还是让我跟热爱一些,它是一款让我放慢了速通想法的游戏,骑马只身驰骋在旷野西部,打猎、采摘、烹饪,亦或是通宵玩德州,让我不疾不徐享受呆在里面的时间,上一个有这样想法的《塞尔达》。 - -以上就是这段时间的游戏感悟了。 -完。 \ No newline at end of file +其他游戏诸如《荒野大镖客2》的苍茫孤独,《只狼》火星四溅的刀剑冲击,《街霸》的拳拳到肉,《UFC》的拳拳见血,《实况20-demo》传切轻盈了许多,《马里奥制造》的无限可能,还在逐一体验。目前《荒野大镖客2》还是让我跟热爱一些,它是一款让我放慢了速通想法的游戏,骑马只身驰骋在旷野西部,打猎、采摘、烹饪,亦或是通宵玩德州,让我不疾不徐享受呆在里面的时间,上一个有这样想法的应该是《塞尔达-旷野之息》了。 diff --git a/_posts/2019-08-21-energy-efficiency.md b/_posts/2019-08-21-energy-efficiency.md deleted file mode 100644 index 5fa70232..00000000 --- a/_posts/2019-08-21-energy-efficiency.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: iOS省电指南—Reduce and Prioritize Work -date: 2019-08-21 16:39:48 +0800 -layout: post -current: post -cover: assets/images/covers/41566386531.jpg -navigation: True -tags: [battery energy timer ] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - -文章是苹果的官方文档《Energy Efficiency and the User Experience》的第一部分"Reduce and Prioritize Work"的简要梳理,刚好最近的功能需求会涉及到多种消耗CPU和IO的操作,预期会对用电量产生一定的影响。所以通过阅读该官方文档,好有一个全面的认知。 - -### 减少后台运算量 -减少在后台工作包括及时终止必要的后台服务和防止不必要的服务在后台执行,降低必要服务的频率。这些都可以通过系统API完成。 - -苹果给出的几个例子: -- Not notifying the system when background activity is complete -- Playing silent audio -- Performing location updates -- Interacting with Bluetooth accessories -- Downloads that could be deferred - -### 设置不同优先级 -苹果对任务优先级有四个维度的划分,即对应Quality of service (QoS) 的四个枚举值。更用户的使用认知也是息息相关,比如交互的行为需要最快的反馈,一次数据的处理操作能够忍受一定的时间,一个下载操作需要更久。所以结合业务场景,选择合适的优先级相比一窝蜂都用高优先级能够带给用户更好的体验。 -```` -NSQualityOfServiceUserInteractive -NSQualityOfServiceUserInitiated -Default(不属于枚举中的)没有指定枚举值时的隐藏默认值,优先级为当前所在位置 -NSQualityOfServiceUtility -NSQualityOfServiceBackground -Unspecified(不属于枚举中的)不涉及QoS的历史代码场景 -``` -### 减少计时器的使用 -定时器会导致CPU或其他系统模块被从空闲态唤起,导致了一定的电能损耗。减少不必要的timer误用,比如靠timer轮训查看状态,就可以用通知的方式解决。对于倒计时功能的timer,可能暂时无法避免或者没有更好的方案。所以苹果也给了必要的三个使用选择。 -- Use timers economically by specifying suitable timeouts. -- Invalidate repeating timers when they’re no longer needed. -- Set tolerances for when timers should fire. - -设置timer的tolerance可以有效降低电能的损耗,因为如果多个timer的触发时间接近,系统可以高效的执行一次触发操作,大大提高调用效率。如果系统没有这种实现,在用户层面也可以实现一个类似的功能。另外如果同时有几个timer,低频的timer就可以由高频的timer统一管理,触发。比如每秒一次的timer就可以在第十次触发时顺带触发十秒一次的timer。NSTimer在系统层面应该也是有更高频的时钟在实行类似的管理。 - -### 引用文档 -- [Energy Efficiency Guide for iOS Apps: Work Less in the Background](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/EnergyGuide-iOS/WorkLessInTheBackground.html#//apple_ref/doc/uid/TP40015243-CH22-SW1) \ No newline at end of file diff --git a/_posts/2021-04-09-clover-upgrade-to-bigsur.md b/_posts/2021-04-09-clover-upgrade-to-bigsur.md deleted file mode 100644 index 68a31942..00000000 --- a/_posts/2021-04-09-clover-upgrade-to-bigsur.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -title: 黑苹果系统升级 Hackintosh upgrade to Big Sur from Catalina -date: 2021-04-09 12:00:48 +0800 -layout: post -current: post -cover: assets/images/covers/WechatIMG11.jpeg -navigation: True -tags: [clover hackintosh Big Sur Catalina ] -class: post-template -subclass: 'post tag-getting-started' -author: Drinking -comments: true ---- - -此次升级把一年前通过Clover安装的黑苹果Catalina系统升级到Big Sur. 期间也花费了不少时间,试了不少错,好歹最后也成功直接升级,没有白费力气.这里就简单记录一下升级过程和经验给需要的人留作参考. - -### 升级Clover -升级Colver主要参考文章 [How to Update Clover for BigSur Compatibility using OpenRuntime and Quirks](https://www.insanelymac.com/forum/topic/345789-guide-how-to-update-clover-for-bigsur-compatibility-using-openruntime-and-quirks-v5123/) **注意逐字逐句阅读** -主要流程简单概述为 -1. 备份当前在用的的EFI文件夹,放到另外的存储设备上 -2. 将最新的Clover安装到独立的U盘,升级Clover Configurator -3. 按照文章指引删除无用的EFI和kext文件 -4. 从[OpenCore](https://dortania.github.io/OpenCore-Install-Guide/config.plist/#selecting-your-platform)中勾选对应硬件.**注意Clover的Quirks区域包含OpenCore三块配置,[How to...](https://www.insanelymac.com/forum/topic/345789-guide-how-to-update-clover-for-bigsur-compatibility-using-openruntime-and-quirks-v5123/)一张图片用三种颜色区分,另一张包含Colver和OpenCore的映射,需要仔细匹配和勾选** -5. 插入U盘,设置BIOS从U盘启动,尝试配置是否能正常进入系统. - -#### 踩坑 -正常进去系统后发现不能正常关机和重启,影响升级操作.尝试了不下于以下几种网络上查到的方案. -1. ACPI中设置fixshutdown或者清空fix相关的选项 -2. 使用slide=0和EmuVariableUefi.efi -3. 逐个尝试AptioMemoryFix.efi,OsxAptioFix2Drv-free2000.efi,OsxAptioFix2Drv.efi,OsxAptioFix2Drv.efi,OsxAptioFix3Drv.efiOsxAptioFix3Drv.efi -4. 逐个替换旧Covler中的kext文件 -5. 删除网卡驱动 - -最后参考文章[ Shutdown & Power Management don't work after Clover 5120+ & OCQuirks](https://www.tonymacx86.com/threads/shutdown-power-management-dont-work-after-clover-5120-ocquirks.304793/page-2)提供的SSDT-PMC.aml文件放置在ACPI>patched文件夹中**得以解决** - -#### 安装系统 -参考文章[Update Directly to macOS Big Sur](https://www.tonymacx86.com/threads/update-directly-to-macos-big-sur.304629/). -期间重启时找不到安装入口,需要设置Colver配置,将Gui中Hide Volumn中的preboot删除,这样就可以显示出MacOS install xxx via xxx 的启动项, 一直走这个入口升级,直到完全升级完成, 到最后进入新的BigSur系统. - -以上是对这次升级的一次记录,希望帮助有需要的朋友节约一些时间. \ No newline at end of file diff --git a/_posts/2022-02-02-woop-en.md b/_posts/2022-02-02-woop-en.md index 72bdbbf4..e55d19b4 100644 --- a/_posts/2022-02-02-woop-en.md +++ b/_posts/2022-02-02-woop-en.md @@ -13,7 +13,7 @@ comments: true --- -If you are not interested in the text, you can visit the [Woop](https://drinking.github.io/boop-web/ ) directly. Any questions about the use of the product, please check the documentation on the product page or leave feedback. +If you are not interested in the text, you can visit the [Woop](https://woop.bytebitter.com) directly. Any questions about the use of the product, please check the documentation on the product page or leave feedback. ### Why built Woop diff --git a/_posts/2022-02-02-woop.md b/_posts/2022-02-02-woop.md index 6cf2e703..75243898 100644 --- a/_posts/2022-02-02-woop.md +++ b/_posts/2022-02-02-woop.md @@ -13,7 +13,7 @@ comments: true --- -如果对行文不感兴趣,可直接访问[产品链接](https://drinking.github.io/boop-web/ ).如果有使用疑问可在产品页面查看文档或留言反馈. +如果对行文不感兴趣,可直接访问[产品链接](https://woop.bytebitter.com).如果有使用疑问可在产品页面查看文档或留言反馈. ### 设计初衷