双葉幼稚園 A Layman's Repository

__attribute__((objc_designated_initializer))

指定初始化器(designated initializer)是 Objective-C 中的一个重要的概念,但是很可惜的是,很多开发者(不知道为什么)并没能正确遵守关于指定初始化器的一些惯例。 之前,我们只能通过 code review 之类的方法来找出、修正这些问题;现在,clang 为我们提供了编译器级别的支持,能够找出不遵守管理的地方并给出警告,我们需要做的是标记出哪个初始化器是指定初始化器,例如:

@interface FTBObject

- (instancetype)init __attribute__((objc_designated_initializer));

@end

通过在接口定义中指定指定初始化器,不仅编译器能够给出相关的警告,而且需要继承该类的开发者也可以不参考文档就知道哪个初始化器是指定初始化器。

目前,clang 可以提供一系列警告,例如非指定初始化器没有调用其他初始化器、指定初始化器没有调用父类的指定初始化器、指定初始化器调用了非指定初始化器、子类没有复写父类的指定初始化器等。 具体的例子可以看这个测试用例

不幸的是,目前系统库的接口还没有这个属性,也就是说只有继承自己的代码和标记了这个指令的第三方库中的类才有用。

This File Does Not Exist At The Requested Revision

git blame 是我非常喜欢的功能,不过在 Xcode 中 blame 的时候有些文件会提示 "This file does not exist at the requested revision",没办法进行 blame,但是其他文件就没有问题。

调查了一下,是因为之前在 Xcode 中把这个文件名的大小写改变了,在项目文件中的文件名的大小写改变了,但是没有改变磁盘上的文件的大小写。HFS+ 默认是大小写不敏感的,所以正常打开文件没有问题,但是 git 的文件系统是大小写敏感的,所以 Xcode 用项目文件里的文件名去 git 中查询历史的时候就查找不到了。解决方法当然是让这些文件名的大小写一致。

先用 git mv 把文件改成另外的名字,然后再用 git mv 改成大小写正确的名字,这样 git 就能意识到文件名发生了改变,git status 就能显示出 rename 来了。不要用 mv,那样 git 意识不到文件改名了。

Fuck HFS+

双叶每周读 Week 44

嘛,上周感觉没什么好说的,于是就什么都没法出来,这次积攒了半个月,感觉还是看了点东西的

  1. OS X 10.9 Mavericks: The Ars Technica Review -> Mavericks 的评测,很长,总能找到不了解的东西,有时间的话前面几个 OS X 版本的评测也可以看下
  2. Git 和 Pager 的那点事 -> 了解下 Git pager
  3. How to lose $172,222 a second for 45 minutes -> 有种在看 Air Crash Investigation 的感觉
  4. 如何利用多核CPU来加速你的Linux命令 — awk, sed, bzip2, grep, wc等 -> 讲解了一下 GNU Parallel,这个之前没听说过,还挺神奇的
  5. Most People Won’t -> 满满一碗鸡汤
  6. 为什么中国伪科学横行?
  7. Google Read-time Bidding (doubleclick RTB) -> Google 是怎么在 100ms 内完成竞价把广告卖出去的
  8. Smart Air -> 之前的那个 200 RMB 的 DIY 空气净化器开始卖了……
  9. Launch Arguments & Environment Variables -> Xcode 的运行参数和环境变量设置,对 debug 很有用处
  10. UIAppearance for Custom Views -> 一篇 UIAppearance 的讲解,我之前尝试过自己实现 UIAppearance protocol,没成功,这样可以继续尝试一下
  11. Smart Proxy Delegation -> 用 NSProxy 来实现一个智能的代理对象,自动判断代理是否响应代理方法

双叶每周读 Week 42

这周是今年的第 42 周,不过这周没读到什么有趣的技术文章,所以就换换口味吧。

  1. 一个没有隐私的世界
  2. 隐私,信息以及信息安全
  3. Hacker News is a social echo chamber
  4. NSError -> 介绍 Cocoa 处理错误的方法,用了 Objective-C 之后就不喜欢其他语言的 exception 了

最近总觉得这个时代是个可悲的时代,不过或许每个时代都有它的可悲之处吧。或许隐私的界限会越来越模糊,老大哥会在越来越多的地方出现。

iOS 7 SDK Mirgration Cheat Sheet

最近搞了一点 iOS 7 SDK 的适配,来几个作弊条

  1. UITableViewCell 默认有了一个白色背景,我们来去掉它

    self.backgroundColor = [UIColor clearColor];
    
  2. 原本 UITableView 是它的 contentViewsuperView,现在中间多了一个 UITableViewCellScrollView,而且这个 UITableViewCellScrollView 默认 clipsToBounds = YES。这就导致很多(没有正确编写的) UITableViewCell 被截断。先来个 quick fix:

    self.contentView.superView.clipsToBounds = YES;
    

不过这仅仅是个 quick fix,真正想修正请正确计算 frame,不要偷懒!

  1. UISearchBar 多了一个 searchBarStyle 属性,把它设置成 UISearchBarStyleMinimal 然后自己设置背景色:

    if ([_searchBar respondsToSelector:@selector(setSearchBarStyle:)]) {
        _searchBar.searchBarStyle = UISearchBarStyleMinimal;
        _searchBar.backgroundColor = [UIColor whiteColor];
    }
    
  2. UISearchDisplayController 带出来的显示搜索结果的 table view 没有背景,会跟后面的 view 叠起来:

    - (void)searchDisplayController:(UISearchDisplayController *)controller
       didLoadSearchResultsTableView:(UITableView *)tableView {
        tableView.backgroundColor = [UIColor whiteColor];
    }
    
  3. 没有 UINavigationController 的视图中,处理 status bar 的方法:

    self.wantsFullScreenLayout = YES;
    

之后把里面的 view 都下移 20 points。

  1. 说实话这个我真没用到:

    if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) {
        self.edgesForExtendedLayout = UIRectEdgeNone;
    }
    

因为我用了

```objc
self.navigationBar.translucent = NO;
```

Apple 对许多属性的默认值做了修改,这应该是有它的意义的,大概是指导设计?我还没有想清楚。

修改的过程中发现除了 Apple 改的那些默认值以外大部分都是历史问题,出来混的,总是要还的。

双叶每周读 Week 41

昨天发烧了,今天补上吧 @_@

  1. The myth of NASA's expensive space pens -> ”美国花费巨资研制太空中用的笔,苏联用铅笔” 的故事的真实版本
  2. 缩进, Tab 还是空格? -> 我是空格党!文章里有些缩进的技巧
  3. Things You Should Never Do, Part I -> 作者反对重写的理由
  4. 编程名言名句
  5. iOS 7 -> objc.io 出了 iOS 7 的 issus,我目前只看了第一篇
  6. Key-Value Observing -> 一篇关于 KVO 的文章,我跟作者的观点一样:最牛逼的功能用了超烂的 API 来封装

双叶每周读 Week 40

唉,还是坚持一下吧。这周过十一,基本是宅了……

  1. How to make $100k in OSS by working hard -> Sidekiq 作者写开源软件赚钱的故事
  2. Six Stages of Debugging
  3. Tessel: The End of Web Development (as we know it) -> 未来一定是 Internet of Things 的时代
  4. Mac apps are harder than iOS apps
  5. OS X 下 terminal 中的复制粘贴

双叶每周读 Week 39

突然觉得自己每天看的东西还算挺有价值的,要不每周挑几篇文章贴出来?尝试一下,不知道能不能坚持下去,应该不能吧哈哈哈。

  1. ARM64 and You -> 了解 ARM64 和 Objective-C 运行时的变化
  2. Why I Hacked Apple’s TouchID, And Still Think It Is Awesome
  3. 7个示例科普CPU Cache
  4. WWDC 2013 Session笔记 - iOS7中弹簧式列表的制作
  5. Less is exponentially more -> Rob Pike 谈少即是多,C++ 程序员为什么不选择 Go
  6. A Story About ‘Magic' -> 遥远年代的一个神奇 bug
  7. Sloppy UI -> 围观 iOS 7 的渣质量

iOS 7

译自 NSHispter

NDA 终于结束了,我们终于可以聊聊 iOS 7 里超赞的新 API 了。而且新 API 有非常多。苹果在 WWDC Keynote 中说有“1500 个新 API”,。(尽管里面很大一部分只是把 id 换成了 instancetype,但是不管怎么说还是非常多)

带着版本升级的激动心情,接下来的几周我们会深入讨论 iOS 7 的许多新特性,但是这周,我们来看下那些隐藏颇深的新特性:NSData Base64 编码, NSURLComponents, NSProgress, NSProcessInfo activities, CIDetectorSmile, CIDetectorEyeBlink, SSReadingList, AVCaptureMetaDataOutput, AVSpeechSynthesizerMKDistanceFormatter


NSData (NSDataBase64Encoding)

Base64 是一种常用的把二进制编码成 ASCII 字符的手段。由于许多常用 web 技术只支持文本,并不支持二进制,所以 Base64 应用十分广泛。例如,CSS 可以用 inline data:// URIs 来内嵌图片,这种形式就经常使用 Base64 编码的文本。另一个例子是 Basic Authentication headers,它使用 Base64 编码用户名/密码对,稍微比完全明文好一点吧。

长久以来,这个重要的函数压根就没有,数以千计的开发者治好到论坛上随便抓一段代码贴过来就用。如此显而易见的缺失就像 iOS 5 之前的 JSON 一样恼人。

但是现在!iOS 7 终于内置了 Base64:

NSString *string = @"Lorem ipsum dolor sit amet.";
NSString *base64EncodedString = [[string dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0];

NSLog(@"%@", base64EncodedString); // @"TG9yZW0gaXBzdW0gZG9sYXIgc2l0IGFtZXQu"

NSURLComponents & NSCharacterSet (NSURLUtilities)

Foundation 框架里面有许多 URI 相关的功能。不过,很多功能是用 NSString 实现的,因为 NSURL 是不可改变的。

NSURLComponents 极大地改善了这种情况。把它当成是 NSMutableURL 就好了:

NSURLComponents *components = [NSURLComponents componentsWithString:@"http://nshipster.com"];
components.path = @"/iOS7";
components.query = @"foo=bar";

NSLog(@"%@", components.scheme); // @"http"
NSLog(@"%@", [components URL]); // @"http://nshipster.com/iOS7?foo=bar"

URL components 的每个属性都有一个 percentEncoded* 的变种(例如 userpercentEncodedUser),这些变种不会对 URI 中的特殊字符进行百分号编码。

你说那些字符算是特殊字符?好吧,这跟字符处在 URL 的哪个部分有关。好消息是 iOS 7 中 NSCharacterSet 增加了一个新的 category 来查询允许出现在 URL 中的字符。

  • + (id)URLUserAllowedCharacterSet
  • + (id)URLPasswordAllowedCharacterSet
  • + (id)URLHostAllowedCharacterSet
  • + (id)URLPathAllowedCharacterSet
  • + (id)URLQueryAllowedCharacterSet
  • + (id)URLFragmentAllowedCharacterSet

NSProgress

NSProgess 是一个有点难描述的类。它既是一个观察者,又是一个委托/协调者,扮演者报告和监控过程的角色。它在 OS X 上的系统级进程进行了整合,不过也可以用于面向用户的 UI。它可以指定暂停和取消的处理逻辑,让这些代码来真正地暂停和取消操作。

任何具有完成状态和总量概念的东西都可以使用 NSProgress,不管是写入文件的字节数、渲染的帧数,还是要从服务器上下载的文件数。

NSProgress 可以用本地化的方式来汇报总体进度:

NSProgress *progress = [NSProgress progressWithTotalUnitCount:100];
progress.completedUnitCount = 42;

NSLog(@"%@", [progress localizedDescription]); // 42% completed

…或者可以给一个代码块来让操作完全停止:

NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 
                                         target:self 
                                       selector:@selector(incrementCompletedUnitCount:) userInfo:nil 
                                        repeats:YES];

progress.cancellationHandler = ^{
    [timer invalidate];
};

[progress cancel];

NSProgess 在 Mac OS X 10.9 Mavericks 下很有用,但是在当前也还是一个包装工作量这个模式的一个很好的类。

NSArray -firstObject

欢呼吧!NSArray 中避免 NSRangeException-lastObject 终于有了对应的取第一个元素对应方法。(好吧,实际上这个方法在 iOS 4 的时候就已经成为私有 API 了,但是现在终于开放出来了)

注意了!

NSArray *array = @[@1, @2, @3];

NSLog(@"First Object: %@", [array firstObject]); // 1
NSLog(@"Last Object: %@", [array lastObject]); // 3

爽!

CIDetectorSmile & CIDetectorEyeBlink

As a random aside, shouldn't it be a cause for concern that the device most capable of taking embarrassing photos of ourselves is also the device most capable of distributing it to millions or people? Just a thought.

iOS 5 以后,Core Image 在 CIDetector 类中加入了面部识别和检测功能。如果检测照片中的人脸还不够炫的话,在 iOS 7 中我们甚至能检测笑脸和闭眼。*发抖*

又一个免费 app 的点子,只保存笑脸照片的相机 app,其中的一段代码可能是这样的:

@import CoreImage;
CIDetector *smileDetector = [CIDetector detectorOfType:CIDetectorSmile 
                                context:context 
                                options:@{CIDetectorTracking: @YES, 
                                          CIDetectorAccuracy: CIDetectorAccuracyLow}];
NSArray *features = [smileDetector featuresInImage:image];
if ([features count] > 0) {
    UIImageWriteToSavedPhotosAlbum(image, self, @selector(didFinishWritingImage), features);
} else {
    self.label.text = @"Say Cheese!"
}

AVCaptureMetaDataOutput

iOS 7 里在 AVCaptureMetaDataOutput 中新增加了扫描 UPC、QR 码、普通条形码和其他变种的方法。只需要添加一个 AVCaptureSession 的输出,实现相应的 captureOutput:didOutputMetadataObjects:fromConnection: 方法:

@import AVFoundation;
AVCaptureSession *session = [[AVCaptureSession alloc] init];
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;

AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device 
                                                                    error:&error];
if (input) {
    [session addInput:input];
} else {
    NSLog(@"Error: %@", error);
}

AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc] init];
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
[session addOutput:output];

[session startRunning];
#pragma mark - AVCaptureMetadataOutputObjectsDelegate

- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputMetadataObjects:(NSArray *)metadataObjects
       fromConnection:(AVCaptureConnection *)connection
{
    NSString *QRCode = nil;
    for (AVMetadataObject *metadata in metadataObjects) {
        if ([metadata.type isEqualToString:AVMetadataObjectTypeQRCode]) {
            // This will never happen; nobody has ever scanned a QR code... ever
            QRCode = [(AVMetadataMachineReadableCodeObject *)metadata stringValue];
            break;
        }
    }

    NSLog(@"QR Code: %@", QRCode);
}

AVFoundation 支持你听说过的所有条形码(大概还有一些没听过的):

  • AVMetadataObjectTypeUPCECode
  • AVMetadataObjectTypeCode39Code
  • AVMetadataObjectTypeCode39Mod43Code
  • AVMetadataObjectTypeEAN13Code
  • AVMetadataObjectTypeEAN8Code
  • AVMetadataObjectTypeCode93Code
  • AVMetadataObjectTypeCode128Code
  • AVMetadataObjectTypePDF417Code
  • AVMetadataObjectTypeQRCode
  • AVMetadataObjectTypeAztecCode

就算没有其他用途,AVCaptureMetaDataOutput 也使得在 iPhone 和 iPad 上创建一个 Passbook 条码读取器变得非常简单。

SSReadingList

即使真正会看保存在稍后阅读中的文章的人很少,可能只比扫描过 QR 码的人多一些。iOS 7 里新的 Safari Services framework 还是带来一个不错的功能:向 Safari 的阅读列表中添加文章。

@import SafariServices;
NSURL *URL = [NSURL URLWithString:@"http://nshipster.com/ios7"];
[[SSReadingList defaultReadingList] addReadingListItemWithURL:URL
                                                        title:@"NSHipster"
                                                  previewText:@"..." 
                                                        error:nil];

AVSpeechSynthesizer

语音合成(TTS)技术在 1960 年代后期出现以来,就成为了可用性恶作剧爱好者的利器。

iOS 7 把 Siri 的方便易用的Speak & Spell 功能带到了一个新的类 AVSpeechSynthesizer 中:

AVSpeechSynthesizer *synthesizer = [[AVSpeechSynthesizer alloc] init];
AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:@"Hello, World!"];
utterance.rate = AVSpeechUtteranceMinimumSpeechRate; // Tell it to me slowly
[synthesizer speakUtterance:utterance];

MKDistanceFormatter

最后,我们用 NSHipster 会喊出“终于来了!”的另一个类来结束我们的 iOS 7 的又新又炫的新 API 介绍,它就是:MKDistanceFormatter

就像上面说的,MKDistanceFormatter 提供了一种把公制/英制距离转换成本地化字符串的方法。

@import CoreLocation;
@import MapKit;
CLLocation *sanFrancisco = [[CLLocation alloc] initWithLatitude:37.775 longitude:-122.4183333];
CLLocation *portland = [[CLLocation alloc] initWithLatitude:45.5236111 longitude:-122.675];
CLLocationDistance distance = [portland distanceFromLocation:sanFrancisco];

MKDistanceFormatter *formatter = [[MKDistanceFormatter alloc] init];
formatter.units = MKDistanceFormatterUnitsImperial;
NSLog(@"%@", [formatter stringFromDistance:distance]); // 535 miles

就是这些了!这只是 iOS 7 超棒的新特性中很小的一部分。想要更多?看下 Apple Developer Center 中的文档 "What's New in iOS 7"

iOS app 中拨电话之后返回自己的 app

iOS app 里面打电话应该算是个比较常见的需求了,一般常用的方法是利用 UIApplication 里的 -openURL: 方法,但是这样打完电话之后就会停留在 Phone.app 里面了。就像这样:

NSURL *phoneCallURL = [NSURL URLWithString:[NSString stringWithFormat:@"tel:112"]];
[[UIApplication sharedApplication] openURL:phoneCallURL];

其实这种行为也是能预料到的,tel: 肯定是 Phone.app 注册的嘛。

不过利用 UIWebView 就能在拨打电话之后返回自己的应用。

_phoneCallWebView = [[UIWebView alloc] init];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"tel:112"]];
[_phoneCallWebView loadRequest:request];

用这个方法不需要自己添加确认的 action sheet 或者 alert view 了,会自动弹出来一个 alert view。另外可以用 UIWebView 的 delegate 方法 -webView:didFailLoadWithError: 来检测不能打电话的设备,在不能打电话的设备上打开 tel: URL 的时候这个方法会被调用。