转载地址:http://www.baidu.com/link?url=dPuMwKqDz2R0ggyWdCkWyBz4vKbBLxBHSJBsYzja9HoIIVpWG6Thq7bTfy3uMFO0
前言
这些年以来,随着Objective-C语言的不断发展和进化。尽管这门语言的核心概念和实践都保持不变,但是这其中的一部分已经有了显著的提升和改变。为了让我们更加容易的写出正确的代码,现代Objective-C语言提升了类型安全、内存管理、性能以及一些其他的方面。因此,在我们现在的以及将来的项目中采用这些新的改变让我们的代码变得更加一致、更加可读、更加可维护。
Xcode提供了一个工具来帮助我们做出这些结构上的改变。但是在使用这个工具之前,我们需要知道Xcode对我们的代码做出了什么样改变,以及它为什么要做出这样的改变。这篇文章会突出介绍如何在我们的代码中采用一些最有效、最有用的现代Objective-C编程方法。
instancetype
使用关键字instancetype
作为需要返回一个类(或者这个类的子类)实例的方法的返回值类型。这些方法包括alloc
,init
,以及类工厂方法。
在合适的场合使用instancetype
代替id
类型来提高你的Objective-C代码的类型安全。例如,看一下下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // .h
@interface MyObject : NSObject
+ (instancetype)factoryMethodA;
+ (id)factoryMethodB;
@end
// .m
@implementation MyObject
+ (instancetype)factoryMethodA { return [[[self class] alloc] init]; }
+ (id)factoryMethodB { return [[[self class] alloc] init]; }
@end
// 调用
void doSomething() {
NSUInteger x, y;
x = [[MyObject factoryMethodA] count]; // Return type of +factoryMethodA is taken to be "MyObject *"
y = [[MyObject factoryMethodB] count]; // Return type of +factoryMethodB is "id"
}
|
由于类方法+factoryMethodA
的返回值类型是instancetype
,这个类型的消息表现为MyObject *
。由于MyObject
没有-count
方法,编译器报了一个关于x
行的警告:
1
| main.m: ’MyObject’ may not respond to ‘count’
|
然而,类方法+factoryMethodB
的返回值值类型为id
类型,编译器就不会报关于y
行的警告啦。因为一个id
类型的对象可以是任何类,同时,一个叫做-count
的方法总是存在于一些类的某个地方,因此,类方法+factoryMethodB
的返回值来实现这个方法对于编译器来说是可能的。
为了使instancetype
类工厂方法能够拥有正确的子类化行为,确保我们在分配类时使用[self class]
而不是直接使用类名。遵循这个约定以确保编译器能够正确的推断出子类的类型。例如:子类化上一个例子中的MyObject
:
1
2
3
4
5
| @interface MyObjectSubclass : MyObject
@end
void doSomethingElse() {
NSString *aString = [MyObjectSubclass factoryMethodA];
}
|
对于这段代码编译器给出了如下的警告:
1
| main.m: Incompatible pointer types initializing ’NSString *’ with an expression of type ’MyObjectSubclass *’
|
在这个例子中,+factoryMethodA
消息发送后返回了一个MyObjectSubclass
类型的对象,是消息接收者类型的对象。编译器因此会相应的认为+factoryMethodA
方法的返回值应该是MyObjectSubclass
的子类,而不是其工厂方法中声明的那个父类。
如何采用?
在你的代码中,用instancetype
类型恰当的代替出现id
返回值类型的地方。比较典型的就是init
方法和类工厂方法。尽管编译器会自动的将以“alloc”,“init”,“new”开头的方法的返回值类型由id
转为instancetype
,但是却不会为其他的方法进行自动转换。Objective-C的约定是为所有的方法明确的使用instancetype
返回值类型。
请注意,你应该只是对于返回值类型用instancetype
替换id
,在其他处的代码是不可以的。与id
类型不同的是,关键字instancetype
只能用于方法声明中的返回值类型。
例如:
1
2
3
| @interface MyObject
- (id)myFactoryMethod;
@end
|
应该转变成:
1
2
3
| @interface MyObject
- (instancetype)myFactoryMethod;
@end
|
Properties
Objective-C的property是使用@property
语法声明的公有方法或者私有方法。
1
| @property (readonly, getter=isBlue) BOOL blue;
|
Properties
捕获对象的状态。它能够表达出对象的内在的属性以及和其他对象之间的关系。Properties
提供了一个安全、方便的方式与这些属性进行交互而不用自己写一组自定义的存取方法(尽管属性允许我们在需要的时候自定义getters
和setters
)。
在越来越多的场合下使用属性代替实例变量有以下的益处:
- 自动合成
getters
和setters
。当你声明一个property
,会默认自动为你创建getter
和setter
方法。 - 意图表达更加清楚的一组方法。由于存取方法的命名规范,能够非常准确的表达
getter
和setter
方法正在做什么。 -
Property
关键字表达表达了一些关于行为的额外信息。属性为声明像assign
(vs copy
),weak
,atomic
(vs nonatomic
)等行为提供了可能。
属性方法遵循一个简单的命名规范。getter方法名为属性的名字(例如:date
),setter方法名位属性名带上set前缀,采用驼峰写法(例如:setDate
)。布尔值属性的命名约定是的getter方法是以“is”开头的:
1
| @property (readonly, getter=isBlue) BOOL blue;
|
因此,下面的每一个方法都是能够工作的:
1
2
3
| if (color.blue) { }
if (color.isBlue) { }
if ([color isBlue]) { }
|
当想要决定什么可以是property
时,一定要记住,下面的这些情况不能为属性:
-
init
方法 -
copy
方法,mutableCopy
方法 - 类工厂方法
- 方法启动了一个操作,同时返回一个
Bool
值 - 方法明确改变了内部状态作为一个getter的副作用。
此外,在你的代码中识别潜在的属性时,需要考虑下面的这些原则:
- 一个read/write property 拥有两个存取方法。setter方法拥有一个参数,无返回值,getter方法没有参数,有一个返回值。如果你想转变这组方法称为property,需要标注为
readwrite
关键字。 - 一个read-only property只有一个存取方法,就是getter方法,无参数,有一个返回值。如果想要转变这个方法为property时,需要标注
readonly
关键字。 - getter方法应该是idempotent(如果getter方法被调用两次,第二次调用的结果和第一次调用的结果应该是相同的)。然而,对于getter方法是合理的去计算它每一次被调用的值。
如何采用?
识别出一组方法,使其有资格转化成一个property,比如:
1
2
| - (NSColor *)backgroundColor;
- (void)setBackgroundColor:(NSColor *)color;
|
使用@property
语法选择合理的关键字来声明成属性:
1
| @property (copy) NSColor *backgroundColor;
|
Enumeration Macros
NS_ENUM
和NS_OPTIONS
宏提供了一个方便、简单的方式来定义基于C语言的枚举和选择。这些宏在Xcode中改善了代码实现,明确指定枚举、选择的类型以及大小。除此之外,这种声明枚举的方式可以很好的兼容旧的的编译器,新的声明方法可以解释基础类型的信息。
使用NS_ENUM
宏来定义enumerations,一组不同的值的集合:
1
2
3
4
5
6
| typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
|
NS_ENUM
宏帮助定义枚举的名字和类型,UITableViewCellStyle
宏的类型为NSInteger
。枚举的类型应该是NSInteger
。
使用NS_OPTIONS
宏来定义options,一组结合在一起的位掩码:
1
2
3
4
5
6
7
8
9
| typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
|
像枚举一样,NS_OPTIONS
宏定义了名字和类型。然而,options的类型通常是NSUInteger
。
如何采用?
替代你的enum
声明,比如:
1
2
3
4
5
6
7
| enum {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
typedef NSInteger UITableViewCellStyle;
|
使用NS_ENUM
语法:
1
2
3
4
5
6
| typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
|
但是,当你使用enum
来定义一个位掩码(bitmask),比如:
1
2
3
4
5
6
7
8
9
10
| enum {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
typedef NSUInteger UIViewAutoresizing;
|
使用NS_OPTIONS
宏:
1
2
3
4
5
6
7
8
9
| typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
|
自动引用计数(ARC)
自动引用引用计数(ARC)是编译器特性,为Objective-C对象提供内存管理,不再需要记住什么时候应该使用retain
,release
,和autorelease
。ARC帮你管理对象的生存周期,在编译器帮你自动插入适当的内存管理代码,编译器也会在合适的时机帮你生成dealloc
方法。
如何采用?
Xcode提供了ARC自动转换工具。选择ARC迁移工具:选择:Edit > Refactor > Convert to Objective-C ARC。更多信息请查看:Transitioning to ARC Release Notes
相关链接
Adopting Modern Objective-C
前言
细细算来,我接触iOS已经有1.5f年的时间了,虽然其中有差不多一年的时间是在大四经历自学和实习的这个阶段。抛去那段时间不算,毕业后在现在的公司工作差不多半年了…
在经历过的几个项目上基本上每一个都会用到第三方开源库,比如SDWebImage
、AFNetworking
、MBProgressHUD
等。然而,每次把这些第三方库导入到我们的项目中要配置一些选项以及添加第三方库本身依赖的系统框架,这个工作是重复的而且非常没有技术含量。有没有什么工具能替代我们做这些工作呢?
一直到最近几天才知道,业界早已有了为iOS项目提供依赖管理的工具(我深深的感觉到我还没入行就先落伍了),这个工具就是:CocoaPods。
CocoaPods简介
CocoaPods是一个负责管理iOS项目中第三方开源库的工具。CocoaPods的项目源码在Github上管理。该项目开始于2011年8月12日,在这两年多的时间里,它持续保持活跃更新。开发iOS项目不可避免地要使用第三方开源库,CocoaPods的出现使得我们可以节省设置和更新第三方开源库的时间
在我们有了CocoaPods这个工具之后,只需要将用到的第三方开源库放到一个名为Podfile的文件中,然后在命令行执行$ pod install
命令。CocoaPods就会自动将这些第三方开源库的源码下载下来,并且为我的工程设置好相应的系统依赖和编译参数
CocoaPods的安装及使用
安装
安装的方式非常简单,Mac下已经自带了ruby,只要使用ruby的gem命令就可以安装了。打开的Mac的终端,在终端运行下面的命令:
1
2
| $ [sudo] gem install cocoapods
$ pod setup
|
说明:执行$ pod setup
这步可能比较慢,需要多等待一段时间,也可能是我网络的问题
更新
当然我们也可以更新我们的CocoaPods,同样也是使用ruby的gem命令:
1
| $ [sudo] gem update cocoapods
|
然而你也可以更新CocoaPods的预览版,执行下面的命令:
1
| $ [sudo] gem update cocoapods --pre
|
查找第三方库
如果我们不知道cocoaPods管理的库中,是否有你想要的库,那么你可以通过$ pod search xxx
命令进行查找,以下是我用$ pod search sdwebimage
查找到的所有可用的库:
1
2
3
4
5
6
7
8
9
10
11
12
| -> SDWebImage (3.5.1)
Asynchronous image downloader with cache support with an UIImageView
category.
pod 'SDWebImage', '~> 3.5.1'
- Homepage: https://github.com/rs/SDWebImage
- Source: https://github.com/rs/SDWebImage.git
- Versions: 3.5.1, 3.5, 3.4, 3.3, 3.2, 3.1, 3.0, 2.7.4, 2.7, 2.6, 2.5, 2.4
[master repo]
- Sub specs:
- SDWebImage/Core (3.5.1)
- SDWebImage/MapKit (3.5.1)
- SDWebImage/WebP (3.5.1)
|
注:我省略了两个库,没有全列出。
使用
假设我的Desktop上有一个已经存在的一个项目名称叫做:CocoaPodsTest,首先,进入项目的根目录,并在根目录下创建一个名叫Podfile的文件(没有任何后缀):
1
2
| $ cd Desktop/CocoaPodsTest/ '进入项目根目录,根据自己项目实际目录'
$ vim Podfile '创建Podfile文件,你可以选择你自己喜欢的编辑器'
|
注:vim的简单用法,$ vim fileName
创建文件fileName,并打开;按i
进入插入模式,输入文本;按esc
进入命令模式后,按:wq
或ZZ
退出并保存。
然后,在Podfile文件中按以下的格式将依赖库的名字列出:
1
2
3
| platform :ios, '6.0' '平台、版本'
pod 'SDWebImage', '~> 3.5.1' '开源库名称、版本'
pod 'AFNetworking', '~> 2.0.3' '开源库名称、版本'
|
保存Podfile文件后,执行如下安装的命令:
当安装命令执行成功后,会输出:
1
2
3
4
5
6
7
| Analyzing dependencies
Downloading dependencies
Installing AFNetworking (2.0.3)
Installing SDWebImage (3.5.1)
Generating Pods project
Integrating client project
[!] From now on use `CocoaPodsTest.xcworkspace`.
|
哈哈,看到类似这样的输出就是成功了。你所需要的第三方开源库都下载好了,并且设置好了相应的依赖以及编译参数。在我们以后用的时候一定要记住以下两点:
1. 最后一行是一个警告,提醒我们需要注意:从现在开始,需要通过xxx.xcworkspace
打开的我们的项目。而不是之前我们一直用的xxx.xcodeproj
2. 当我们每次修改了Podfile
这个文件后,一定要记得执行命令:$ pod install
,还可以执行$ pod update
来更新类库
总结
用CocoaPods给我们的iOS项目添加依赖库真的太方便了,几个命令就搞定了,我个人建议像我一样还不会使用CocoaPods进行项目依赖的初级开发者,尤其是像我这样刚毕业的本科生,这个工具有必要学会,不能被鄙视,更能提高效率
有很多iOS大牛早已写了关于cocoaPods的相关教程,我个人又参考各大牛的博客写了一遍,只为能增加使用CocoaPods的熟练度。如有造成侵权行为,请联系本人