IOS 琐碎
- ASIHTTPRequest
- AFNetWorking
- MKNetworkKit
个人以Google出来的信息得出的对比:
| 网络库 | 优点 | 缺点 |
| ———— | ————- | ———— |
| ASIHTTPRequest | 老牌、功能强大、文档丰富 | 停止更新、新特性少、厚重 |
| AFNetWorking | github上比较火的项目、有稳定的两个主要负责人、能支持比较新的特性、一直有更新 | 文档数目一般、有些功能貌似要自己写 |
| MKNetworkKit | 支持ARC、号称要有 ASIHTTPRequest的功能,AFNetWorking的轻便 | 文档数目最少、只有作者一个主要负责人 |
在结合我是一个新新手的缘由。如果用库的话,首选ASIHTTPRequest。 候选AFNetWorking。
AFNetWorking 被Google到这样的解决方法.本来准备fork了。然后仔细看了看下面的讨论。
觉得自己实现一个? 原理在这里
sunmmer大神给了一个不用库实现的例子
后面想了想,对于网络其实我也是新手来的。自己写,未必有成熟的库写的好。所以决定使用ASIHTTPRequest。
定义类
大部分面向对象编程都是由编写新类组成。在Objective-C中定义类有两种途径:
- 接口声明类的方法和实例变量并且声明它的父类
- 类的实现编写代码来实现类的各个方法
这两个部分通常都定义在它自己的文件里。但是有时一个类的定义可以通过使用一种叫做类别的功能跨越多个文件。类别可以把类定义分成几个独立的部分或者对一个存在的类定义进行扩展。类别会在“Categories and Extensions.”中进行介绍。
源文件
类的接口和实现通常放在两个不同的文件里,尽管编译器并不需要这样。接口文件必须对类的使用者有效。
一个文件可以声明或实现多个类。不过通常每一个类都有一个单独的接口文件,不然的话也会有一个独立的实现文件。保证类的接口文件独立分开可以更好的将他们作为各自独立的实体进行区分。
接口和实现文件通常在类之后命名。实现文件的文件名使用.m作为扩展名来标明它包含了Objective-C源代码。接口文件可以被分配其他任何扩展名。因为他们被包含在其他的源文件中,接口文件的文件名通常以.h作为扩展名来标明它是一个头文件。例如:类Rectangle 会在Rectangle.h中进行声明并且在 Rectangle.m中定义它的具体实现。
把一个对象的接口和实现分离开更好的服从了面向对象程序设计。对象是一个独立的实体对于外界来看就像是一个黑盒。一旦你决定了一个对象如何同程序中的其他元素相互作用—也就是说一旦你声明了它的接口那么你就可以随意的改变它的实现而不会影响到程序中的其他部分。
类的接口
接口的定义以编译指令@interface 作为开始并且以指令@end作为结束。(所有Objective-C的编译指令都是以“@”开始)
@interface ClassName : ItsSuperclass
{
instance variable declarations
}
method declarations
@end
第一行声明了一个新的类名并且链接到它的父类。父类定义了新类在继承体系中的位置。如果没有冒号和父类类名,那么新类就声明为一个根类,也就是和NSObject 同等的类。
紧跟着第一部分声明之后,由花括号括起来的部分声明实例变量,也就是声明每个类的实例的数据结构。下面是Rectangle 类的实例变量声明的一部分:
float width;
float height;
BOOL filled;
NSColor *fillColor;
紧接着下面是方法的声明,在花括号所括的实例变量和@end之间。类方法(只有类对象可以使用的方法)的方法名以加号(+)开头:
+ alloc;
实例方法(类的实例可以使用的方法)的方法名以减号(-)开头:
- (void)display;
你可以使用相同的名字定义一个类方法和一个实例方法,虽然这不是一个常规用法。一个方法同样可以和一个实例变量名字相同,这种方式会被经常用到,特别是当一个方法用于返回一个实例变量的值。例如:Circle 有一个radius 方法来匹配radius 实例变量。
方法返回值的类型声明使用标准C的语法:
- (float)radius;
参数类型也是用同样的方法声明:
- (void)setRadius:(float)aRadius;
如果返回值或参数的类型没有明确指定,那么它默认为id类型。 上面例子中的alloc 方法就返回id类型。
当有多个参数时,参数在方法名之后接冒号进行定义。多个参数之间由空格隔开,就和消息中传递多个参数的定义是一样的。例如:
- (void)setWidth:(float)width height:(float)height;
如果方法的参数个数是可变的则使用一个逗号后接省略号来定义,如下:
- makeGroup:group, ...;
引用接口
那些使用接口类的模块必须要在代码中引用接口文件, 接口文件中包含一些模块用于创建类的实例,声明类中应有的方法,或者声明一个类中应有的实例变量。接口通常由#import 指令引用:
#import "Rectangle.h"
#import 命令和#include功能是相同的,有一点例外就是#import 命令能够确保同一个文件不会被重复引用。因此在这个文档的例子中使用#import 代替#include。
这里要考虑一个问题就是一个类是建立在所继承的类之上的,因此一个接口首先需要引用它的父类的接口:
#import "ItsSuperclass.h"
@interface ClassName : ItsSuperclass
{
instance variable declarations
}
method declarations
@end
这就意味着每一个接口文件都间接的包括所有所继承类的接口文件。当一个原始模块引用了一个接口,那么它就使所有在这个继承体系上建立起来的类都引用了这个接口。
注意,如果有一个precomp—— 一个预编译头,那么应该先引用这个预编译头。
引用其他类
接口文件声明一个类同时引入它的父类也就暗含着包含了所继承的类中所声明的信息(从NSObject 类向下一直到它的父类)。如果接口使用的类不在继承体系中,它就必须明确的引入这些类或者使用@class 指令声明他们。
@class Rectangle, Circle;
@class 指令只是简单的通知编译器“Rectangle” 和 “Circle”是类名。它并不会引入他们的接口文件。
接口文件使用类名为实例变量、返回值、参数静态指的定类型。例如:
- (void)setPrimaryColor:(NSColor *)aColor;
使用了NSColor 这个类名。
像这样的声明中仅使用类名作为一个类型并不需要知道这个类接口的细节(例如它有什么方法和实例变量),因此@class 指令已经可以为编译器提供足够的信息进行预先校验(例如类型校验)。但是当一个接口是被真正使用(需要实例这个类、向类对象发送消息)的时候,接口文件就必须要被引入。通常情况下接口文件仅使用@class 命令声明类,而相对应的实现文件才引入他们的接口(因为它需要去实例这些类或者向这些类发送消息)。
@class 指令最大限度的减少了编译器和连接器所见的代码,因此这也是预先声明一个类名的最简单的方法。同时很自然 的避免了一个潜在的问题就是当引用文件的时候这些文件同时还引用了其他文件。例如,如果一个类A声明了一个类B类型的实例变量同时它们两个的接口文件又互 相引用,那么他们都不能正确的编译。
接口的作用
使用接口文件的目的是声明一个新类提供给其他源模块(或是其他的程序员)。它包含了如何使用这个新类的所有信息。(当然程序员同时更希望有一些文档)
- 接口文件告诉使用者这个类是如何同继承关系联系起来的,同时还告诉使用者这个类继承或引用了什么其他的类。
- 接口文件通知编译器这个类的对象应该包含哪些实例变量,同时告诉程序员这个类的子类能够继承到什么变量。虽然实例变量在类的实现中浏览比接口中更 自然,但是他们必须在接口文件中声明。这样声明是必须的,因为编译器必须在某一个对象使用的时候知道它的结构而不是在它定义的时候。作为一个程序员通常可 以忽略你所使用的类的实例变量,除非在你定义一个子类的时候。
- 最后,通过接口声明方法的列表,接口文件可以通知其他模块该类的类对象和实例能够接收什么消息。每一个可以从外部调用的方法都需要在接口文件中声明,仅供类内部调用的私有方法可以不在接口文件中声明。
类的实现
类的定义和它的声明结构非常相似。它是以@implementation指令开头并以 @end 指令结尾:
@implementation ClassName : ItsSuperclass
{
instance variable declarations
}
method definitions
@end
然而,每一个实现文件必须引用它的接口文件。例如Rectangle.m 要引用 Rectangle.h。因为类的实现不需要重新声明这个类,类的实现可以省略以下部分:
- 父类的名字。
- 实例变量的声明。
引用接口文件使实现更简明并且使它主要精力都集中在方法的定义上:
#import "ClassName.h"
@implementation ClassName
method definitions
@end
方法的定义和C语言函数一样,使用一对花括号。括号前面的部分和接口中的声明方法相同只不过没有分号。例如:
+ (id)alloc
{
...
}
- (BOOL)isFilled
{
...
}
- (void)setFilled:(BOOL)flag
{
...
}
参数个数可变的方法使用一个函数来处理参数:
#import
...
- getGroup:group, ...
{
va_list ap;
va_start(ap, group);
...
}
实例变量的引用
默认情况下,定义的实例方法可以引用对象中所有的实例变量。它可以通过变量名来引用变量。虽然译器会创建一个和C语言中结构体相同的结构来存储实例变量,但是实际上这种结构是隐藏的。你不需要任何结构体操作符(. 或者->)去引用对象的变量。例如,下面这个方法中引用了这个类的实例变量filled :
- (void)setFilled:(BOOL)flag
{
filled = flag;
...
}
虽然这个类的对象和它的filled 实例变量都没有定义为这个方法的参数,但是这个实例变量仍然在方法的引用范围之内。这种简化的语法在Objective-C编程中非常的重要。
当实例变量属于一个不是当前类的对象时,这个对象必须明确的指定一个类型。引用一个固定类型对象的实例变量时需要使用指针操作符(->)。
例如,假设有一个类Sibling 声明了一个固定类型的对象twin,作为一个实例变量:
@interface Sibling : NSObject
{
Sibling *twin;
int gender;
struct features *appearance;
}
如果一个固定类型对象的实例变量在一个类的调用范围之内(这个例子中是因为twin 的类型和当前类是相同的所以可以调用),Sibling的方法可以直接为他们赋值:
- makeIdenticalTwin
{
if ( !twin ) {
twin = [[Sibling alloc] init];
twin->gender = gender;
twin->appearance = appearance;
}
return twin;
}
实例变量的调用范围
虽然实例变量在类接口中声明,但是它更多的用于一个类的具体实现。一个对象的接口主要是用于定义类的方法而不是它的数据结构。
通常一个实例变量会有一个方法与之相对应,例如:
- (BOOL)isFilled
{
return filled;
}
但是这不是必须的,有些方法的返回值也不会存储在实例变量中,同时一些用于存储数据的实例变量也不希望被外部访问。
一个类会经常的变化,即使它声明的方法保持不变实例变量也有可能会发生变化。由于消息连接了各个类的实例,这些变化并不会对它的接口产生影响。
为了使对象有能力隐藏它的数据,编译器限制了实例变量的访问范围,也就是限制了程序对它们可见性。但是同时提供了一些机动性,我们可以指定可见范围分为4个等级。每个等级都有一个编译指令:
|
指令 |
含义 |
|
@private |
变量只限于声明它的类访问。 |
|
@protected |
变量可以被声明它的类以及继承该类的类使用。所有没有明确指定访问范围的变量默认为@protected |
|
@public |
变量可以在任何位置访问。 |
|
@package |
类似于java中包的概念,可以把变量的访问范围控制在一个范围内例如一个framework。 |
Figure 2-1 此图说明了变量的访问范围
一条访问范围的指令可以作用于下一条指令之前所有定义的变量。在下面例子中age 和evaluation是private, name, job, 和 wage是protected; boss是public.
@interface Worker : NSObject
{
char *name;
@private
int age;
char *evaluation;
@protected
id job;
float wage;
@public
id boss;
}
没有指定访问范围的(例如name)默认为@protected
类中声明的变量无论如何定义访问范围都可以被该类内定义的方法使用。例如上例中Worker 类中有一个job变量,该变量就可以被下面这样一个方法使用,如果一个类不能访问自己的实例变量那么很显然这个变量无论如何也不会被使用了。
- promoteTo:newPosition
{
id old = job;
job = newPosition;
return old;
}
通常,一个类可以访问它父类的实例变量。在变量继承的同时也继承了被引用的能力。很显然每个类都有他们完整的数据结构,特别是当从父类继承了一个非常巧妙的数据结构时这就显得非常实用了。所以在上面例子中的promoteTo:方法定义在Worker 类中或者其子类中都可以访问job。
不过,由于下面一些原因需要限制子类对实例变量的直接访问:
- 一旦一个子类访问了父类的实例变量,这个父类也就和子类的实现捆绑到一起了。在后续的处理中这个变量不能被销毁或者改变用途否则稍有疏忽就会破坏子类。
- 并且,如果一个子类访问父类的变量并改变了变量的值,就有可能会使父类产生一个bug,特别是当这个变量涉及类的内部处理时。
所以要限制变量只能在类内访问时就必须将它设置为 @private。这样,如果要访问这个变量就只能通过调用一个公共的方法(get/set方法)。
另外,@public变量可以在任何地方访问。通常情况下其他对象如果想要获得变量的值必须要发送一个消息来请求,但是一个公共变量就可以在任何位置直接访问了。例如:
Worker *ceo = [[Worker alloc] init];
ceo->boss = nil;
需要注意一点对象需要先明确的指定类型才可以调用它的公共变量。
@public变量使对象无法隐藏它的数据。这样就违背了面向对象编程的基本原则——将数据封装在对象中防止被随意浏览和由疏忽引起的错误。因此公共变量应该尽量避免是用,除非有特殊的目的。
向自己和父类发送消息
Objective-C提供了两个途径在对象的方法中指向该对象来执行它的方法——self 和super。
假设你定义了一个方法reposition用于改变对象显示的坐标。它可以通过调用setOrigin::方法来实现这个功能,它需要做的就是向自身发送一个 setOrigin::消息。当写这部分代码时,就可以使用self 或 super来引用这个对象。reposition方法可以如下:
- reposition
{
...
[self setOrigin:someX :someY];
...
}
或者:
- reposition
{
...
[super setOrigin:someX :someY];
...
}
在这里,self和super 都引用这个对象来接收一个reposition消息。但是这两个方式是截然不同的,.self是消息分发程序传递给每个方法的一个隐藏的参数,他是一个局部变量可以像变量名一样在方法中使用。. Super仅仅在作为一个消息表达式中的接收者时来才用来代替self。作为接收者,这两种方法主要不同在于对发送消息的处理上:
- self 是从接收对象的消息分发表中查找方法。上面例子中当前类会首先接收到reposition消息并从当前类开始执行。
- Super开始搜索方法的位置是截然不同的,它是从super 出现的方法的父类开始查找方法。上面例子中会从父类中定义的reposition开始执行。
每当super接受到一个消息时,编译器会用另一个消息分发程序替代objc_msgSend函数。这个替代的分发程序直接从父类开始查找,也就是向super发送消息的类的父类而不是它自身。
一个使用self和super的例子
我们使用一个继承体系中的3个类来说明 self 和 super的区别。假设我们创建一个类Low的对象。类Low 的父类为Mid,同时类Mid的父类是High。这3个类同时都定义了negotiate方法为他们自己所用。另外Mid类定义了一个自己的方法makeLastingPeace,并调用了negotiate 方法。类和方法如图2-2中说明。
Figure 2-2 The hierarchy of High, Mid, and Low
假设makeLastingPeace 方法使用self 向对象发送了一个negotiate 消息:
- makeLastingPeace
{
[self negotiate];
...
}
当向Low 发送一个makeLastingPeace 消息,makeLastingPeace 方法会向相同的Low 对象发送一个negotiate 消息。然后消息转发程序会找到Low中定义的negotiate 方法并执行。
然而,如果makeLastingPeace 如下使用super 作为接收者,
- makeLastingPeace
{
[super negotiate];
...
}
消息转发程序会找到High类中定义的negotiate 方法来执行。它会忽略Low而跳到Mid的父类(High),因为makeLastingPeace 是在Mid中定义的。它不会找到Mid 中定义的negotiate
如例所示,super 提供了一个方法以跳过一个复写的方法。在这里使用super 使得makeLastingPeace 绕过了Mid 中定义的negotiate 方法而调用它所复写的方法(High 中的negotiate)。
如我们刚刚所说的这种情况,Mid 中的negotiate方法是不能够被调到的,这看上去像是一个错误,但是这样做是有目的的:
- 类Low 有意复写了Mid 中的negotiate 方法所以Low 的实例(以及它的子类)会调用这个被重新定义的方法。也就是说Low 的设计者不希望Low 的对象运行它继承的方法。
- Mid 的方法makeLastingPeace的设计者向super 发送negotiate 消息(第二种实现方法),这样就有意的跳过了Mid 的negotiate 方法(同时也跳过了在它的子类中所定义的negotiate 方法)以执行 High中所定义的方法。这样设计就是为了使用父类而非其他类中的negotiate 方法。
Mid 的negotiate 方法仍然会被用到,但是那需要直接向Mid 实例发送消息。
Super的使用
使用向super 发送消息可以使方法的实现分布在多个类中。你可以通过复写一个现有的方法来修改或扩展它的功能:
- negotiate
{
...
return [super negotiate];
}
对于某些任务,在继承关系中的个各类可以实现同一个方法各自完成工作的一部分,同时向super 发送消息以完成其余工作。init方法,这个方法会初始化最新分配的实例。每个Init方法都负责初始化它的类中所定义的实例变量。但是在这之前他会先向父类发送一个init 消息以使它继承的类先进行初始化。每个init 方法都会按照这个步骤处理,所以类会按照继承顺序来初始化它的实例变量:
- (id)init
{
self = [super init];
if (self) {
...
}
}
Initializer methods have some additional constraints; they are described in more detail in “Allocating and Initializing Objects.”
这样还可以使超类中定义的方法专注于核心的功能,然后子类通过向super发送消息来包含这些功能。例如,每个类创建一个实例都必须为新对象分配内存空间并初始化它的isa变量 。分配内存一般使用NSObject 中定义的alloc 和allocWithZone: 方法。如果其他类复写了这两个方法(很少这样做),它仍可以通过super使用这个基本功能。
自身重定义
super 仅仅是一个通知编译器从何处开始查找方法的标志,它仅仅被用于消息的接收者。但是self 是一个变量名,可以被用在很多地方甚至可以被赋值。
类的方法通常并不关心类对象,而只关心类的实例。例如,许多类方法把一个实例的初始化和内存分配合并到一起,同时还经常要为实例变量设置初始值。在这样一个方法中它也想像一个实例方法那样,向最新分配内存的实例发送消息来调用实例的self 。但是这样做是错误的。 self 和super 都代表接收对象——这个对象获得消息来知道运行哪个方法。在一个实例方法中,self代表这个实例;但是在一个类方法中self 代表类对象。下面这个例子告诉我们什么不能做:
+ (Rectangle *)rectangleOfColor:(NSColor *) color
{
self = [[Rectangle alloc] init]; // BAD
[self setColor:color];
return [self autorelease];
}
为了避免混淆,通常在一个类方法中使用一个变量来关联一个实例:
+ (id)rectangleOfColor:(NSColor *)color
{
id newInstance = [[Rectangle alloc] init]; // GOOD
[newInstance setColor:color];
return [newInstance autorelease];
}
实际上,在类方法中与其向类发送alloc 消息不如向self发送alloc 消息。这种情况下,如果这个类是一个子类,那么rectangleOfColor:消息就被这个子类接收,返回的实例也就是子类类型的(例如, NSArray 的array 方法是从 NSMutableArray继承而来的)
+ (id)rectangleOfColor:(NSColor *)color
{
id newInstance = [[self alloc] init]; // EXCELLENT
[newInstance setColor:color];
return [newInstance autorelease];
}
See “Allocating and Initializing Objects” for more information about object allocation.
欢迎交流讨论。转载请注明出处。本文地址: http://www.sjslibrary.com/?p=242

浙公网安备 33010602011771号