iOS7中Objective-C和Foundation的新特性

转自kingzwt2009的专栏html

 

注意事项(Ray):文章来自iOS 7 by Tutorials  iOS 7Feast的一部分(略)前端

 

Objective-C 是最重要的iOS和OSX apps的开发工具。你可使用其余语言的第三方框架开发apps,例如HTML&Javascript或者C#,可是若是你很快的写出一个超炫的高效率的原声apps你就须要使Objective-C。ios

 

Foundation 是你开发Objective-C应用时用到的核心框架之一。objective-c

 

做为一名iOS开发者,很是有必要了解最新的Objective-C和Foundation的特性,在iOS7中有了一些重要的改变须要你了解。express

 

在这篇文章中,你将快速浏览一些在Objective-C和Foundation中新的功能。编程

 

1.Modules(模块)数组

机会是好的,你已经写了一千遍或更多#import语句:xcode

#import <UIKit/UIKit.h>   #import <MapKit/MapKit.h>   #import <iAd/iAd.h>

 

这个语法要追溯到Objective-C的根:vanilla C。#import语句是预处理器指令和#include有相似的方式工做。惟一的区别是#import不会导入已经导入的头文件;它是一次性处理。缓存

 

当预处理遇到一个#import命令时,就会按字面的意思用被导入的头文件的所有内容替换那一行。预编译会递归的这么处理,即便多是大量的头文件。网络

 

UIKit的头文件,UIKit.h,包含了UIKit框架中包含的全部其余头文件。这意味着,您没必要手动导入每一个框架的头文件,例如UIViewController.h,UIView.h UIButton.h的。

 

对UIKit框架的大小感到好奇嘛?经过计算全部行的所有UIKit中的头,你会发现它至关于超过11,000行代码!

 

在一个标准的iOS应用,你会在您的大部分文件中导入的UIKit,这意味着每个文件最终被长11000行。这是不够理想的,更多的代码意味着更长的编译时间。

 

1.1 原始解决方案:预编译头文件(Original solution: Pre-compiled Headers)

预编译的头文件,或PCH文件,试图解决这个问题,经过提供在编译的预处理阶段预先计算和缓存须要的代码。你可能看过Xcode生成的stock PCH 文件,像下面这样:

#import <Availability.h>   #ifndef __IPHONE_5_0   #warning "This project uses features only available in iOS SDK 5.0 and later."   #endif      #ifdef __OBJC__       #import <UIKit/UIKit.h>       #import <Foundation/Foundation.h>   #endif

 

若是开发人员开发的app的targets是iOS5以前的一个SDK,#warning将通知他们。UIKit和Foundation umbrella 头文件是stockPCH的一部分。由于在您的应用程序里的每个文件将使用Foundation而且大部分会使用UIKit。所以这些都是很好的添加对于PCH文件以便于在你的APP中预先计算和缓存这些文件的编译文件。

 

你可能会问“这有什么问题嘛?”PCH没有任何技术性的问题就像是——if it isn’t broke, don’tfix it(没有坏,就不要修)。然而你可能错失了不少性能优点,因为一个易维护的、高度优化的PCH文件致使(你可能会错过了一台主机上的维护良好的,高度优化的PCH文件的性能优点)。例如你可能在好几个地方用到Map Kit框架,你就会看到了经过添加Map Kit umbrella头文件或者单独的你用到的Map Kit类头文件到PCH文件中对编译时间的提高。

 

咱们都是lazy developers ,没有人有时间去维护咱们工做的项目的PCH文件。那就是为何modules被开发为LLVM的特性。

 

注意事项:LLVM是一个模块化和可重复使用的编译器和工具技术与Xcode捆绑的集合。 LLVM有几个组成部分:对oc开发者最重要的是clang,原生的C、C++和Objective-C编译器;和LLDB,原生debugger—开发者最好的朋友。

 

1.2 新的解决方案:模块 (Modules)

Modules第一次在Objective-C中公共露面是在2012 LLVM开发者大会上Apple’s Doug Gregor的一次谈话。这是一次迷人的谈话,强烈推荐给对编译感兴趣的人。你能够在线看这些视频:http://llvm.org/devmtg/2012-11/#talk6

 

Modules封装框架比以往任什么时候候更加清洁。再也不须要预处理逐行地用文件全部内容替换#import指令。相反,一个模块包含了一个框架到自包含的块中,就像PCH文件预编译的方式同样提高了编译速度。而且你不须要在PCH文件中声明你要用到哪些框架,使用Modules简单的得到了速度上的提高。

 

可是Modules不仅有这些,我相信你会想起这些步骤当你第一次在一个app使用一个新的框架的时候,就像下面这样:

(1).在使用框架的文件中添加#import

(2).用的框架写代码

(3).编译

(4).查看连接错误

(5).想起忘记连接的框架

(6).添加忘记的框架到项目中

(7).从新编译

 

忘记连接框架式是一件常常的犯的错误,可是Modules解决的很是好。

 

一个Modules不只告诉编译器哪些头文件组成了Modules,并且还告诉编译器什么须要连接。这个就解救了你不用你去手动的连接框架。这虽然是一件小事,可是能让开发更加简单就是一件好事。

 

1.3 怎样使用Modules

Modules的使用至关简单。对于存在的工程,第一件事情就是使这个功能生效。你能够在项目的Build Settings经过搜索Modules找到这个选项,改变Enable Modules 选项为YES,像这样:

 

全部的新工程都是默认开启这个功能的,可是你应该在你全部存在的工程内都开启这个功能。

    

Link Frameworks Automatically选项能够用来开启或者关闭自动链接框架的功能,就像描述的那么简单。仍是有一点缘由的为何你会想要关闭这个功能。

    

一旦Modules功能开启,你就能够在你的代码中使用它了。像这样作,对之前用到的语法有一点小小的改动。用@import代替#import:

@import UIKit;   @import MapKit;   @import iAd;

只导入一个框架中你须要的部分也是可能的。例如你只想要导入UIView,你就这样写:

@import UIKit.UIView;

对的-他真的是这么简单,技术上,你不须要把全部的#import都换成@import ,由于编译器会隐式的转换他们。然而尽量的用新的语法仍是好的习惯。

    

在你兴奋的要开始使用Modules以前,不幸的是有一个小警告。Xcode5的Modules还不支持你本身的或者第三方的框架。这是一个不幸的缺点,没有事情是完美的,即便是Objective-C!

 

2.新的返回类型-instancetype   

Objective-C添加了一个新的返回类型,名字叫instancetype。这个仅仅被用做Objective-C方法的返回类型和对编译器的一个暗示,暗示方法的返回类型将是这个方法属于的类的实例。

 

注意事项: 这个特征在iOS7和Xcode上没有严格,可是随着时间的推移会被悄悄的加进最近的Clang。然而Xcode5第一次声明苹果已经在他们的框架中使用了这个。你能够再官方的Clang网页看到更多的内容:http://clang.llvm.org/docs/LanguageExtensions.html#objective-c-features

    

为何要使用instancetype呢?看看下面的代码:

NSDictionary *d = [NSArray arrayWithObjects:@(1), @(2), nil];   NSLog(@"%i", d.count);

虽然这个明显是不正确的,可是编译器却不会提醒你任何错误。本身尝试一下在Xcode4.6下编译。你将看到没有任何警告,可是这段代码明显是错误的。这段代码甚至可以没有异常的跑起来,由于NSDictory和NSArray的实例都能相应count。

     

这段代码正常的缘由是因为Objective-C的强大的动态特性。这个类型是对编译器的一个指导。Count方法在运行的时候被查找不管什么类,正好dictionary变量有这个方法。在这种状况下,count方法存在,编译器相信他是正确的。然而稍后你用到了NSDictionary有而NSArray没有的方法例如objectAtIndex:就会出现问题。首先他不会明确指出问题出如今哪里。

     

可是问什么编译器没有指出 +[NSArray arrayWithObjects:]方法返回的实例不是NSDictionary实例呢?那是由于这个方法声明以下:

+ (id)arrayWithObjects:(id)firstObj, ...;

注意到返回类型是id。id类型是一个意味着任何Objective-C类的umbrella类型。他甚至都不是NSObject的子类。方法没有返回类型信息而不是返回Objective-C类的实例。这样作是有用的,当你隐式的转换id到一个确切的类型时编译器不会警告你。例如上面的NSDictionary例子。若是产生警告,id就没有用啦。

    

可是这个方法的返回类型为何是id呢?你能够子类化这个方法而后仍然没有问题的使用它。为了证实为何,考虑下面的NSArray的子类:

@interface MyArray : NSArray   @end

如今考虑下你的子类在下面的代码的使用:

MyArray *array = [MyArray arrayWithObjects:@(1), @(2), nil];

如今你应该知道为何arrayWithObjects:返回类型必须是id。若是是NSArray*,这个子类须要转化成必要的类。这就是新的instancetype返回类型用到的地方。若是你看iOS7SDK中NSArray的头文件,你将注意到这个方法变成了下面的样子:

+ (instancetype)arrayWithObjects:(id)firstObj, ...;

惟一的不一样就是返回类型。新的返回类型提示编译器返回类型是方法被调用的类的实例。因此当arrayWithObjects:被调用的是NSArray时,返回类型是NSArray*。当调用的是MyArray时,返回类型是MyArray*。

    

当维护成功子类化的能力的时候,用id就会出现的问题。若是用Xcode5编译原始的代码,你会看到下面的警告:

warning: incompatible pointer types initializing 'NSDictionary *' with an expression of type 'NSArray *' [-Wincompatible-pointer-types]   NSDictionary *d = [NSArray arrayWithObjects:@(1), @(2), nil];

那是有帮助的,如今你有机会修改这个问题以防止接下来crash。

    

初始化方法是候选要使用这个新的返回类型的。如今若是你设置初始化方法返回一个不完整的类型编译器已经提醒你了。可是他可能隐式的转化id到instancetype。你应该仍然使用instancetype,由于明确一点仍是比较好的。

    

尽量多的使用instancetype,他会成为Apple的标准-你不会知道这个将减小你多少你未来的degugging的痛苦时间。

 

3.新的 Foundations

    

接下来就是Objective-C核心开发框架Foundation的一些新东西。没有Foundation很难开发Objective-C应用,全部的iOS Apps都须要使用。在新的iOS SDK中看看这些新添加的内容。

    

Foundation最主要的提高是网络。(说的应该是NSURLSession)在iOS 7 by Tutorials 有一整章描述。(略)

    

文章剩下部分展现了Foundation新增长的和改变的东西。

 

3.1 NSArray

尝试在NSArray实例中访问一个Object,若是下表越界将爆出异常。当你用数组当作队列的时候,你可能常常要访问数组中第一个或者最后一个元素。 在先进先出队列(FIFO)你可能要从数组的前端POP元素,若是是先进后出队列(FILO)就要从数组末尾POP元素。

    

然而,当你访问数组的第一个或者最后一个元素的时候,你必定要肯定没有超出数组的边界,若是数组是空得话常常发生这样的访问。这就会致使在调用objectAtIndex:不报错而产生冗余的代码,就像下面的这样:

NSMutableArray *queue = [NSMutableArray new];   // ...   if (queue.count > 0) {       id firstObject = [queue objectAtIndex:0];       // Use firstObject   }   // ...   if (queue.count > 0) {       id lastObject = [queue objectAtIndex:(queue.count - 1)];       // Use lastObject   }

 

要访问最后一个元素,你应该会用到NSArray的这个方法:

- (id)lastObject;

Objective-C开发者应该能够高兴了,如今他们有了一个方法来访问数组的第一个元素:

- (id)firstObject;

简单的方法老是被证实是有用的。你不在须要检查数组是否是空的啦。你可能曾经遇到过因为越界产生的Crash。你能够看看下面的注意事项:

 

注意事项:若是你仔细的看NSArray头文件,其实firstObject在iOS4.0就已经出现啦,直到iOS7才对外开放。所以你能够在iOS7以前获取这个方法,可是你必须在你本身的头文件里声明这个方法firstObject来告诉编译器它确实存在。这不是一个提倡的方法,好歹Apple把这个方法公开了。

    

先前的代码能够用这两个方法重写,就不用检查数组长度了,以下:

NSMutableArray *queue = [NSMutableArray new];   // ...   id firstObject = [queue firstObject];   // Use firstObject   id lastObject = [queue lastObject];   // Use lastObject

3.2 NSData  

Data是你编程处理最多的事情。NSData是Foundation类,封装了原始字节并提供方法操纵这些字节,能够从一个文件读或者写数据。可是一个简单的任务Base64编码和解码尚未原生的实现。直到iOS7才出现。

    

Base64是一组二进制到文本转换的方案,以ASCII格式提供二进制数据。这些方案用来编码二进制数据以存储或者经过把多媒体文件转换成文本数据进行传输。这个能保证数据在传输过程当中的完整性。Base64编码的最多见的用途是处理电子邮件附件,或者编码小图片,这些小图片是经过基于Web的API返回的JSON相应的一部分。

    

在iOS7以前,Base64的 编码和解码是须要本身实现的或者使用第三方库。典型的Apple风格,如今是很是容易的使用这个功能。有四个Base64方法以下:

- (id)initWithBase64EncodedString:(NSString *)base64String          options:(NSDataBase64DecodingOptions)options;      - (NSString *)base64EncodedStringWithOptions:         (NSDataBase64EncodingOptions)options;       - (id)initWithBase64EncodedData:(NSData *)base64Data          options:(NSDataBase64DecodingOptions)options;       - (NSData *)base64EncodedDataWithOptions:         (NSDataBase64EncodingOptions)options;

头两个方法是处理字符串的,后两个方法是处理UTF-8编码数据的。这两个成对的方法功能是同样的,可是有时候用其中一个比另外一个效率要高。若是你想要Base64编码字符串而后写进文件,你应该使用UTF-8编码数据的这对方法。另外一方面,若是你打算Base64编码字符串而后用作JSON,你应该使用另一对方法。若是你曾经实现过Base64编码方法,如今能够删除了,由于Apple已经帮你实现了。

 

3.3 NSTimer

NSTimers在apps中常常用来执行周期性任务。NSTimer虽然颇有用可是也会产生问题。当有几个定时器在用的时候,他们可能间断性的触发。这就是意味着CPU是间断性处于活动状态的。这样作是更加有效率的,当CPU换起的时候执行一些任务,而后进入睡眠状态。为了解决这个问题,Apple给NSTimer添加了一个容忍属性来适应这种行为。

     

容忍提供系统一个指导在timer在计划以后容许延迟多长时间。为了减小CPU负荷底层系统将要集合这些活动。新属性的方法是:

- (NSTimeInterval)tolerance;   - (void)setTolerance:(NSTimeInterval)tolerance;

你可能永远都不须要用到这个属性,可是当你在很是密切相近的触发了几个定时器,你可能发现他是有用的,当你在用Instruments检测CPU使用率的时候。

 

3.3 NSProgress

不常常见到Foundation会完整的添加一个新类。他是一个稳定的框架。主要是由于不常常用到核心的类。然而iOS7提供了一个完整的新类NSProgress。

    

本质上,NSProgress是用来经过Objective-C代码产生进度报告的,分离每个独立模块的进度。例如,你能够在一些数据上执行几个不一样的任务,而后每一个任务能够管理他本身的进度而后报告给他的父任务。

 

3.3.1NSProgress结构

NSProgress最简单的使用方法是报告一些任务集合的进度。例如,你有10个任务执行,当每一个任务完成的时候你能够报告进度。当有一个任务完成的时候进度增长%10。而后在NSProgress的实例上使用Key Value Observing(KVO),你可以了解到这个实例的进度。你可使用这个通知来更新进度条或者显示一个指示文字。

    

NSProgress有更多的用途。Apple经过这个父子类的关系结构使他更增强大。NSProgress的结构更像是网状树。每个NSProgress有一个父类和多个子类。每个实例有一个执行的工做单元的总数,当前任务会处理完成的子任务数的更新来反馈当前状态。这么作的话,父类也会被通知进度。

    

为了减小NSProgress实例的传递,每一个线程有本身的NSProgress实例而后子实例能够直接从这个实例建立。没有这个功能,每一个想要报告进度的任务不得不经过参数的方式来通知。

 

3.3.2报告进度

NSProgress使用很是简单。如下面的方法开始:

+(NSProgress *)progressWithTotalUnitCount:(int64_t)unitCount;

这个方法建立了一个NSProgress实例做为当前实例的子类,以要执行的任务单元总数来初始化。例如,若是任务是循环一个数组,而后你可能用数组数来初始化NSProgress实例。例如:

NSArray*array = /* ... */;       NSProgress*progress =       [NSProgressprogressWithTotalUnitCount:array.count];       [arrayenumerateObjectsUsingBlock:       ^(id obj, NSUInteger idx, BOOL *stop) {           // Perform an expensive operation onobj           progress.completedUnitCount = idx;       }];

 

随着迭代的进行,上面的代码会更新NSProgress实例来反映当前进度。

 

3.3.3接收进度更新 

你能够经过下面的属性在任什么时候候获取任务进度:

@property(readonly) double fractionCompleted;

返回值是0到1,显示了任务的总体进度。当没有子实例的话,fractionCompleted就是简单的完成任务数除以总得任务数。

    

Key Value Observing(KVO)是最好的方法来获取fractionCompleted值得变化。这么作很是简单。你只须要作的是添加一个NSProgress的fractionCompleted属性的观察者。像下面这样:

[_progressaddObserver:self              forKeyPath:@"fractionCompleted"                 options:NSKeyValueObservingOptionNew                  context:NULL];

 而后覆盖KVO的这个方法来获取改变:

-(void)observeValueForKeyPath:(NSString *)keyPath                         ofObject:(id)object                           change:(NSDictionary*)change                          context:(void *)context   {       if (object == _progress) {           // Handle new fractionCompleted value           return;       }           // Always call super, incase it uses KVOalso       [super observeValueForKeyPath:keyPath                            ofObject:object                              change:change                             context:context];   }

在这个方法中你能够获取fractionCompleted的值的改变。例如你可能改变进度条或者提示文字。

    

固然,当你处理完的时候记得注销KVO是很重要的。

[_progressremoveObserver:self                 forKeyPath:@"fractionCompleted"                     context:NULL];

你必须老是要注销的,若是你没有注销,当被注册的Object释放的时候就会Crash。因此若是必要的话在dealloc中注销做为最后的保障。

 

4.WhereTo Go From Here(之后该怎么办)

     (略)

 

5.原文连接和参考连接

原文连接:http://www.raywenderlich.com/49850/whats-new-in-objective-c-and-foundation-in-ios-7

参考连接:

http://www.onevcat.com/2013/06/new-in-xcode5-and-objc/

http://www.longyiqi.com/blog/programming-languages/2012/04/05/at-import-objc/

相关文章
相关标签/搜索