斯坦福大学iOS公开课笔记(4)-objective-C知识和Foundation框架

这一节课中讲得都是一些很基础的东西,从objective-C的一些简单的知识到Foundation框架中的各个对象的使用。

objective-C 知识

创建对象

alloc init方法

一般情况下,我们使用alloc init方法进行对象的创建,比如:

1
2
3
NSMutableArray *cards = [[NSMutableArray alloc] init];
CardMactingGame *game = [[CardMatchingGame alloc] initWithCardCount:12 usingDeck:d];

使用类方法创建

还有一些是使用类方法来创建,比如:

1
NSString *moltuae = [NSString stringWithFormat:@"%d", 42];

alloc init方法&类方法

还有一些是既有alloc init方法,又有类方法的,比如format方法

既有

1
[NSString stringWithFormat:...]

又有

1
[[NSString alloc] initWithFormat:...];

他们的作用完全相同。他们同时存在算是一种历史遗留问题。

通过另一个方法来创建

比如NSString中的

1
- (NSString *)stringByAppendingString:(NSString *)otherString;

你发送一个字符串给他,他会创建一个新的字符串返回给你。

nil

nil表示不指向任何东西,我们可以向nil发送消息,程序不会发生崩溃,但是也不会发生任何事情。

动态绑定

动态绑定中有一个很重要的东西叫做 id,id是一个指针,所以不存在*id。它指向未知或者未指定对象。实际上oc中所有的指针都是id。但是id类型如果使用不当就会照成程序崩溃。接下来我们来说明一下。

1
NSString *s = @"x";

上边这行代码很清楚的说明了编译器想要的东西,s的类型,他的值是多少。

1
id obj = s;

而这一行就有些危险,他声明了一个id类型的obj并指向了字符串类型s。

1
NSArray *a = obj;

这时候我们让一个数组类型的变量指向了字符串类型,这样就非常危险了。很有可能发生崩溃。

为了理解的更充分一点,我们来举一个例子:
我们现在有两个类,一个类Vehicle(交通工具),他有一个方法move(运动)。还有一个类Ship(船),他继承于Vehicle,所以他也可以实现move方法,而且他自身还有一个shoot(射击)方法。

1
2
3
4
5
6
7
@interface Vehicle
- (void)move;
@end
@interface Ship
- (void)shoot;
@end

现在我声明一个局部变量,一条船s。这时候我执行shoot让船射击,或者执行move让船移动都没有任何的问题。

1
2
3
Ship *s = [[Ship alloc] init];
[s shoot];
[s move];

我还可以声明一个交通工具v,让他指向s,这也没问题,因为船是交通工具的一种。

1
Vehicle *v = s;

那现在我让v执行shoot射击,这时候编译器会发出警告,因为Vehicle没有shoot方法。但是运行时,他不会崩溃,因为我们知道v是一艘船,所以不会崩溃。

1
[v shoot];

接下来我声明一个id类型的obj,不指定obj是个什么东西。然后我让他执行shoot射击,这时候,编译器不会提醒你有错误,因为我们确实有一个shoot方法,而且obj不知道是个什么东西,所以不会报错。但是在运行时,因为obj中没有shoot这个方法,所以这时候程序就会发生崩溃。

1
2
id obj = ...;
[obj shoot];

但是如果我现在让obj执行一个不存在的方法时,编译器就会报错,因为编译器会觉得我可能是写错了?拼写出了问题,所以这时候编译器就会提示。

1
[obj someMethodNameThatNoObjectAnyWhereRespondsTo];

那接下来我们继续假设,有一个NSString类型的hello,我让他执行shoot操作,那运行时肯定会崩溃,因为我们指定了hello就是字符串类型的,而字符串并没有设计这个操作,那么一定会崩溃。而且编译器也会对我们发出警告。

1
2
NSString *hello = @"hello";
[hello shoot];

那么如果现在我使用强制转换类型创建一个helloShip,这时候编辑器会认为helloShip是一个ship,但是我们明白,他是一个字符串,这个时候我们如果让helloShip执行shoot方法时,编译器不会报错,因为他认为helloShip是ship,有shoot方法。但是在运行时,程序会发生崩溃。因为字符串类型不会对shoot方法进行反应。

1
2
Ship *helloShip = (Ship*)hello;
[helloShip shoot];

这种情况下我们的代码就会非常危险,如果一个不注意就会发生崩溃,那么如何避免这样的问题,我们就要用到类型保护机制,我们可以使用内省或者协议来对我们的类型进行保护。

类型保护机制(内省)

  • isKindOfClass
    他可以让你问一个NSObject对象或者继承于NSObjet类的对象,是否属于这一系列类。

  • isMemberOfClass
    他与isKindOfClass差不多,他是用来确定你是否实际是这个类。

  • respondsToSelector
    他是说id指向的这一对象,是否能对某一个特定的方法做出反应,比如船是否可以执行射击操作。

内省经常用于MVC中的通信和从数组中选择一个东西出来的时候的判断。

比如:

这段代码就非常不安全,如果otherCards中存在不能响应play方法的时候,就会照成程序的崩溃。

1
2
PlayingCard *otherCard = [otherCards firstObject];
[otherCard play];

添加了类型保护机制之后就不会出现这样的问题了,这时候通过isKindOfClass确认之后。我们给card发送消息的时候,就不会发生崩溃了。

1
2
3
4
5
6
7
PlayingCard *otherCard = [otherCards firstObject];
id card = [otherCards firstObjct];
if ([card isKindOfClass:[PlayingCard class]])
{
PlayingCard *otherCard = (PlaytingCard *)card;
[otherCard play];
}

Foundation

Foundtion这里并没有讲太多东西,都是一些基础的数据类型,所以这里也就不详细写出来了。

NSObject

所有的类都继承于NSObject,他有一个很重要的方法

1
- (NSString *)description;

当我们使用NSLog的时候,就会调用到description函数。

NSArray

不可变数组。可以使用@[]方法创建新的数组。

NSMutableArray

可变数组,能够使用NSArray中的所有方法,并且可以添加和移除数组里的元素。

NSNumber

用于存放所有原始类型,整形、浮点型、布尔类型等等。可以直接使用 @3 这种格式来声明。

NSDara

二进制文件

NSDate

日期相关的东西会使用到这个,还可以使用NSCalendar来处理日历,NSDateFormatter来处理日期格式,还有NSDateComponets

NSDictionary

字典,仅次于数组的最重要的一种类型。由键值对组成。

NSMutableDictionary

可变字典,与数组和可变数组的关系一样,可以使用字典的所有功能,并且可以添加删除元素。

NSUserDefaults

他是一种共享字典,在程序里一直可以保存,但是他不能保存很大的东西,比如图片啊什么的,他可以存储一些字符串或者数字等一些小东西。

在设置完数据之后一定要记得使用synchronize来同步,如果不同步的话,之前设置的内容在程序杀掉之后就不会存在了,所以一定要记得使用synchronize同步数据。

NSRange

用来表示一个范围,他是一个结构体,用来表示一个起始位置和长度。

1
2
3
4
typedef struct{
NSUInteger location;
NSUInteger length;
}NSRange;

Fonts 字体

这里提到,如果你只会使用一种设置字体的方法,那么一定就是这个方法:

1
UIFont *font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];

后边的参数有很多种,可以根据使用的场景选择不同的字体类型。

并且还说明了最好不要使用系统字体来设置字体。

NSAttributedString

这是一个神奇的字符串,你可以对他进行很多设置,但是要记住,这个字符串的内容是不可变的。可以通过NSRange来对部分字符串进行修改。

这个类的具体使用会在下一节课中写一个Demo来着重讲解。所以这里就先不多啰嗦,等到下节课的笔记中再详细介绍如何使用它。