Programming with Objective-C
Posted 题材新颖
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Programming with Objective-C相关的知识,希望对你有一定的参考价值。
本节的内容主要是 OC 的语言基础,对 OC 比较熟的可以不用看这一篇的内容了,基本也就是翻译一下官方文档。
这一篇的主要内容就是关于 OC 的语言基础,主要是针对值和集合而言。实际上 OC 中的值可以先分为两类,一类是 C 语言提供的类型,一类是 OC 为我们提供的类型。
C 语言提供的类型,就是 int, float, char 这些,而在 OC 中,我们则是用对象来表示一个值,所以主要类型是 NSInteger, NSUInteger 以及 CGFloat 这样的形式。C 语言所提供的这些类型的好处就在于我们不需要使用一个对象来表示值。但是在涉及到集合的操作的时候,我们往往需要的是 OC 中提供的类型。
C语言基本类型
首先介绍的是 C 语言中的基本类型,它们的使用都很简单:
int someInteger = 42;
float someFloatingPointNumber = 3.1415;
double someDoublePrecisionFloatingPointNumber = 6.02214199e23;
当然,在 OC 中我们也可以直接使用这些基本类型,比如将其声明为属性:
@interface XYZCalculator : NSObject
@property double currentValue;
@end
声明为属性的时候,我们也可以使用点操作符在类的内部对其进行访问,因为这一层访问主要是基于访问方法来获取和设置值,所以我们用 C 的类型还是用 OC 的类型差别不大。
Objective-C 中的基本类型
OC 中使用 BOOL 类型来存放布尔值,它的值是 YES 或者 NO。而在 Cocoa 或者 Cocoa Touch 中对象所包含的方法中,参数一般使用的都是 NSInteger 或者 CGFloat 这种类型的值。比如 NSTableViewDataSource 以及 UITableViewDataSource 协议中的方法,所用的就是 NSInteger 类型的值。
不论 NSInteger 也好,还是 NSUInteger 也好,他们的范围都和目标结构有关,比如在32位的系统下运行,他们就是32比特的值,如果在64位下运行,那么他们就会占据64位的空间。在使用系统 API 的时候,苹果推荐还是使用 NSInteger 这样的值作为参数传递。
结构体
在 Cocoa 以及 Cocoa Touch API 中,有时候也会使用 C 语言中的结构体来存储他们的值,比如:
NSString *mainString = @"This is a long string";
NSRange substringRange = [mainString rangeOfString:@"long"];
这个地方的 NSRange 其实获取到的就是一个结构体,它包含了一个 long 字符串在原本字符串中的起始值,以及 long 这个字符串的长度。
对象也可以用于表示值
如果说你需要用一个对象来表示基本类型的值,那么你就可以使用 Cocoa 以及 Cocoa Touch 提供的基本类型,一般我们在使用集合的时候就会需要用对象来表示值。
字符串使用 NSString 对象来表示
在之前的很多代码中,我们都会看到 NSString 这个类,它用于表示一个字符串。在 OC 中,如果我们想创建一个 NSString 类的对象,可以采取很多种方法,比如使用标准的 alloc init 方法,或者使用工厂方法,也可以直接用字面值来实现:
NSString *firstString = [[NSString alloc] initWithString:"Hello World!" encoding:NSUTF8StringEncoding];
上面这些代码都可以通过已有的字符串来创建一个字符串对象。不过,这里创建的字符串对象都是不可变的,也就是说一旦我们创建了这个字符串对象,那么我们就无法修改它的内容。如果我们需要一个不同的字符串,那么我们只能创建一个新的字符串对象,比如:
NSString *name = @"John";
name = [name stringByAppendingString:@"ny"]; // 返回的是一个新字符串对象
需要注意的一点是,对于一个普通的字符串对象,我们采用 stringByAppendingString: 这样的方法的时候,最后获取的都是一个新的字符串对象所以我们需要一个变量来接受它,直接使用 [name stringByAppendingString:] 这样的形式我们是没有办法接受到新产生的字符串对象的,而 name 依旧指向原本的字符串。
所以如果我们的字符串是可能产生变动的,那么我们需要一个 NSMutableString 类的对象,它是 NSString 类的一个子类,或者说是 NSString 的可变版本。我们基于 NSMutableString 类的对象,可以直接对字符串进行修改,比如:
NSMutableString *name = [NSMutableString stringWithString:@"John"];
[name appendString:@"ny"];
另外说一下,其实 NSMutableString 的实现并不难,就好比之前我们在使用的时候会采取 name = [name stringByAppendingString:] 这样的形式一样,并不是说这个过程就没有产生新的字符串对象,而是 Cocoa 将这个过程封装起来的,我们在用的时候代码更加简洁了,同时 Cocoa 又提供了一些新的方法供我们使用。
格式化的字符串
格式化的字符串在各个编程语言中都有,基本上就是采用一个替换符来按照自定义的格式在字符串中插入变量。格式化字符串的作用还是挺大的,并且从形式上来讲也是相当灵活的。具体的代码如下:
int magicNumber = ...
NSString *magicString = [NSString stringWithFormat:@"The magic number is %i", magicNumber];
数字可以用 NSNumber 类的实例来表示
实际上 NSNumber 类可以用来表示 C 语言中的基本类型,包括 char, double, float, int, long, short 以及相应的 unsigned 变量,另外 OC 中的 BOOL 值也是可以用 NSNumber 来表示的。同样地,NSNumber 的实例也可以采用不同的实例化方法:
NSNubmer *magicNumber = [[NSNumber alloc] initWithInt:42];
NSNumber *unsignedNumber = [[NSNumber alloc] initWithUnsignedInt:42u];
NSNumber *longNumber = [[NSNumber alloc] initWithLong:42l];
NSNumber *boolNumber = [[NSNumber alloc] initWithBOOL:YES];
NSNumber *simpleFloat = [NSNumber numberWithFloat:3.14f];
NSNumber *betterDouble = [NSNumber numberWithDouble:3.1415926535];
NSNumber *someChar = [NSNumber numberWithChar:'T'];
对于 NSNumber 类,我们也可以直接使用字面值来初始化:
NSNumber *magicNumber = @42;
NSNumber *unsignedNumber = @42u;
NSNumber *longNumber = @42l;
NSNumber *boolNumber = @YES;
NSNumber *simpleFloat = @3.14f;
NSNumber *betterDouble = @3.1415926535;
NSNumber *someChar = @'T';
这种写法和直接用工厂方法来实例化是一样的。
使用 NSValue 来表示其他的值
之前提到的 NSNumber 实际上是 NSValue 的一个子类,它主要的作用是封装单个值或者一个数据项。相比 C 语言中的基本类型,NSValue 也可以用于表示指针或者结构体。NSValue 提供了很多工厂方法让我们可以直接根据一个结构体来创建一个值,比如 NSRange,具体的用法如下:
NSString *mainString = @"This is a long string";
NSRange substringRange = [mainString rangeOfString:@"long"];
NSValue *rangeValue = [NSValue valueWithRange:substringRange];
当然,一些自定的结构体也是可以存放在 NSValue 对象中的,比如:
struct MyIntegerFloatStruct aStruct;
aStruct.i = 42;
aStruct.f = 3.14;
NSValue *structValue = [NSValue value:&aStruct
withObjCType:@encode(MyIntegerFloatStruct)];
集合基本是对象
在 C 语言中,我们表示集合的方式主要是数组,用于存储一系列同种类的变量。而在 OC 中,集合主要是 Cocoa 以及 Cocoa Touch 中集合类的实例,比如 NSArray, NSSet 以及 NSDictionary。这些类,主要作用就是管理一组对象,这就是说,C 语言中的基本类型是没有办法直接放到 OC 的集合中去的,如果要放进去的话,就必须将他们先放到 NSNubmer 或者 NSValue 的实例中去,然后再把这些实例添加到集合中去。
对于 OC 中的集合,需要注意的就是它们保存的并不是对象的拷贝,而是对象本身,并且是采用强引用的形式保存的。所以放在集合中的对象想要释放,至少得先把集合给释放掉。另外,由于直接存放对象本身,所以如果我们需要从集合中获取一个对象并执行某些操作的时候,就非常方便了。同样地,NSArray, NSSet, NSDictionary 本身都是不可变的,它们也都有一个可变的子类。
数组
数组和其他两种集合的不同之处在于,它存放的对象是有序的,就像其他语言中,我们会通过下标访问一样,下标就代表着对象之间的顺序。
数组的创建
数组的创建和之前提到的一些值一样,可以通过多种实例化的方式来获取对象:
+ (id)arrayWithObject:(id)anObject;
+ (id)arrayWithObjects:(id)firstObject, ...;
- (id)initWithObjects:(id)firstObject, ...;
arrayWithObjects 和 initWithObjects 这个两个方法的独特之处在于他们的最后一个参数是 nil,所以,如果说我们插入的对象中间有一个对象的值是 nil,那么最后我们得到的数组只会到这个对象为止,因为编译器判断 nil 就是初始化的终点,这个地方是需要注意的。
数组的查询
对于数组对象,我们可以获取其中的一些信息,比如:
NSUInteger numberOfItems = [someArray count];
if ([someArray containsObject:someString])
...
以上就是对于数组中对象个数以及是否含有某个对象的判断,我们还可以通过下标来获取一个对象,当然,这个时候如果访问越界的话系统还是会报错的,所以一般在访问前还是会做一层判断:
if ([someArray count] > 0)
NSLog(@"First item is: %@", [someArray objectAtIndex:0]);
另外,下标访问其实除了使用 objectAtIndex: 形式之外,我们也可以使用中括号来进行访问,这和其他语言中数组的下标访问是一致的。
数组的排序
之前也有说到了,数组的一个特性就在于它所包含的元素是有序的,这也就意味着我们可以调整其中元素的顺序。不过在对数组排序的时候,需要注意一点,数组本身是不可变的,所以排序之后产生的是一个新数组,我们需要用一个对象来保存结果。打个比方:
NSArray *unsortedStrings = @[@"gammaString", @"alphaString", @"betaString"];
NSArray *sortedStrings =
[unsortedStrings sortedArrayUsingSelector:@selector(compare:)];
这里使用的是 NSString 类的一个方法 compare: ,当然我们也可以自定义一个方法来实现排序的功能,不过这个自定义方法需要注意一件事,那就是要根据比较的原则,如果说结果为小那么就应当返回 NSOrderedAscending,大的话就返回 NSOrderedDescending,相等则返回 NSOrderedSame。
可变数组
可变数组实际上就是数组的一个子类,添加了可变的属性,并且多了一系列与可变相关的方法。
NSMutableArray *mutableArray = [NSMutableArray array];
[mutableArray addObject:@"gamma"];
[mutableArray addObject:@"alpha"];
[mutableArray addObject:@"beta"];
[mutableArray replaceObjectAtIndex:0 withObject:@"epsilon"];
对于可变数组,我们也可以使用 sortUsingSelector: 来进行排序。
集
实际上,在所有用于存放对象的类中,集的概念应该是比较容易让人感到困惑的。首先,它是无序的,并且,它所包含的对象都是唯一的。一定要说的话,集其实只是单纯地表明,它所包含的这些对象可以归为一类,除此之外,就没有什么其他的含义了。至于它的初始化,基本上和数组是一致的。
因为集用的不多,而且它本身确实容易让人感到困惑,这里就不多讲了,有兴趣的可以直接看官方文档的解释:https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Collections/Articles/Sets.html#//apple_ref/doc/uid/20000136。
字典
字典也是比较特殊的一种集合,因为它不存在有序或者无序的问题,它所包含的内容,是以键值对的形式呈现的。无论我们是要放入对象,还是要从中获取对象,都是基于键来访问。关于键的设置,最好是用字符串作为键。当然,使用其他的对象来当做键也可以,但是在字典获取键的时候,它拿到的是副本,所以我们用作键的对象必须实现了 NSCopying 协议。
创建字典
字典的创建也可以用常规的初始化以及工厂方法来实现,比如:
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
someObject, @"anObject",
@"Hello, World!", @"helloString",
@42, @"magicNumber",
someValue, @"aValue",
nil];
对于 NSDictionary 这个类,需要注意的就是我们在初始化的时候,都是先传对象再传键,这和其他的编程语言中有所不同,使用的时候一定要注意。
字典的查询
之前也说过,字典存放的是键值对,键值对的访问模式就是通过键来获取值,也就是下面这样:
NSNumber *storedNumber = [dictionary objectForKey:@"magicNumber"];
如果说对象找不到,那么就会返回 nil,以前也有提到过,对 nil 发送消息是可行的,所以在使用字典的时候需要注意获取的对象是否为空的问题。另外,我们也可以通过下标访问来获取对象,只是下标中的内容应当是键的值。
可变字典
和其他几个类一样,苹果的设计思路是基类为不可变版本,然后为你提供一个可变的子类,字典也有一个可变的版本 MutableNSDictionary,使用这个类的话,我们就可以动态地向字典中添加或者删除对象。
[dictionary setObject:@"another string" forKey:@"secondString"];
[dictionary removeObjectForKey:@"anObject"];
另外,对于集合类,它们在初始化的时候都是以 nil 作为终结,所以如果我们真的有需要在集合中插入一个 nil,那么必须得传进去一个对象。苹果对 nil 的定位是,不存在对象,它是一个特殊值,所以并不是一个实际的对象,那么如果我们要传入一个空的对象,就必须使用 NSNull 这个类来获取一个对象,就像下面这样:
NSArray *array = @[ @"string", @42, [NSNull null] ];
使用集合来完成数据持久化
一般来说,数据处理完之后,总有一些数据是需要记录的,在 ios中,我们可以通过文件系统、归档、SQLite 和 CoreData 来实现数据的持久化,这里不会详细说明数据持久化,只是说明一些这些集合类在持久化的过程中可以起到的作用。
使用 NSArray 和 NSDictionary 来进行数据持久化非常简单,比如:
NSURL *fileURL = ...
NSArray *array = @[@"first", @"second", @"third"];
BOOL success = [array writeToURL:fileURL atomically:YES];
if (!success)
// an error occured...
同时,对于磁盘上的文件,如果它们存储的数据对象是属性列表类型中的一种,那么就可以直接通过一个文件把它们转换成运行时的对象:
NSURL *fileURL = ...
NSArray *array = [NSArray arrayWithContentsOfURL:fileURL];
if (!array)
// an error occurred...
最后说一下关于集合类的遍历,对于苹果提供的这几个集合类,我们都可以使用 for in 的形式来搞定,不过对于字典,for in 中循环的变量是 key,所以在定义 key 的时候最好是采用可以标识不同对象的某一个类,这样在遍历的时候会方便很多。
以上是关于Programming with Objective-C的主要内容,如果未能解决你的问题,请参考以下文章
Aspect-Oriented Programming : Aspect-Oriented Programming with the RealProxy Class
System and device programming——R&W with semaphore
PyQt5 GUI Programming With Python 3.6
[RxJS] Reactive Programming - Using cached network data with RxJS -- withLatestFrom()