OC基础

OC基础编程技巧 程序员

正如它的名字所传达的含义,Foundation 框架是全部 iOS 和 Mac OS X 编程所使用的基本工具。要成为这两个平台上成功的开发者,必须对这套工具了如指掌。 编程

Foundation 框架定义了数量众多的类以及协议,它们各司其职。但三种类和协议的地位更加突出,它们是最基本的部分: 设计模式

  • 根类和相关的协议。 根类,即  NSObject ,还伴有一个同名的协议。它肯定了全部 Objective-C 对象的基本接口和行为。同时也有一些协议,其余类能够采用这些协议来拷贝这些类的实例并对编码它们的状态。
  • 数值类。 数值类可以产生一个实例(称为数值对象),也就是将字符串、数字、日期、二进制数据等基本类型数据封装起来的面向对象包装。
  • 群体类。 群体类的一个实例(称为群体)管理着一组对象。区分不一样类型的群体就要看访问它所包含的对象的方式是什么。一般,群体中包含的项目都是一系列数值对象。

群体和数值对象是 Objective-C 编程中极其重要的内容,由于它们常常被用做方法的参数和返回值。 数组

根类和 Objective-C 对象

在类继承中,根类不从其余类继承,同时全部其余的类都最终继承自根类。 NSObject  是 Objective-C 继承中的根类。其余类都从  NSObject  继承一套基本的接口到 Objective-C 运行时体系中。这些类的实例又都是从  NSObject  继承而得到 Objective-C 最根本的特性。 安全

但就其自身而言, NSObject  的实例作不了什么有趣的事,顶多只是个对象而已。要使用更多属性和逻辑来定制你的程序,就必须创造一个或多个继承自  NSObject  的类,或者使用已有的直接或间接继承自  NSObject  的类。 网络

NSObject  采用了  NSObject  的协议,它声明了一些附加方法,能够被全部对象的接口使用。另外, NSObject.h (包含了  NSObject  类定义的头文件)中包含  NSCopyingNSMutableCopying  和  NSCoding  协议。当某个类采用了这些协议后,它便得到了对象拷贝和对象编码的基本对象行为。模型类(封装了应用数据并管理这些数据的实例的类)常常采用对象拷贝和对象编码协议。 app

NSObject  类和相关协议定义了建立对象、浏览继承链、查阅对象的特征和功能、比较对象、拷贝对象和把对象进行编码等的一系列方法。本文接下来主要讲述的就是这类任务的基本要求。 框架

建立对象

一般,建立对象时,要先为它分配内存,而后将它初始化。虽然这是两个单独的步骤,但它们联系甚密。有些类能够经过调用它们的工厂方法来建立对象。 函数

建立对象 – 分配内存和初始化 工具

要为对象分配内存,对它的类发送一个  alloc  消息就能获得该类的一个“原始”(未初始化)的实例。当你为一个对象分配内存时,Objective-C 运行时会在应用的虚拟内存中为该对象预留足够大的内存空间。除了分配内存自己以外,这个环节还有另外几个用途,例如把实例变量所有设为 0 等。

为原始实例分配好内存以后,你必须将其初始化。初始化也就是将对象设置为初始状态,换句话说,就是让它的实例变量和属性为合理的值,而后再返回这个对象。初始化是为了保证返回的对象能够被使用。

你会发如今很多框架中都含有  initializers (初始器)方法,便可以初始化对象的方法。它们的形式大多相似。初始器是实例方法,方法开头为  init ,返回一个  id  类型的对象。根对象  NSObject  声明了  init  方法,全部其余的类都继承了这个方法。其余的类固然也能够声明本身的初始器,各自要有本身的关键字和参数类型。例如,NSURL 类声明了以下初始器:

- (id)initFileURLWithPath:(NSString *)path isDirectory:(BOOL)isDir

当你为一个对象分配内存并将其初始化的时候,能够将内存分配方法和初始化方法嵌套起来。若是使用上边这个初始器的话,能够写成这样:

NSURL *aURL = [[NSURL alloc] initFileURLWithPath:NSTemporaryDirectory() isDir:YES];

做为一种安全的编程习惯,你能够检查返回的对象以验证对象的建立是否正确。若是建立过程当中发生了意外而致使对象建立失败,初始器将返回  nil 。虽然 Objective-C 容许对  nil  发送消息而不会产生任何反作用(好比抛出异常),但你的代码显然不可能正常工做,由于没有任何方法可以被调用。你不该该使用  alloc  返回的实例,而要使用初始器返回的实例。

经过调用类的工厂方法来建立对象

经过调用类的工厂方法也能建立一个对象。工厂方法是一种类方法,它可以分配内存、初始化,并返回实例自身。类的工厂方法属于一种便捷方法,由于它们只需一步就能够建立对象,而不是上文讲过的两步。它们的形式是这样的:

+ ( type ) className … (这里的  类名称  不包含任何前缀)

Objective-C 框架中的类有些会定义一种工厂方法,这种工厂方法实际上起到了初始器的做用。好比, NSString  就声明了以下两种方法:

- (id)initWithFormat:(NSString *)format, …;

+ (id)stringWithFormat:(NSString *)format, …;

下边的例子就是  NSString  工厂方法的一种用法:

NSString *myString = [NSString stringWithFormat:@"Customer: %@", self.record.customerName];

用对象的术语来思考

在运行时,每一个应用都是一组互相协做的对象构成的;这些对象互相之间能够通讯,以完成应用所需的工做。每一个对象都有本身的角色,至少要对一件事 负责,而且至少链接一个其余对象。(孤立的对象毫无价值。)以下图所示,对象所组成的网络中既有框架对象也有应用程序对象。应用程序对象时自定义的子类的 实例,通常继承自某个父类框架。这些对象组成的网络通常被成为对象图。

app_as_object_network

你须要建立这些链接,或者关系,在各个对象之间进行引用。引用的语言形式有不少,其中有实例变量、全局变量,甚至包括(在有限的做用域内)本地 变量。而关系,能够是一对一关系,也能够是一对多关系,能够表示出一系列从属关系的概念。这些关系就是某个对象对其余对象进行访问、沟通或者控制的手段。 被引用的对象天然也就成了消息的接收者。

应用的对象间传递的消息是让应用持续工做的重要因素。比如乐团中的演奏家同样,应用中的每一个对象都有各自的角色,为应用的运行履行本身的这部分 职责。有的对象能够显示一个椭圆形的界面响应点按动做,有的会管理一些承载各类数据的数据集合,有的则控制整个应用生命周期内的各大事件。但为了完成它们 各自的任务,它们还必须可以互相交流。每一个对象都要有向同一应用中别的对象发送消息的能力,也要有接收别的对象发来的消息的能力。

有些对象之间紧密成对,即互相之间直接相连,它们互发消息时是很容易的。但还有些非紧密成对的对象,即在对象图中被分隔开的对象,它们之间要进 行通讯就要另想办法。Cocoa 和 Cocoa Touch 框架含有许多帮助非紧密成对的对象进行通讯的功能和机制(以下图所示)。这些机制和技术都创建在一些设计模式之上(咱们会在后面探讨),这样就使得应用更 加高效而且具备超强的可扩展性。

communication_loosely_coupled

管理对象图,避免内存泄漏

Objective-C 程序里的对象共同组成一张对象图:由各个对象和其余对象的关系(或引用)而造成的网络。对象之间的引用分为一对一和一对多(经过对象集合)引用。对象图十 分重要,由于它是使对象保持生命力的关键因素。编译器会检查对象图中引用的强弱,并根据须要保持对象发出或释放对象消息。

在 C 语言或 Objective-C 语言中,可使用含有全局变量、实例变量或本地变量的结构来构造对象间的引用。这些结构各自都有本身暗含的做用域。好比,本地变量引用的一个对象的做用域 就是声明它的函数块所在的位置。一样重要的是,对象间的引用也是分强弱的。强引用会指示出本身的全部者是谁;指向别人的对象拥有被指向的对象。弱引用则是 指向别人的对象和被指向的对象之间没有从属关系。对象的生命周期由它的强引用数量多少决定。只要对象有强引用关系,它就不会被释放。

Objective-C 里的引用默认都是强引用。一般来讲这很方便,让编译器管理对象的运行时生命周期,当你使用对象时它们不会被释放。可是若是粗心未做全面检查,对象间的强引 用可能会造成无限循环,以下图左边所示。这样的循环链在运行时会致使运行时不会释听任何一个对象,它们都有指向本身的强引用。继而,这样的死循环就形成了 内存泄露。

strong-ref-cycle-weak-ref

就图中的对象而言,若是你取消 A 和 B 之间的引用,则 B、C、D、E 构成的子对象图则“永远”不会从内存中释放,由于这些对象每个都有强引用,造成了一个死循环。若是在 E 和 B 之间引入弱引用,就能够打破强引用死循环了。

为了修正强引用死循环的问题,精明的程序员会使用弱引用。运行时会持续跟踪对象的弱引用。一旦对象再也不有强引用,运行时就会从释放该对象,并将全部指向该对象的引用改成  nil 。对变量来讲(全局、实例和本地变量),在对象名前面加上  __weak  限定词就能够将其标记为弱引用。对于属性来讲,可使用  weak  选项。在如下这几类引用中,你应该使用弱引用:

  • 委托

    @property(weak) id delegate;

    在《设计模式》篇里,“用设计模式让应用开发流水线化”教程将向你详解委托和目标机制。

  • 未被顶级对象引用的插座变量(Outlet)

    @property(weak) IBOutlet NSString *theName;

    插座变量是对象间的一种链接(或引用),被归档在故事版文件或 nib 文件中,当应用运行并载入故事版或 nib 文件时就会恢复插座变量。故事版或 nib 文件中顶级对象的插座变量通常而言是窗口、视图、视图控制器或其余控制器等,应该为  强引用 (默认的,或未标记的)。

  • 目标

    (void)setTarget:(id __weak)target

  • 块对象中指向  self  的引用

    __block typeof(self) tmpSelf = self;
    [self methodThatTakesABlock:^ {
        [tmpSelf doSomething];
    }];

    块对象会对它捕获的变量产生强引用。若是你在块对象里使用了  self ,则会对  self  产生强引用。因此,若是  self  对块对象也有强引用(一般都会这样),就造成了强引用死循环。为了不死循环,你须要在块对象的外面建立一个指向  self  的  (或  __block )引用,如上边的范例所示。

管理对象的可变性

可变对象是指在你建立后可以变动其状态的对象。通常来讲你须要使用属性或存取方法来进行改变。不可变对象则是建立后便被封装好,状态不可改变的对象。在 Objective-C 框架中建立的大部分类的实例都是可变的,但有几种是不可变的。不可变对象具备以下优势:

  • 在使用不可变对象时,不用担忧它的值会发生意外变化。
  • 对于许多类型的对象而言,不可变对象可以提高应用程序的性能。

在 Objective-C 框架中,不可变类的实例一般是封装起来的离散值或缓冲区值的集合,好比数组和字符串。这些类一般带有一个可变的衍生类,类名里多出“ Mutable ”(可变)一词。好比有一个  NSString  类(不可变)和  NSMutableString  类。须要注意的是,对于  NSNumber  或  NSDate  等封装了离散值的不可变对象,就不必存在可变的衍生类了。

若是你须要常常改变对象的内容,那么就使用可变对象,而不使用不可变对象。若是你从框架中接收到的对象是个不可变对象,请遵守返回的类型来行事,不要尝试改变对象的内容。

建立并使用值对象

值对象是指封装了(C 语言类型的)基本数据类型值的对象,并提供一系列与该值有关的功能。值对象在对象表中表明的是标量类型。Foundation 框架为你提供了下列类,用来生成字符串、二进制数据、日期和时间、数字等值对象:

  • NSString  和  NSMutableString
  • NSData  和  NSMutableData
  • NSDate
  • NSNumber
  • NSValue

值对象在 Objective-C 编程中十分重要,由于应用会把这些对象看成方法、函数的参数和返回值进行调用。经过传递值对象,框架里的各个部分甚至不一样的框架之间便可以交换数据。由于 值对象表明的是标量值,所以你能够在集合或者其余须要用到对象的地方使用它们。值对象除了有普通数据类型的相同特征和做为编程的必要成分以外,还有更大的 优点:你可以经过更加有效并且十分优雅的方式对这些封装起来的值进行操做。就拿  NSString  类来举例,它有搜索并替换字符串的方法,有写入字符串到文件或(更经常使用)到 URL 的方法,还有构建文件系统路径的方法等。

在有些场合中,你可能以为使用基本数据类型更加有效和直接,例如  int (整数型)、 float (浮点型)等等。举个具体的例子就是在计算某个值的时候。因此  NSNumber  和  NSValue  对象不多被看成框架中方法的参数和返回值。然而,须要注意到许多框架会声明本身的数值数据类型,并把这些数据类型看成参数和返回值进行传递和调用,好比  NSInteger  和  CGFloat 。你须要在合适的场合使用这些框架定义的数据类型,这样可以帮助你把代码提炼出来,远离底层平台。

使用值对象的基本方法

建立值对象的基本模式是:为你的代码或框架代码利用基本数据类型值建立一个值对象(可能稍后就将其做为方法的参数传递出去)。在你的代码中,你稍后就会访问对象中封装的数据了。用  NSNumber  类来举例再合适不过了:

int n = 5; // 基本数据类型的赋值
NSNumber *numberObject = [NSNumber numberWithInt:n]; // 利用基本数据类型建立一个值对象
int y = [numberObject intValue]; // 从值对象中得到封装后的数值(y == n)

多数“值”类会声明一个初始器以及用来建立实例的工厂方法。有些类——好比  NSString  和  NSData  不只提供了初始器,还有利用存储在本地、远程文件甚至内存中的数据来建立实例的工厂方法。这些类还提供了一些补充方法,能够将字符串和二进制数据写入某个文件或 URL 制定的位置中。下边的范例代码演示了  initWithContentsOfURL:  方法利用一个 URL 对象中制定的文件的内容建立了一个  NSData  对象;在使用完数据以后,代码将数据对象写回文件系统中:

NSURL *theURL = // 利用字符串路径建立文件 URL 的代码…
NSData *theData = [[NSData alloc] initWithContentsOfURL:theURL];
// 使用获得的数据…
[theData writeToURL:theURL atomically:YES];

大多数值类除了可以建立值对象并让你访问封装好的值之外,还提供一系列简单的操做好比比较对象等。

字符串

做为 C 语言的超集,Objective-C 关于字符串的用法和 C 语言同样。换句话说,单个字母用单括号包裹,字符串用双括号。不过,Objective-C 框架通常而言不会使用 C 风格的字符串,而是使用  NSString  对象。

在《你的第一个 iOS 应用》教程中,在编写  HelloWorld  应用时你曾建立了一个格式化的字符串:

NSString *greeting = [[NSString alloc] initWithFormat:@”Hello, %@!”, nameString];

NSString  类为字符串提供了一个对象包裹,所以自带有不定长字符串存储的内存管理功能、支持众多字符编码(尤为是 Unicode 编码)、以及  printf  风格的格式化语法。由于你会常常用到字符串,所以 Objective-C 提供了利用常量建立  NSString  对象的快捷形式。要使用这种快捷形式,只需在常规的双引号包裹的字符串前边加上  @  符号,像下面的范例中这样:

// 建立字符串“My String”并带上一个换行符
NSString *myString = @”My String\n”;
// 建立一个格式化字符串“1 String”
NSString *anotherString = [NSString stringWithFormat:@"%d %@", 1, @"String"];
// 利用一个 C 语言字符串建立 Objective-C 字符串
NSString *fromCString = [NSString stringWithCString:"A C string" encoding:NSASCIIStringEncoding];

时间和日期

NSDate  对象和其余的值对象不一样,由于它在根本上是时间而不是基本数据类型。日期对象利用参考时间,按秒封装了一个间隔值。参考时间就是 GMT 2001 年 1 月 1 日的第一个实例。

光是  NSDate  的实例自身可能用处还不是很大。它确实能表明某个时刻,但没有日历、时区和个别地区的时间约定等这些上下文,并没有太大意义。幸亏 Foundation 类提供了这些概念的实体:

  • NSCalendar  和  NSDateComponents :你能够将日期和日历联系起来,包括由此引伸出的时间单位例如年、月、小时、一周中的某一天等。你还能够进行日期的计算。
  • NSTimeZone :当日期和时间必须反映出某个地区的时区时,你能够将时区对象和日历关联起来。
  • NSLocale :本地化对象,里面封装了和时间有关的文化和语言格式的规约。

下面的代码段展现了如何使用  NSDate  对象配合上述这些对象来获取你须要的信息(本例中,当前时间的打印格式为小时,分钟,秒)。请参考代码段下边对应的数字项后边的注释:

NSDate *now = [NSDate date]; // 1
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; // 2
[calendar setTimeZone:[NSTimeZone systemTimeZone]]; // 3
NSDateComponents *dc = [calendar components:(NSHourCalendarUnit|NSMinuteCalendarUnit|
    NSSecondCalendarUnit) fromDate:now];  // 4
NSLog(@”The time is %d:%d:%d”, [dc hour], [dc minute], [dc second]); // 5

  1. 建立一个表示当前时间的日期对象。
  2. 建立一个表示公历的对象。
  3. 用表明系统偏好设置中设定的时区的对象,设置日历对象的时区。
  4. 调用日历对象的  components:fromDate:  方法,将第一步里建立的日期对象做为参数传递。调用此方法后会返回一个包含了时间对象的小时、分钟和秒元素的对象。
  5. 在控制台打印出当前的时、分、秒。

虽然这个范例最终将结果打印出来,但更加推荐的使用方式是利用日期格式化器( NSDateFormatter  类的实例)在应用的界面上显示日期信息。在进行日期计算时必定要选用正确的类和方法;不要对时、分、秒、日等数值单位进行硬编码。

建立并使用群体

群体也是一种对象,它可以以特定方式存储其余对象并容许客户访问那些对象。你一般会将群体看成方法和函数的参数进行传递,也经常从方法和函数的返回值得到一个群体。群体每每包含值对象,但其实它们能够包含任何类型的对象。大部分群体对它们所包含的对象会产生强引用。

Foundation 框架种有好几种群体,其中三种在 Cocoa 和 Cocoa Touch 编程中极其重要:数组、字典和集合。这些群体的类一样分别有不可变与可变的形式。可变群体可以添加和移除对象,不可变群体只能含有它们建立时所包含的对 象。全部群体均可以进行枚举,也就是轮流检查所包含的每一个对象。

不一样类型的群体会以各自不一样的方式组织它们所包含的对象:

  • NSArray  和  NSMutableArray :数组是按顺序存储的一系列对象。你能够经过某个对象的位置序号来找到它(也就是它的索引)。数组中的第一个对象索引为 0(数字零)。
  • NSDictionary  和  NSMutableDictionary :字典将条目以“键值对(Key-Value)”的形式存储在一块儿。键是惟一标识符,一般是字符串;值就是你想要存储的对象自己。你能够经过键来直接访问它对应的对象。
  • NSSet  和  NSMutableSet :集合里的对象是无序存储的,而且每一个对象只能出现一次。一般要访问集合里的某个或某几个对象时,你必须使用筛选或对对象进行判断等方式。

collections

因为它们的存储、访问和性能各有不一样,在不一样的场合也就各有利弊。

在数组中以特定顺序存储对象

数组中的对象是按顺序存储的。所以,当顺序比较重要时你就能够选择数组。举个例子,许多应用都采用数组来存储表格视图中的内容或者菜单中的项目;索引值为 0 的对象表明第一排,索引值 1 上的对象对应第二排,以此类推。访问数组中对象的速度比访问集合的速度稍慢。

NSArray  类有多个初始器和类工厂方法用来建立和初始化数组,其中有几个尤为经常使用。你能够利用一系列对象来建立数组,使用  arrayWithObjects:count:  和  arrayWithObjects:  方法(及其对应的初始器)便可。前边一个方法的第二个参数能够用来限制第一个参数中的对象个数;后面的方法中你可使用  nil  来停止一系列用半角逗号分隔的对象。

// 建立一个含有字符串对象的静态数组
NSString *objs[3] = {@”One”, @”Two”, @”Three”};
// 用该静态对象建立一个新数组对象
NSArray *arrayOne = [NSArray arrayWithObjects:&(*objs) count:3];
// 建立一个用 nil 结尾的对象列表的数组
NSArray *arrayTwo = [[NSArray alloc] initWithObjects:@”One”, @”Two”, @”Three”, nil];

在建立可变数组时,你可使用  arrayWithCapacity: (或  initWithCapacity: )方法来建立此数组。容量参数只是做为期待数组大小的预设值,可以让数组在运行时更加高效。也就是说,数组的实际大小能够超过所指定的容量。

通常状况下,要经过索引位置(从 0 起始)访问数组中的对象时须要调用  objectAtIndex:  这个方法:

NSString *theString = [arrayTwo objectAtIndex:1]; // 返回数组中的第二个对象

NSArray  还有其余方法,你能够访问数组中的对象,也能够访问它们的索引。好比  lastObjectfirstObjectCommonWithArray:  和  indexOfObjectPassingTest:  方法。

数组的另外一个重要功能是对所包含的每一个对象均进行操做,这个过程叫作枚举。你一般会枚举某个数组,以此判断某个或某些对象是否符合某个值或者条 件,若是条件成立则能够进一步进行操做。共有三种枚举方式可供选用:快速枚举,块对象枚举,或者使用 NSEnumerator 对象。快速枚举正如其名 称所示,通常而言在获取数组中的对象时比其余枚举方式更快。快速枚举有其特定的语法:

for   (type variable   in   array)   { /* 规定   variable ,并执行所需的操做 */ }

好比此例:

NSArray *myArray = // 获取数组
for (NSString *cityName in myArray) {
    if ([cityName isEqualToString:@"Cupertino"]) {
        NSLog(@”We’re near the mothership!”);
        break;
    }
}

有几种  NSArray  方法是经过块对象进行枚举的,最简单的一个是  enumerateObjectsUsingBlock: 。块对象有三个参数:当前对象,它的索引值,以及一个布尔值,若是它为  YES  则枚举结束。块对象中的代码效果和花括号里的快速枚举效果彻底同样:

NSArray *myArray = // 获取数组
[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    if ([(NSString *)obj isEqualToString:@"Cupertino"]) {
        NSLog(@”We’re near the mothership!”);
        *stop = YES;
    }
}];

NSArray  还有数组排序、搜索、对数组中每一个对象起做用等的方法。

若要往可变数组中添加对象,则要调用  addObject:  方法;新增的对象会被放置在数组末尾。你也可使用  insertObject:atIndex:  将对象放在数组中的某特定位置。经过调用  removeObject:  或者  removeObjectAtIndex:  方法就能够将对象从数组中移除了。

用字典存储键值对

利用字典能够将对象以键值对的形式存储在群体中,键值对是指一个标识符(键)与一个对象(值)组成的对子。字典是无序群体,由于键值对能够以任何顺序存储。虽然键能够是任意形式,但最好是可以描述值的字符串,好比  NSFileModificationDate  或  UIApplicationStatusBarFrameUserInfoKey (都是字符串常量)。当它们是公有键时,用字典在任意类型的对象之间传递信息再好不过了。

经过它的初始器和类工厂方法, NSDictionary  类有许多建立字典的方式,但其中两个是最为经常使用的: dictionaryWithObjects:forKeys:  和  dictionaryWithObjectsAndKeys: (或者它们对应的初始器)。前一个方法中你须要传入一个对象数组和键数组;键和值要在位置上一一对应。后面一个方法中你须要指定第一个对象值和它的键、第二个对象值和它的键、第三个、第四个,以此类推;用  nil  即可以结束这个对象系列。

// 首先建立一个键的数组以及一个值的补充数组
NSArray *keyArray = [NSArray arrayWithObjects:@"IssueDate", @"IssueName", @"IssueIcon", nil];
NSArray *valueArray = [NSArray arrayWithObjects:[NSDate date], @”Numerology Today”,
    self.currentIssueIcon, nil];
// 建立字典,将键数组和值数组传入
NSDictionary *dictionaryOne = [NSDictionary dictionaryWithObjects:valueArray forKeys:keyArray];
// 用值、键轮流的方式建立数组,用 nil 来结束本字典
NSDictionary *dictionaryTwo = [[NSDictionary alloc] initWithObjectsAndKeys:[NSDate date],
    @”IssueDate”, @”Numerology Today”, @”IssueName”, self.currentIssueIcon, @”IssueIcon”, nil];

要访问字典中的对象值,须要调用  objectForKey:  方法,并在参数中指定一个键。

NSDate *date = [dictionaryTwo objectForKey:@"IssueDate"];

你能够向可变字典中经过调用  setObject:forKey:  添加条目,也能够用  removeObjectForKey:  删除条目,还能够用  setObject:forKey:  来替换任何给定键所对应的值。这些方法运行速度都很快。

用集合存储无序对象

集合与数组类似也是对象群体,但集合中的条目是无序存储的。你没法经过索引或者键来访问集合里的对象,而是随机访问( anyObject ),经过枚举群体或者使用筛选器、测试等方式查找对象。

虽然在 Objective-C 中集合对象不像字典和数组那么经常使用,它们仍然是某些技术中很是重要的群体类型。在 Core Data(一种数据管理技术)中,当你声明一个一对多关系的属性时,属性类型就应该是  NSSet  或者  NSOrderedSet 。集合在 UIKit 框架中的原生触摸事件处理中也是很是重要的,好比:

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *theTouch = [touches anyObject];
    // 处理代码……
}

有序集合是集合定义以外的一个特例。在有序集合中,条目的顺序十分重要。测试某个条目是否存在时,有序集合比数组的速度更快。

在运行时检验对象能力

内省(Introspection)是 Objective-C 中  NSObject  类的一个强大而实用的特性,可让你在运行时获知关于对象的一些信息。这样,你就能避免一些错误,好比将消息发送给一个不认识它的对象,或者觉得某个对象继承自另外一个对象,实际上却不是。

在运行时,对象能够传达关于它本身的三种重要类型的信息。

  • 它是不是某个类或子类的实例
  • 它是否能响应某条消息
  • 它是否遵照某个协议

探究对象是不是某个类或其子类的实例

这样作的方式是对对象调用  isKindOfClass:  方法:

static int sum = 0;
for (id item in myArray) {
    if ([item isKindOfClass:[NSNumber class]]) {
        int i = (int)[item intValue];
        sum += i;
    }
}

isKindOfClass:  方法须要一个  Class  类型的对象做为参数;要得到这个对象,在类符号上调用  class  方法即可。检查此方法返回的布尔值并进行下一步操做。

NSObject  还声明了其余用来探究对象继承信息的方法。好比  isMemberOfClass:  方法会告诉你对象是不是某个指定类的实例,而  isKindOfClass:  会告诉你对象是不是某个类或其子类的成员。

探究对象是否可以响应某个消息

这样作的方法是对对象调用  respondsToSelector:  方法:

if ([item respondsToSelector:@selector(setState:)]) {
    [item setState:[self.arcView.font isBold] ? NSOnState : NSOffState];
}

respondsToSelector:  方法须要一个选择器做为参数。选择器是 Objective-C 的一个数据类型,能够在运行时标识某个方法;利用  @selector  编译器指令能够指定该选择器。在你的代码中,检查该方法返回的布尔值并进行下一步操做。

为了标识要发送给对象的消息,一般是调用  respondsToSelector:  方法,这比检测类的类型要更有用。好比,某个类的最新版本中可能实现了一个旧版本中不存在的方法。

探究对象是否遵照某个协议

这样作的方法是对对象调用  conformsToProtocol:  方法:

- (void) setDelegate:(id __weak) obj {
    NSParameterAssert([obj conformsToProtocol:
        @protocol(SubviewTableViewControllerDataSourceProtocol)]);
    delegate = obj;
}

conformsToProtocol:  方法须要协议的运行时标识符做为参数;利用  @protocol  编译器指令能够指定此标识符。接下来检查该方法返回的布尔值并进行下一步操做。

比较对象

利用  isEqual:  方法能够将两个对象进行比较。接收到此消息的对象将和做为参数传入的对象进行比较;若是它们相同则此方法返回  YES 。范例:

BOOL objectsAreEqual = [obj1 isEqual:obj2];
if (objectsAreEqual) {
    // 执行某些操做…
}

注意,对象的相等并非对象的同一。对象的同一性要用  ==  来检测两个变量是否指向同一个实例。

那么当你比较两个对象时,到底是在比较什么内容呢?这要视具体的类而定了。根类  NSObject  使用指针相等性来做为比较的基本点。其下任何层级的子类都可将父类比较的基本点的实现按照特定类的条件进行重写,好比对象状态。举例来讲,假设有个 Person(人)对象和另外一个 Person 对象,若是它们的姓、名、生日属性都相同,那么就断定它们相等。

Foundation 框架的值和群体对象以  isEqualToType:  的形式声明比较方法,这里的  Type  是去掉“NS”前缀的类名称,例如  isEqualToString:  和  isEqualToDictionary: 。比较方法能够用来肯定传入的对象是否适合于某个给定的类型,以便接下来进行其余形式的比较。

拷贝对象

要拷贝某个对象,对其调用  copy  方法便可:

NSArray *myArray = [yourArray copy];

做为被拷贝的对象,接收消息的对象的类必须遵照  NSCopying  协议。要让对象可以被拷贝,你必须采用这个协议的  copy  方法并实现它。

当你须要使用程序另外一个地方的对象并想要彻底保持对象的各项状态的话,就须要拷贝这个对象。

拷贝行为是根据类的不一样而各自区别的,并且还依赖于个别实例的特性。大多数类实现了深拷贝,它会复制实例中的全部实例变量和属性;有些类则实现浅拷贝,它只会复制实例变量和属性的引用。

拥有可变与不可变两种变体的类还会声明一个  mutableCopy  方法,用来建立对象的可变拷贝。好比,若是对一个  NSString  对象调用  mutableCopy  方法,你会获得一个  NSMutableString  实例。

相关文章
相关标签/搜索