回顾上节课内容:

主要学了两样东西,xcode和很多obj-c。

1、如何创建新项目?开始界面的Create键还有文件菜单的创建新项目。

2、如何显示项目里的不同文件?比如左边的navigator,上部的选项条。

3、xcode的UI布局:使用对象库,inspector,debugger控制台等。

4、从右下角拖button、label到view上,还有从中拖出条线来连接到controller来实现controller对view的通信,还有利用action让view向controller发话。

5、如何获取帮助?按住option键再点击进入完整文档。

6、如何在模拟器上运行程序?点击黄色的warning和红色的error,会显示一行内容,右边会显示个数。

7、如何创建一个类?calculatorbrain通过文件菜单的new file创建。

8、如何定义一个类?头文件里的public API,m文件里的private实现,还有如何使用@interface在m里创建private接口。

9、创建property,包括基本类型和指针类型,property里的nonatomic声明,strong、weak的用法。

10、怎么使用@synthesize来创建property的getter和setter,还有辅助实体变量,使用下划线property名字,所以辅助实体变量不会与property重名,重名会导致一些问题。

11、对象指针、id、固定类型,固定类型可以帮助发现bug。

12、如何定义类方法,如何声明本地变量,延迟实例化一个对象,包装原始类型成为对象,使用nslog把信息打印到控制台是非常强大的debug技巧。

13、如何添加删除数组元素,如何分配空间和初始化。

这节课主要是讲Obj-C语法

首先要深入property,property太重要了,所以要多花些时间。

1、为什么要有property?

  •  实体变量的安全性和继承能力
  •  提供了一个阀门给延迟实例化,比如:UI更新,一次性检测。

2、实体变量

  •  property必须要有实体变量吗?不需要。怎样才能让property没有实体变量,不用@synthesize,自己创建setter和getter,这样就不会有辅助实体变量,需要自己制造或计算property。
  •  可以有实体变量但没有property吗?也是可以的,但希望都用property。

3、为什么要用.号?

  •  看起来舒服,容易看懂,也使得property的读取更明显一点,读代码的时候更容易发现这是在调用getter。
  •  .号还能配合C里面的结构体

任何的类型,比如类、结构体、自定义类型的名字都需要大写,其他名字都不大写。

4、strong VS weak

strong和weak是指针的属性,比如当遇到uibutton *的时候,这个property是个指针,需要指定strong或者weak。

strong表示保留它所指向的堆上的内存区域直到它不再指向这块区域了。也就是说,我强力指向了一个区域,我不再指向它的条件只有我指向nil,或者我自己也不在内存上了,没有人strong指向我了。

weak表示只要还有人strong指向它那么就保留它。也就是说,我不再指向它了也没关系。weak还表示如果没有人指向它了,它就会被清除出内存,同时我就被指向nil,因为我不能读取不存在的东西。weak只在ios5上起作用,你设置了weak只有ios5能够在runtime时自动设为nil。如果编译为ios4,就不能使用weak的设空机制,必须使用strong,自己去设置为nil。

这是垃圾回收吗?这不是垃圾回收,这还是引用计数。用引用计数表示堆上还有多少strong指针,当它变为0就马上释放。垃圾回收是不可控的,这里是完全可控的,当失去最后一个strong指针,它马上就被释放,没有延迟。

这里strong和weak都是针对property,本地变量都是strong的。当函数结束后,这个指针不再指向它了,那就马上被清理掉了。这怎么实现呢?编译器帮你计数引用计数。

当我的对象被清理出内存时会收到通知吗?会,就是这个dealloc方法。

5、nil

nil表示0值,指针nil就是不指向任何东西。所有synthesize生成的实体变量初始值都是0,如果希望指针指向什么东西,你可以调用setter或者在getter里使用延迟初始化。可以隐含测试是否为nil。

向nil发送消息不会使程序崩溃,事实上什么也不执行,如果有返回值,就返回0。如果返回的是C结构体怎么办,如果给一个nil的返回C结构体的对象发消息,得到一个未定义的C结构体,这个东西是随机的不是你想要的。

布尔型,NO是0,YES是非0。只能大写BOOL。

6、实例方法 VS 类方法

实例方法开头是—,类方法开头是+。

实例方法和在其他语言里一样是普通的,对象是实例对象。类方法的对象是类而不是实例,通常用来创建对象或工具方法。

方法的参数识别:带*星号的,是类指针变量,内容在堆上,不带星号的是变量在栈上。

两者的调用语法相似但不一样,都是以[开始,但实例方法中后面跟的是一个实例指针,但类方法在[后面是一个类。还有一点,发一个“class”消息给实例,这个特殊方法会返回这个实例的类,然后就可以给这个类发消息了。

逗号是当你有不确定数量参数时用的,比如[NSString stringWithFormat:@"%g",result]。

在实例方法和类方法的实现里,self和super也有些不同。在实例方法里,根据继承原理发消息给self或super其实都是发给self,因为self不是实例,只是类,super也一样。

7、实例化

实例化用来创建对象,获取对象的最好方法是要求其他对象给你一个,比如stringByAppendingString。不是所有返回的对象都是新建的,如果对象已经存在就返回它的指针。所有数组中的元素都是strong指针。获得对象的主要方法还是使用类方法,所以绝大多数对象都是通过其他对象或者类来获取的。

如果从0开始创建一个对象呢?方法是alloc和init组合使用,alloc是NSObject的类方法,为对象在堆上分配一个足够大的空间,分配的对象初始值是0或nil。对象只分配为空值还不够,还要初始化。不管对象是否需要初始化,我们永远100%要在alloc外面加上init或initWith,所以永远要这么写,决不能只有alloc,也不能下一行才行,必须要直接加在后面。可以允许有任意多个其他的init方法。

init这里有个小技巧,所有的类必须为子类提供一个初始化方法,方便子类初始化的时候,先调用父类初始化,因为创建子类的时候会调用子类初始化,当你这么做的时候需要用这个init方法先初始化父类,知道这个指定的init方法很重要。如果我继承了UIView,我需要在初始化里写[super initWithFrame],否则父类就没办法初始化。可以通过查看文档找到父类指定的init方法,然后在子类的初始化里调用这个方法。

如何新建自定义的初始化方法?可以延迟初始化就不用初始化了。以下是自定义初始化方法:



@implementation MyObject  
    - (id)init  
    {  
       self = [super init]; // call our super’s designated initializer   
       if (self) {  
       // initialize our subclass here  
       }  
       return self;  
    }   
    @end



这是NSObject指定的初始化init,这里有行很特别的代码,self = [super init]。在obj-c里self只是本地指针,但这是唯一可以对它赋值的情况。那为什么要对self赋值呢?这是一种协议机制,确保super的初始化在我们之前,如果super没有初始化就返回nil,所以如果因为某些原因没法初始化,初始化失败就允许返回nil,所以在子类里要检查父类是否返回nil。这里调用了super的指定初始化,检查它是否返回nil。如果不是nil就init自己,最后返回self。

id是否等于void *?不是,id是obj-c的内置类型,id的表现很想void *,但编译器是知道id的确切类型的,所以当对它发消息是要先检查一遍。比如可以任意发消息给void *,但发给id会先检查它是否是个对象,可能无法检查id是否能回应消息,但至少知道一部分合法消息。

以上是指定初始化,现在是一个快捷初始化,它不是指定的,但很方便。



@implementation CalculatorBrain
- (id)initWithValidOperations:(NSArray *)anArray {
    self = [self init];
    self.validOperations = anArray; // will do nothing if self == nil 
    return self;
} 
@end



注意这个init里面我没有调用super的指定初始化,我在自己的快捷初始化里调用了自己的指定初始化。

8、动态连接

固定类型和id在runtime时没有区别,唯一区别是编译器能否辅助发现bug。消息的执行函数的决定发生在runtime。比如



NSString *s = ...;

id obj = s;



无所谓类型是id或NSString什么的,这个函数会自己去代码中查找。给出具体类型其实只是为了方便查找bug。

9、映射



@interface Vehicle  
- (void)move;  
@end  
@interface Ship : Vehicle  
- (void)shoot;  
@end
  
Ship *s = [[Ship alloc] init];  
[s shoot];  
[s move]; 
 
Vehicle *v = s;  
[v shoot];

id obj = ...; 
[obj shoot]; 
[obj someMethodNameThatNoObjectAnywhereRespondsTo];
NSString *hello = @”hello”;
[hello shoot];
Ship *helloShip = (Ship *)hello; 
[helloShip shoot];
[(id)hello shoot]



v没有shoot方法,事实上这个指针内存上是有shoot方法的,但编译器不会知道,所以类型检查是基于指针的固定类型,但这个不会崩溃,还是会执行。因为这个v其实是Ship,Ship可以shoot,但编译器会抱怨。如果把hello映射到Ship上,编译器会通过,编译器认为如果你在映射,你知道自己在做什么。[helloShip shoot]不会被警告,但运行会崩溃。

10、自我测量

id很危险可以让程序崩溃,怎么办?可以使用自我测量。使用id不是一直是坏的,比如不同类型的数组就很好用,如果想要数组有多个类型就要用id了。还有如果想要对象的成员是private的,你又想把它交出去,但你不想被知道它是什么类,所以就设为id。

但有时需要知道某些id的类,以下三个方法都可以帮你知道:



isKindOfClass: returns whether an object is that kind of class (inheritance included) 



isMemberOfClass: returns whether an object is that kind of class (no inheritance)



respondsToSelector: returns whether an object responds to a given method



isKindOfClass和isMemberOfClass的区别在于继承,isKindOfClass告诉你是否属于该类或它的子类,isMemberOfClass不包括继承类。isKindOfClass中的参数不能直接写类名,要调用该类的类方法class去获取,比如[obj isKindOfClass:[NSString class]]。



selector比较特别,在obj-c里是用来描述方法的,用了@selector就可以不用再写上参数的类型和名字,只要写上调用的关键字和冒号就可以了。obj-c里有个类型用来保留selector的返回值,这个返回值的类型就是SEL。



11、foundation

foundation是个框架,含有很多重要对象。

NSObject几乎是任何ios对象的基础,它有+alloc和自我测量方法,还有description(我们用了NSLog到控制台,通常我们会重载description来显示自己的内容)、copy和multableCopy(不可以直接复制对象,只有实现了特殊协议的对象才可以)。

NSString是Unicode编码的任意语言的字符串,是不可变的,或者让它做你要求它做的事然后它会返回一个新的字符串给你。字符串的通常用法是,给它发一个消息,然后返回一个新的字符串。

可变版本的字符串叫做NSMutableString,它的部分功能用NSString也能实现,可以用append来实现可变。为什么不鼓励用MutableString?通过append建一个新字符串的过程和用MutableString是一样的,此外,NSString已经被优化到了极致。

NSNumber是原始类型的包装,它还提供了转接功能,比如包装了int但你需要一个float,它会自动转接好给你。

NSValue为结构体之类的数据提供包装,比如CGPoint。

NSData是用来装无结构数据的、二进制码之类的。

NSDate表示日期时间,过去、现在或未来,还有各种有格式的日期时间。

NSArray是一个有序的对象的集合,根据增加和删除操作会改变大小,它是不可变的,有不少类方法可以创建一个数组。主要方法有count和objectAtIndex,count是数组个数,objectAtIndex是指定位置的内容。对象NSNull可以放数组里,但nil不行。



+ (id)arrayWithObjects:(id)firstObject, ...; // nil-terminated arguments  
NSArray *primaryColors = [NSArray arrayWithObjects:@“red”, @“yellow”, @“blue”, nil];  
+ (id)arrayWithObject:(id)soleObjectInTheArray; // more useful than you might think!  
- (int)count;  
- (id)objectAtIndex:(int)index;  
- (id)lastObject; // returns nil (doesn’t crash) if there are no objects in the array  
- (NSArray *)sortedArrayUsingSelector:(SEL)aSelector;  
- (void)makeObjectsPerformSelector:(SEL)aSelector withObject:(id)selectorArgument;  
- (NSString *)componentsJoinedByString:(NSString *)separator;  
- (BOOL)containsObject:(id)anObject; // could be slow, think about NSOrderedSet



NSMutableArray是可变数组,可以用NSArray的方法创建,也可以用array建个空的数组。NSMutableArray的copy会返回不可变的数组,但是不可变数组的可变copy会返回可变数组。可变数组继承了不可变数组的全部方法。



+ (id)arrayWithCapacity:(int)initialSpace; // initialSpace is a performance hint only + (id)array;  
- (void)addObject:(id)anObject; // at the end of the array - (void)insertObject:(id)anObject atIndex:(int)index;  
- (void)removeObjectAtIndex:(int)index;  
- (void)removeLastObject;  
- (id)copy;



NSDictionary是个哈希表,这是不可变的,想要里面的东西,就在创建的时候放进去。



+ (id)dictionaryWithObjects:(NSArray *)values forKeys:(NSArray *)keys;  
+ (id)dictionaryWithObjectsAndKeys:(id)firstObject, ...;  
NSDictionary *base = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:2], @“binary”,  
[NSNumber numberWithInt:16], @“hexadecimal”, nil];  
- (int)count;  
- (id)objectForKey:(id)key;  
- (NSArray *)allKeys;  
- (NSArray *)allValues;



NSMutableDicationary类:



+ (id)dictionary; // creates an empty dictionary (don’t forget it inherits + methods from super)  
- (void)setObject:(id)anObject forKey:(id)key;  
- (void)removeObjectForKey:(id)key;  
- (void)removeAllObjects;  
- (void)addEntriesFromDictionary:(NSDictionary *)otherDictionary;



NSSet是个不可变无序的唯一的对象集合,不能放两个一样的对象,不是说同一个对象指针,而是内容一样的两个对象。

NSOrderSet不是NSSet的子类,但是是NSArray和NSSet的混合,它能够在指定位置插入对象,但仍然不能重复放入对象。

枚举,可以for....in。



NSSet *mySet = ...; 
for (id obj in mySet) {
    // do something with obj, but make sure you don’t send it a message it does not respond to
    if ([obj isKindOfClass:[NSString class]]) {
      // send NSString messages to obj with impunity
    }
}



NSDictionary *myDictionary = ...;  
for (id key in myDictionary) {  
    // do something with key here  
    id value = [myDictionary objectForKey:key];  
    // do something with value here   
}



在for....in里不要修改可变数组。

12、Property List

表示任何6个类型的组合:NSArray, NSDictionary, NSNumber, NSString, NSDate, NSData。

为什么要有Property List?因为ios有的API参数是id,它的文档上规定这个id必须是Property List

NSUserDefaults是个轻量级的Property List数据库,轻量级就是小型的,它以字典为基础,存在于应用运行的空隙。用standardUserDefaults方法进行读写。无论何时用NSUserDefaults,你必须和磁盘进行同步,这是安全起见,只要写[[NSUserDefaults standardUserDefaults] synchronize],任何操作之后都要同步,否则数据不会被保存,而且同步开销不大。