zoukankan      html  css  js  c++  java
  • OC总结 【OC基础语法相关知识】

    m是OC源文件扩展名,入口点也是main函数,第一个OC程序:

     1 #import <Foundation/Foundation.h>
     2 
     3 int main(int argc, const char * argv[])
     4 
     5 {
     6 
     7 theme manager
     8 
     9     @autoreleasepool {
    10 
    11         NSLog(@"Hello, World!");
    12 
    13     }
    14 
    15     return 0;
    16 
    17 }

    预处理指令import会自动检验头文件有没有被包含过,防止重复包含,NSLOG是日志输出,OC字符串以@开头,自动换行,int类型的占位符是@i。OC所有关键字以@开头,@autoreleasepool与内存管理有关。

     

    OC中的类分两个文件,.h用来声明类的变量和函数,

    .m文件负责实现,与.h配合使用。OC中最根本的类叫NSObject,OC是单继承的。声明类以@interface开头,以@end结尾,实现类用@implementation开头,以@end结尾。继承用冒号。OC当中使用一个类时,导包就是#import一个类的头文件。

    声明类时,成员变量要声明在大括号中,方法声明在大括号外,如果是对象方法要写-号,静态方法要写+号,所有在.h文件当中声明的方法都是公共方法,凡是类型,都要写括号,在方法名后,一个参数要一个括号,如:

     1 //Student.h
     2 
     3 #import <Foundation/Foundation.h>
     4 
     5 @interface Student : NSObject {
     6 
     7     int age;
     8 
     9 }
    10 
    11 -(int)getAge;
    12 
    13 -(void)setAge:(int)age;
    14 
    15 @end

    实现类时,首先要导入.h的声明.

     1 //Student.m
     2 
     3 #import "Student.h"
     4 
     5 @implementation Student
     6 
     7 - (int)getAge {
     8 
     9     return age;
    10 
    11 }
    12 
    13 - (void)setAge:(int)newAge {
    14 
    15     age = newAge;
    16 
    17 }
    18 
    19 @end

    对象的创建需要调用类的静态方法alloc分配内存,调用静态方法要写[],里面是类名和方法名,返回值需要用指针来接收,也就是说OC中的对象都要写个*,比如这句话调用了Student的一个静态方法alloc分配内存,并返回了一个指针来接收,其实alloc方法返回的是id类型,可以暂时理解为任何类型:

    Student *stu = [Student alloc];

    分配内存后要调用一个动态方法进行初始化,相当于stu指针给Student发送了一个init消息:

    stu = [stu init];

    也就是说定义一个变量需要两句话,但是很麻烦,所以可以连起来写,这种方法最常用:

    Student *stu = [[Student alloc] init];

    OC不支持垃圾回收,需要用后释放:

    [stu release];

    调用方法不用括号:

    [stu setAge:100];

    整个调用代码:

     1 #import <Foundation/Foundation.h>
     2 
     3 #import "Student.h"
     4 
     5 int main(int argc, const char * argv[])
     6 
     7 {
     8 
     9     @autoreleasepool {
    10 
    11         Student *stu = [[Student alloc] init];
    12 
    13         [stu setAge:100];
    14 
    15         int age = [stu getAge];
    16 
    17         NSLog(@"%i", age);
    18 
    19         [stu release];
    20 
    21     }
    22 
    23     return 0;
    24 
    25 }

    实际上,OC中的get方法不推荐使用get前缀,都是直接写变量名,比如[stu age];。

    如果要定义有多个参数的方法可以:

    1 - (void)setAge:(int)newAge andNo:(int)newNo{
    2 
    3     age = newAge;
    4 
    5     no = newNo;
    6 
    7 }

    调用:

    [stu setAge:100 andNo:1];

    也就是说方法名可以拆成多个部分。

    注意,这里的方法的方法名是setAge:andNo:,冒号也是方法名的一部分。

     

    OC也支持点语法,比如:person.age = 10;

    不过这句话并不是java那样给成员变量赋值,而是调用了对应的set方法,在编译的时候会自动转化为方括号的语法。如果点语法在等号左边,则调用set方法,在右边则调用get方法。为了和成员变量加以区分,OC推荐成员变量都以_开头。在set方法当中,绝对不能调用self.age=newAge,根据上面的解释,这种写法会死循环。同样在get方法里也不能return self.age。在OC当中用点语法来表示get/set方法。

    在点语法当中,

     

    OC中的init是一个对象方法,返回id类型,可以自己写构造方法,自己写构造函数的时候需要先调用父类构造函数,由于在内存分配时可能失败,所以要判nil,严格的实现写法如下:

     1 -(id)initWithAge:(int)age andNo:(int)no {
     2 
     3     if (self = [super init]){
     4 
     5         self.age = age;
     6 
     7         self.no = no;
     8 
     9     }
    10 
    11     return self;
    12 
    13 }

    调用如下:

    Student *stu = [[Student alloc] initWithAge:10 andNo:2];

    注意OC当中构造函数并没有要求函数名和类名一样,其中点语法调用的是set方法,不存在死循环问题。

     

    如果没有构造方法,可以用下面的语法来创建对象,这是个简写的语法,不推荐这么用。

    Student *stu = [Student new];

     

    可以通过NSLog(@"%@", stu);语句打印一个对象的内存地址,如果想自己重写输出格式,需要复写如下方法:

    1 -(NSString *)description{
    2 
    3     NSString *str = [NSString stringWithFormat:@"age is %i and no is %i", self.age, self.no];
    4 
    5     return str;
    6 
    7 }

    可以让一个变量自动释放内存,需要多调用一个autorelease方法,比如:

    Student *stu = [[[Student alloc] initWithAge:10 andNo:2] autorelease];

    通常情况下,使用系统自带的一些静态方法创建的对象都是可以自动释放的。

     

    OC的成员变量默认是protected的,子类可以访问。对于成员变量,提倡使用get和set方法。OC访问权限只有@public,@protected和@private三种,可以如下声明:

    1 @interface Student : NSObject {
    2 
    3     @public
    4 
    5     int _age;
    6 
    7     int _no;
    8 
    9 }

    这样声明的话,两个变量全是public的,这样就需要在外部使用一些C++语法访问了,是不提倡这样做的。

    关于方法的权限,因为别人要使用你的类,所以包含在.h文件里的就是公有方法,如果直接把方法写在.m文件中,就是私有方法了。

    如果想直接访问成员变量,那么它首先是public的,然后需要使用->符号访问。

     

    在OC方法当中,动态方法当中使用self,谁调用这个方法self就是谁,在静态方法当中,self代表的是当前类。

     

    实际上,OC自身提供了属性机制:

     1 #import <Foundation/Foundation.h>
     2 
     3 @interface Student : NSObject {
     4 
     5     int _age;
     6 
     7 }
     8 
     9 @property int age;
    10 
    11 @end

    当编译器遇到原型关键字时,会自动把这行代码展开成get和set方法的“声明”。但是.h里没有实现,所以需要在.m里用一句话实现:

    1 #import <Foundation/Foundation.h>
    2 
    3 #import "Student.h"
    4 
    5 @implementation Student
    6 
    7 @synthesize age;
    8 
    9 @end

    更灵活的一点是,如果通过synthesize实现了get和set方法,那么如果在.h文件里找不到同名的变量,会自动生成一个“同名”的“私有”的成员变量,所以在.h文件里这个成员变量也可以省略了。

    也就是说@synthesize age生成的成员变量是age,但是为了区分成员变量和get方法,通常习惯把成员变量定义成_age,所以为了让生成的age使用_age,所以要改成:

    @synthesize age = _age;

    在这种方式下,是不会生成一个叫age的同名成员变量的,这时会生成一个叫_age的成员变量(注意是私有的而不是保护的)。也就是说一个完整的封装是:

     1 @interface Student : NSObject {
     2 
     3     int _age;
     4 
     5 }
     6 
     7 @property int age;
     8 
     9 @end
    10 
    11 #import <Foundation/Foundation.h>
    12 
    13 #import "Student.h"
    14 
    15 @implementation Student
    16 
    17 @synthesize age = _age;
    18 
    19 @end

    在XCode4.5以上的版本当中,连synthesize那一句都可以不写了,直接在.h里声明一个变量就完事了,而且在这种方式下,访问的是私有的_age成员变量!

    如果我们手动实现了get或set方法,那么synthesize就不会生成对应的方法了。

     

    所有继承了NSObject的对象都需要内存管理,OC在对象的内部维护一个整数作为引用计数器,当它为0时会回收这个对象。当对象创建完时(alloc,new,copy),这个数字为1。给对象发送retain或release消息,可以让计数器+1或-1.当一个对象计数器为0时,系统会自动调用dealloc消息,可以重写这个方法做内存释放控制,重写时在最后面调用父类的这个方法,但是不要人工调这个方法。可以发送retainCount消息获取引用计数器值。在对象被回收之后,不要多次release,否则会导致野指针错误。

    OC的ARC机制全称自动引用计数,意思是把所有release之类的工作全交给系统来管理。

     

    对于对象的聚合/组合,比如Student拥有Book,这种方式是不推荐的:

    1 Student *stu = [[Student  alloc] initWithAge:10];
    2 
    3 Book *book = [[Book alloc] initWithPrice:3.5];
    4 
    5 stu.book = book;
    6 
    7 [book release];
    8 
    9 [stu release];

    是因为点语法调用的setter方法并没有改变book对象的引用计数,所以之后两个对象才会被正常释放并没有内存泄露。下面看一个例子:

      1 //book.h
      2 
      3 #import <Foundation/Foundation.h>
      4 
      5 @interface Book : NSObject
      6 
      7 @property float price;
      8 
      9 -(id)initWithPrice:(float)price;
     10 
     11 @end
     12 
     13 //book.m
     14 
     15 #import "Book.h"
     16 
     17 @implementation Book
     18 
     19 -(id)initWithPrice:(float)price{
     20 
     21     if (self == [super init]){
     22 
     23         _price = price;
     24 
     25     }
     26 
     27     return self;
     28 
     29 }
     30 
     31 -(void)dealloc{
     32 
     33     NSLog(@"book:%f 被销毁了", _price);
     34 
     35     [super dealloc];
     36 
     37 }
     38 
     39 @end
     40 
     41 //student.h
     42 
     43 #import <Foundation/Foundation.h>
     44 
     45 #import "Book.h"
     46 
     47 @interface Student : NSObject {
     48 
     49     //这里需要定义一个成员变量,因为自己写了get和set方法
     50 
     51     Book *_book;
     52 
     53 }
     54 
     55 @property int age;
     56 
     57 -(id)initWithAge:(int)age;
     58 
     59 -(Book *)book;
     60 
     61 -(void)setBook:(Book *)book;
     62 
     63 -(void)readBook;
     64 
     65 @end
     66 
     67 //student.m
     68 
     69 #import "Student.h"
     70 
     71 @implementation Student
     72 
     73 -(id)initWithAge:(int)age{
     74 
     75     if (self == [super init]){
     76 
     77         _age = age;
     78 
     79     }
     80 
     81     return self;
     82 
     83 }
     84 
     85 -(Book *)getBook {
     86 
     87     return _book;
     88 
     89 }
     90 
     91 -(void)setBook:(Book *)book {
     92 
     93     //需要先判断,防止重复赋值,同时防止retain一个已释放的野指针
     94 
     95     if(_book != book){
     96 
     97         //先释放旧的成员变量,OC中即便释放nil也不会出空指针错误
     98 
     99         //但是释放一个野指针就会报错
    100 
    101         [_book release];
    102 
    103         //必须要手动给子对象的引用计数+1
    104 
    105         _book = [book retain];
    106 
    107     }
    108 
    109 }
    110 
    111 -(void)readBook{
    112 
    113     NSLog(@"当前读的书是:%f", _book.price);
    114 
    115 }
    116 
    117 -(void)dealloc{
    118 
    119     //在类的set方法里retain,类自己就要负责销毁
    120 
    121     [_book release];
    122 
    123     NSLog(@"student:%i 被销毁了", _age);
    124 
    125     //最后调用父类的释放方法
    126 
    127     [super dealloc];
    128 
    129 }
    130 
    131 @end
    132 
    133 //main.m
    134 
    135 #import <Foundation/Foundation.h>
    136 
    137 #import "Student.h"
    138 
    139 #import "Book.h"
    140 
    141 void test(Student *stu){
    142 
    143     Book *book = [[Book alloc] initWithPrice:3.5];
    144 
    145     stu.book = book;
    146 
    147     //在哪个方法里分配,就在哪个方法里释放
    148 
    149     [book release];
    150 
    151     stu.book = book;
    152 
    153    
    154 
    155     Book *book2 = [[Book alloc] initWithPrice:4.5];
    156 
    157     //经常会重新调用set方法,所以它的内部负责释放先前的book
    158 
    159     stu.book = book2;
    160 
    161     [book2 release];
    162 
    163 }
    164 
    165 int main(int argc, const char * argv[])
    166 
    167 {
    168 
    169     @autoreleasepool {
    170 
    171         Student *stu = [[Student  alloc] initWithAge:10];
    172 
    173         test(stu);
    174 
    175         [stu readBook];
    176 
    177         [stu release];
    178 
    179     }
    180 
    181     return 0;
    182 
    183 }

    如果一个类持有另一个类,而且在.h文件里使用#import "Book.h"导入的话,会把.h全拷过去,性能会受影响,而且getset等方法也会暴露出去,一般不这么写,习惯在.h文件里使用关键字:@class Book;,只要知道Book是个类就可以了,在.m文件里真正要用到类里的方法时,再使用#import "Book.h"来获取其中的方法。一个典型的错误就是A类improt了B类,B也improt了A类,这样就会死循环而报错。而且,如果有多个文件同时improt了一个头文件,那么一旦这个头文件发生了改变,其他引用它的文件都要重新编译,而@class不存在这个问题。

    需要注意的是,如果是继承某个类,就一定要improt头文件,因为这样才能知道它里面定义了什么方法。如果只是定义成员变量或属性,就@class。

     

    假设这时候Student类里有很多对象做属性,那么代码是这样:

     1 #import "Student.h"
     2 
     3 #import "Book.h"
     4 
     5 #import "Card.h"
     6 
     7 @implementation Student
     8 
     9 -(void)setBook:(Book *)book{
    10 
    11     if (_book != book){
    12 
    13         [_book release];
    14 
    15         _book = [book retain];
    16 
    17     }
    18 
    19 }
    20 
    21 -(void)setCard:(Card *)card{
    22 
    23     if (_card != card){
    24 
    25         [_card release];
    26 
    27         _card = [card retain];
    28 
    29     }
    30 
    31 }
    32 
    33 -(void)dealloc{
    34 
    35     [_book release];
    36 
    37     [_card release];
    38 
    39     [super dealloc];
    40 
    41 }
    42 
    43 @end

    这些内存管理的set方法太啰嗦了,所以可以不手写这些set方法,而在属性定义的时候这样:

     1 @class Book;
     2 
     3 @class Card;
     4 
     5 @interface Student : NSObject
     6 
     7 @property (retain) Book *book;
     8 
     9 @property (retain) Card *card;
    10 
    11 @end

    这里的retain代表先release旧值,然后retain新值,就不用写内存安全的成员对象的set方法了。所以所有OC对象最好都这么写,但是如果不写,就会生成不管理内存的标准getset方法。

    对于常量类型的成员变量,有这样的写法:

    @property (assign) int age;

    这个assign可以不写,默认就是这样,而且不能写retain,非OC对象不需要管理内存。

    Property后面可以带多个参数,用逗号间隔,比如:

    @property (nonatomic,retain) Card *card;

    这里可以写的属性有三种:

    getter的处理:readwrite(默认)/readonly(只生成get方法),

    setter的处理:assign(默认,直接赋值)/retain(先释放后ratain)/copy(先释放后copy),

    原子性:atomic(默认,给getset方法加锁,线程安全)/nonatomic(禁止多线程保护,性能更好)。通常iphone开发不考虑线程安全。

    对于BOOL类型,需要保证get方法变成IsXxxx,需要加上参数:

    @property (nonatomic, getter = isRich) BOOL rich;

     

    OC有自动释放池机制,这与java垃圾回收不同,在池子释放时,会对池子里所有的对象调用一次release方法(并不是销毁)。OC对象只要发送一条autorelease消息,OC就会把这个对象添加到释放池当中。所以说autorelease实际是把对象的释放延迟到池子释放了,但是它并不会改变计数器。

    最常用的写法是:

    1     @autoreleasepool {
    2 
    3         Student *stu = [[[Student alloc]init]autorelease];
    4 
    5     }

    在OC中有一个潜规则,用来快速创建一个对象的静态方法和类名相同,而且静态方法都自动释放,比如有一个创建Student的静态方法:

    1 @interface Student : NSObject
    2 
    3 +(id)student;
    4 
    5 @end

    实现的时候一定要注意自动释放:

    1 +(id)student {
    2 
    3     return [[[Student alloc]init]autorelease];
    4 
    5 }

    静态方法一般都不需要我们手动来管理内存。

     

    注意点:

    在ARC下,只能使用@符号创建一个释放池。

    不要把大量循环操作放在释放池下,因为这会导致大量循环内的对象没有被回收,这种情况下应该手动写release代码。

    尽量避免对大内存对象使用autorelease,否则会延迟大内存的回收。

    ios中很多对象都已经自动释放了,不需要手动再release。

     

    Category(分类)可以动态地给已经存在的类添加方法,类似C#的扩展方法。需要新建一个文件,类型是OCcategory,在Category on当中选择目标类,会生成“类名+分类名”.h和.m两个文件,生成的类名右边括号里就是分类名。需要注意的是这个类的.h文件里必须import原始类,不能@class,原因是要知道原先类里有什么方法。

     1 #import "Student.h"
     2 
     3 @interface Student (Test)
     4 
     5 -(void)test2;
     6 
     7 @end
     8 
     9 #import "Student+Test.h"
    10 
    11 @implementation Student (Test)
    12 
    13 -(void)test2{
    14 
    15     NSLog(@"调用了test2方法");
    16 
    17 }
    18 
    19 @end
    20 
    21 #import <Foundation/Foundation.h>
    22 
    23 #import "Student.h"
    24 
    25 #import "Student+Test.h"
    26 
    27 int main(int argc, const char * argv[])
    28 
    29 {
    30 
    31     @autoreleasepool {
    32 
    33         Student *stu = [Student student];
    34 
    35         [stu test2];
    36 
    37     }
    38 
    39     return 0;
    40 
    41 }

     

    Protocol(协议)类似于C#/java中的接口,可以声明方法,与java不同的是实现类可以只实现一部分方法。在OC中的潜规则是协议名都是“Xxxelegate”。

    OC的最根本协议叫NSObject,定义一个协议需要实现这个根本协议,实现协议用尖括号表示,以一个按钮点击监听器做例子:

      1 //Button.h
      2 
      3 #import <Foundation/Foundation.h>
      4 
      5 //为了让协议用Button做参数,声明这个类
      6 
      7 @class Button;
      8 
      9 //定义一个协议,实现基础协议,以Delegate结尾
     10 
     11 @protocol ButtonDelegate <NSObject>
     12 
     13 //定义协议的点击方法,顺便把触发的按钮传进来
     14 
     15 -(void)onClick:(Button *)btn;
     16 
     17 @end
     18 
     19 //定义一个按钮类
     20 
     21 @interface Button : NSObject
     22 
     23 //定义一个遵循协议的delegate属性,遵循协议用尖括号表示
     24 
     25 //相当于在java当中定义一个接口类型的属性
     26 
     27 @property (nonatomic, retain) id<ButtonDelegate> delegate;
     28 
     29 //按钮有一个模拟的点击方法,用于触发协议中的onClick
     30 
     31 -(void)click;
     32 
     33 @end
     34 
     35 //Buttom.m
     36 
     37 //导入按钮的头文件
     38 
     39 #import "Button.h"
     40 
     41 //按钮的实现
     42 
     43 @implementation Button
     44 
     45 //为防止内存泄露需要先释放协议
     46 
     47 -(void)dealloc{
     48 
     49     [_delegate release];
     50 
     51     [super dealloc];
     52 
     53 }
     54 
     55 //按钮点击的模拟方法
     56 
     57 -(void)click{
     58 
     59     //OC语法:判断代理有没有实现onClick:方法
     60 
     61     if ([_delegate respondsToSelector:@selector(onClick:)]){
     62 
     63         //调用协议当中的方法,并把sender传进去
     64 
     65         [_delegate onClick:self];
     66 
     67     }
     68 
     69 }
     70 
     71 @end
     72 
     73 //Buttom.m
     74 
     75 //导入按钮的头文件
     76 
     77 #import "Button.h"
     78 
     79 //按钮的实现
     80 
     81 @implementation Button
     82 
     83 //为防止内存泄露需要先释放协议
     84 
     85 -(void)dealloc{
     86 
     87     [_delegate release];
     88 
     89     [super dealloc];
     90 
     91 }
     92 
     93 //按钮点击的模拟方法
     94 
     95 -(void)click{
     96 
     97     //OC语法:判断代理有没有实现onClick:方法
     98 
     99     //这个_delegate是主函数通过set方法赋值进去的
    100 
    101     if ([_delegate respondsToSelector:@selector(onClick:)]){
    102 
    103         //调用协议当中的方法,并把sender传进去
    104 
    105         [_delegate onClick:self];
    106 
    107     }
    108 
    109 }
    110 
    111 @end
    112 
    113 //ButtonListener.h
    114 
    115 #import <Foundation/Foundation.h>
    116 
    117 //因为要用到协议所以提前声明
    118 
    119 @protocol ButtonDelegate;
    120 
    121 //尖括号表示实现协议
    122 
    123 @interface ButtonListener : NSObject <ButtonDelegate>
    124 
    125 @end
    126 
    127 //ButtonListener.m
    128 
    129 //导入监听器自身头文件
    130 
    131 #import "ButtonListener.h"
    132 
    133 //为使用协议的方法导入协议所在的头文件
    134 
    135 #import "Button.h"
    136 
    137 //实现的地方就不用尖括号协议名了
    138 
    139 @implementation ButtonListener
    140 
    141 -(void)onClick:(Button *)btn {
    142 
    143     NSLog(@"按钮-%@被点击了", btn);
    144 
    145 }
    146 
    147 @end
    148 
    149 //main.m
    150 
    151 #import <Foundation/Foundation.h>
    152 
    153 #import "Button.h"
    154 
    155 #import "ButtonListener.h"
    156 
    157 int main(int argc, const char * argv[])
    158 
    159 {
    160 
    161     @autoreleasepool {
    162 
    163         ButtonListener *listener = [[[ButtonListener alloc]init]autorelease];
    164 
    165         Button *btn = [[[Button alloc]init]autorelease];
    166 
    167         Button *btn2 = [[[Button alloc]init]autorelease];
    168 
    169         btn.delegate =listener;
    170 
    171         btn2.delegate =listener;       
    172 
    173         [btn click];
    174 
    175         [btn2 click];
    176 
    177     }
    178 
    179     return 0;
    180 
    181 }

    通常建议建立一个OCProtocol单独保存协议,只有.h没有.m,因为它不需要实现类。

    如果需要同时实现两个协议,则<协议1,协议2>。

    如果需要控制协议里的方法必须要实现,则需要在方法的上面加上@required,这样一来,这个标记下的所有方法都必须实现了,但是即便标记了,也可以不实现,编译器不会报错,因为C语言语法弱。选择性实现的用@optional表示,默认是required(废的)。

    判断一个类是否实现了协议,有如下方法:

    if ([listener conformsToProtocol:@protocol(ButtonDelegate)])

     

    Block封装了一段代码,可以在任何时候执行,类似于函数指针,也类似C#的Func和Action,它用尖括号定义,可以做参数注入lambda,也可以做回调,比如:

     1 int (^Sum)(int,int) = ^(int a, int b) {
     2 
     3         return a + b;
     4 
     5 };
     6 
     7 int a = Sum(10,11);
     8 
     9 在block里是可以使用花括号外的变量的,但是不能修改它,类似java在外部加一个final,除非加一个关键字__block就能改变了,比如:
    10 
    11 void test() {
    12 
    13     __block int c = 1;
    14 
    15     int (^Sum)(int,int) = ^(int a, int b) {
    16 
    17         c = 10;
    18 
    19         return a + b + c;
    20 
    21     };
    22 
    23     NSLog(@"%i", Sum(1,2));
    24 
    25 }

    另外可以提前声明block的类型:

    1 typedef int (^MySum) (int,int);

    这样就可以用这个类型来定义block了:

    1 MySum sum = ^(int a, int b) {
    2 
    3     return a+b;
    4 
    5 };

    下面是一个block作为监听器属性的例子,可以理解为从调用层set一个lambda表达式进去:

     1 //main.m
     2 
     3 #import <Foundation/Foundation.h>
     4 
     5 #import "Button.h"
     6 
     7 int main(int argc, const char * argv[])
     8 
     9 {
    10 
    11     @autoreleasepool {
    12 
    13         Button *btn = [[[Button alloc] init] autorelease];
    14 
    15         btn.block = ^(Button *btn) {
    16 
    17             NSLog(@"按钮-%@被点击了", btn);
    18 
    19         };
    20 
    21         [btn click];
    22 
    23     }
    24 
    25     return 0;
    26 
    27 }
    28 
    29 //buttom.h
    30 
    31 #import <Foundation/Foundation.h>
    32 
    33 @class Button;
    34 
    35 //定义一个block的类型
    36 
    37 typedef void (^ButtonBlock) (Button *);
    38 
    39 @interface Button : NSObject
    40 
    41 //定义一个block的属性
    42 
    43 @property (nonatomic, assign) ButtonBlock block;
    44 
    45 -(void)click;
    46 
    47 @end
    48 
    49 //Button.m
    50 
    51 #import "Button.h"
    52 
    53 @implementation Button
    54 
    55 -(void)click{
    56 
    57     _block(self);
    58 
    59 }
    60 
    61 @end

    常用结构体

    1 typedef struct _NSRange {
    2 
    3     NSUInteger location;
    4 
    5     NSUInteger length;
    6 
    7 } NSRange;

    表示一个范围,比如字符串中某个子串的位置范围。

    结构体可以直接赋值:

    NSRange range = {.location = 7, .length = 3};

    也可以使用函数(常用):

    NSRange ranges = NSMakeRange(7,3);

    可以用函数把NSRange转成字符串:

    NSStringFromRange(range) 

    struct CGPoint {

      CGFloat x;

      CGFloat y;

    };

    typedef struct CGPoint CGPoint;

    typedef CGPoint NSPoint;

    NSPoint表示一个点。

    构建函数:

    NSMakePoint(10,9);

    CGPointMake(10,9);(常用)

    快速打印使用函数:

    NSStringFromPoint(point)

     

    struct CGSize {

      CGFloat width;

      CGFloat height;

    };

    typedef struct CGSize CGSize;

    typedef CGSize NSSize;

    表示宽度和高度,同样有如下方法:

    NSMakeSize(10, 8);

    CGSizeMake(10, 8);(常用)

    NSStringFromSize(size);

     

    struct CGRect {

      CGPoint origin;

      CGSize size;

    };

    typedef struct CGRect CGRect;

    typedef CGRect NSRect;

    存储位置和尺寸,也就是一个矩形范围。

    创建:

    NSMakeRect(10, 10, 80, 80);

    CGRectMake(10, 10, 80, 80);(常用)

    打印:

    NSStringFromRect(rect)

     

    字符串:

        //字符串常量,不用管内存

        NSString *str1 = @"A String";

       

        //常规方法

        NSString *str2 = [[NSString alloc] init];

        str2 = @"A String";

        [str2 release];

       

        //构造方法

        NSString *str3 = [[NSString alloc] initWithString:@"A String"];

        [str3 release];

        //对应的静态方法,不需要管理内存(推荐使用静态方法)

        str3 = [NSString stringWithString:@"A String"];

       

        //转化C语言的字符串

        NSString *str4 = [[NSString alloc] initWithUTF8String:"A String"];

        [str4 release];

        str4 = [NSString stringWithUTF8String:"A String"];

       

        //格式化创建

        NSString *str5 = [[NSString alloc] initWithFormat:@"My age is %i and height is %.2f", 19, 1.55f];

        [str5 release];

    str5 = [NSString stringWithFormat:@"My age is %i and height is %.2f", 19, 1.55f];

     

    可以从文件里读字符串:

     1 int main(int argc, const char * argv[])
     2 
     3 {
     4 
     5     @autoreleasepool {
     6 
     7         NSError *error;
     8 
     9         NSString *path = @"/Users/mac/Desktop/1.txt";
    10 
    11         NSString *str = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
    12 
    13         if (error == nil) {
    14 
    15             NSLog(@"%@", str);
    16 
    17         } else {
    18 
    19             NSLog(@"%@", error);
    20 
    21         }
    22 
    23     }
    24 
    25     return 0;
    26 
    27 }

    这个方法的要求一个NSError**类型的参数,也就是需要一个指向指针的指针,所以就需要传一个指针的地址过去,因为调用函数时产生了一个临时的指针型变量,所以对这个临时变量修改,是不会反映到外部变量的。

    也可以通过URL读取:

    1 NSURL *url = [NSURL URLWithString:@"http://www.badu.com"];
    2 
    3 NSString *str = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];

    如果需要定义一个方法改变字符串,应该这样写:

     1 void change(NSString **str){
     2 
     3     *str = @"456";
     4 
     5 }
     6 
     7 int main(int argc, const char * argv[])
     8 
     9 {
    10 
    11     @autoreleasepool {
    12 
    13         NSString *str = @"123";
    14 
    15         change(&str);
    16 
    17         NSLog(@"%@", str);
    18 
    19     }
    20 
    21     return 0;
    22 
    23 }

    将字符串写入文件如下:

    1 NSString *str = @"123456";
    2 
    3 NSString *path = @"/Users/mac/Desktop/1.txt";
    4 
    5 NSError *error;
    6 
    7 [str writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error];

    第二个参数是原子性,如果是yes,则会将字符串写入一个临时文件,全都写完之后剪切到目标文件,如果写临时文件中途报错,临时文件就会被删除,更加安全。如果是非原子性,则直接写到目标文件。

     

    其他常用方法:

     1 NSString *str = @"guangDong";
     2 
     3 NSLog(@"%@",[str uppercaseString]);
     4 
     5 NSLog(@"%@",[str lowercaseString]);
     6 
     7 //首字母变大写,其他字母全变小写
     8 
     9 NSLog(@"%@",[str capitalizedString]);
    10 
    11  
    12 
    13 //比较字符串内容
    14 
    15 BOOL result = [@"abc" isEqualToString:@"ABC"];
    16 
    17 //比大小,右边的大是升序返回1,左边的大是降序返回0
    18 
    19 NSComparisonResult result1 = [@"abc" compare:@"ABC"];
    20 
    21 //忽略大小写比较
    22 
    23 NSComparisonResult result2 = [@"abc" caseInsensitiveCompare:@"ABC"];
    24 
    25  
    26 
    27 NSString *str = @"123456.txt";
    28 
    29 //startWith
    30 
    31 BOOL result1 = [str hasPrefix:@"123"];
    32 
    33 //endWith
    34 
    35 BOOL result2 = [str hasSuffix:@"txt"];
    36 
    37 //搜索
    38 
    39 NSRange range = [str rangeOfString:@"345"];
    40 
    41 if (range.location == NSNotFound) {
    42 
    43     //没有找到
    44 
    45 }
    46 
    47  
    48 
    49 //截取
    50 
    51 [str substringFromIndex:3];
    52 
    53 [str substringToIndex:5];
    54 
    55 [str substringWithRange:NSMakeRange(2, 4)];
    56 
    57 //split
    58 
    59 NSArray *arr = [str componentsSeparatedByString:@","];
    60 
    61     return 0;
    62 
    63 }

    将数组拼接成路径pathWithComponents

    将路径分解成数组pathComponents

    是否是绝对路径,(本质是判断左边是不是/) isAbsolutePath

    返回最后一个目录lastPathComponent

    获取除了最后一个目录之外的路径stringByDeletingLastPathComponent

    在最后面拼接一个路径stringByAppendingPathComponent

    获取扩展名pathExtension

    删掉扩展名stringByDeletingPathExtension

    拼接扩展名stringByAppendingPathExtension

    //转字符串 

    NSString *str = @"100";

    //转int

    int a = [str intValue];

    //算字数

    int len = [str length];

    //取字符

    unichar c = [str characterAtIndex:0];

    //返回c语言的字符串

    char *s = [str UTF8String];

     

    除了NSString,还有一个NSMutableString是可变字符串,是NSString的子类。

     1 NSMutableString *str = [[NSMutableString alloc] initWithCapacity:8];
     2 
     3 [str setString:@"1234"];
     4 
     5 [str appendString:@"567890"];
     6 
     7 [str replaceCharactersInRange:[str rangeOfString:@"456"] withString:@"xxx"];
     8 
     9 [str insertString:@"yyy" atIndex:6];
    10 
    11 [str deleteCharactersInRange:[str rangeOfString:@"xxyy"]];
    12 
    13 NSLog(@"%@", str);
    14 
    15 [str release];

    NSArray是不可变数组,可以放任何OC对象,创建方法:

    1     //创建一个空数组(不能再往里加东西了)
    2 
    3     NSArray *array = [NSArray array];
    4 
    5     //创建一个有元素的数组,只允许装OC对象,也不能装nil,nil表示结束
    6 
    7 NSArray *array1 = [NSArray arrayWithObjects:@"a",@"b",nil];

    常用方法:

     1     //获取元素个数,实际是个get方法
     2 
     3     int count = array1.count;
     4 
     5 int count1 = [array1 count];
     6 
     7     //判断元素存在
     8 
     9     if ([array1 containsObject:@"a"]) { }
    10 
    11     //获取最后一个元素
    12 
    13     NSString *last = [array1 lastObject];
    14 
    15     //根据位置获取
    16 
    17     NSString *one = [array1 objectAtIndex:1];
    18 
    19     //获取元素位置
    20 
    21 int index = [array1 indexOfObject:@"b"];

    当把一个对象加入数组时,对象的计数器会+1,当数组被销毁时,会把里面每一个元素的计数器-1,所以不用管数组内部对象的内存问题。

     

    NSArray可以让每个对象调用一个方法,但是有一定的局限性,最多传递一个参数:

    1 [array makeObjectsPerformSelector:@selector(test)];
    2 
    3 [array makeObjectsPerformSelector:@selector(test2:) withObject:@"123"];
    4 
    5 OC类似js可以遍历数组:
    6 
    7 for (id obj in array) {
    8 
    9 }

    OC还有类似C#中ForEach执行lambda的方法:

     1     [array enumerateObjectsUsingBlock:
     2 
     3      ^(id obj, NSUInteger idx, BOOL *stop) {
     4 
     5          NSLog(@"%@-%zi", obj, idx);
     6 
     7          if (idx == 1) {
     8 
     9              *stop = YES;   //停止遍历
    10 
    11          }
    12 
    13 }];

    另外OC还可以通过objectEnumerator方法得到迭代器,可以使用nextObject等方法。

     

    数组可以拼接:

     1     //添加元素生成新的数组
     2 
     3     NSArray *array2 = [array arrayByAddingObject:@"3"];
     4 
     5     //添加另一个数组
     6 
     7     NSArray *array3 = [array arrayByAddingObjectsFromArray:[NSArray arrayWithObjects:@"3",@"4", nil]];
     8 
     9     //截取子元素
    10 
    11     NSArray *array4 = [array3 subarrayWithRange:NSMakeRange(2, 3)];
    12 
    13     //用分隔符拼接数组每个元素成字符串
    14 
    15    NSString *str = [array3 componentsJoinedByString:@","];

    数组可以排序:

        

     1 //用一个指定的比较方法进行排序
     2 
     3     NSArray *array2 = [array sortedArrayUsingSelector:@selector(compare:)];
     4 
     5     //用block设置比较方件进行排序
     6 
     7     NSArray *array3 = [array sortedArrayUsingComparator:^NSComparisonResult(Student *obj1, Student * obj2) {
     8 
     9         //比较算法
    10 
    11     }];
    12 
    13     //使用排序描述器进行复杂排序,这里读的是属性
    14 
    15     NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES];
    16 
    17     NSSortDescriptor *sortDescriptor2 = [NSSortDescriptor sortDescriptorWithKey:@"book.name" ascending:YES];
    18 
    19     NSArray *descs = [NSArray arrayWithObjects:sortDescriptor,sortDescriptor2,nil];
    20 
    21 NSArray *array4 = [array sortedArrayUsingDescriptors:descs];

    与数组对应的还有可变数组NSMutableArray,是NSArray的子类,可以使用addObject方法添加对象,也有对应的删除方法等。当对可变数组添加或删除对象时,会对对应对象进行retain或release操作。

     

    和数组类似,OC有NSDictionary来存放key-value对,有如下常用方法:

     1  //创建
     2 
     3     NSDictionary *dic = [NSDictionary dictionaryWithObject:@"v" forKey:@"k"];
     4 
     5     //最常用的
     6 
     7     NSDictionary *dic2 = [NSDictionary dictionaryWithObjectsAndKeys:@"v1",@"k1",@"v2",@"k2", nil];
     8 
     9     NSArray *objects = [NSArray arrayWithObjects:@"v1",@"v2",nil];
    10 
    11     NSArray *keys = [NSArray arrayWithObjects:@"k1",@"k2",nil];
    12 
    13 NSDictionary *dic3 = [NSDictionary dictionaryWithObjects:objects forKeys:keys];

    取值方法:

    id value = [dic objectForKey:@"k1"];

    另外有allKeys,allValues方法,但是词典并不是有序的。

    多个key可以对应一个value,可以使用allKeysForObject来获取所有的key。 

     1 //传统遍历
     2 
     3     for (id key in dic) {
     4 
     5         id value = [dic objectForKey:key];
     6 
     7         NSlog(@"%@", key);
     8 
     9     }
    10 
    11     //key迭代器
    12 
    13     NSEnumerator *enumer = [dic keyEnumerator];
    14 
    15     id key = nil;
    16 
    17     while (key = [enumer nextObject]){
    18 
    19         NSlog(@"%@", key);
    20 
    21     }
    22 
    23     //object迭代器
    24 
    25     //[dic objectEnumerator]
    26 
    27     //block迭代器
    28 
    29     [dic enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
    30 
    31         //...
    32 
    33 }];

    对于词典的内存管理,不论是当做key还是value放入词典,都会把计数器+1,做为key的对象要实现NSCopying协议。在字典释放时,会自动把里面的东西-1。

     

    词典的子类是NSMutableDictionary是可变词典,有add方法:[dic setObject:@"v4" forKey:@"k4"];,也有相应的移除方法removeObjectForKey。

     

    NSNumber是对非OC对象的包装器,用于把int等类型的变量打包成对象放入集合,但是不支持自动打包解包。

    1 NSNumber *number = [NSNumber numberWithInt:10];
    2 
    3 int num = [number intValue];

     

    但是NSNumber不能包装结构体,所以需要用NSValue,它是NSNumber的父类,可以包装任何值,比如:

       

     1  //包装系统自带的结构体
     2 
     3     CGPoint point = CGPointMake(10, 10);
     4 
     5     NSValue *value= [NSValue valueWithPoint:point];
     6 
     7     //取值
     8 
     9     CGPoint point1 = [value pointValue];
    10 
    11     //包装自定义的结构体
    12 
    13     char *type = @encode(CGPoint);
    14 
    15     NSValue *value1 = [NSValue value:&point withObjCType:type];
    16 
    17     //取值
    18 
    19     CGPoint point2;
    20 
    21 [value1 getValue:&point2];

    NSNull用来表示空值,和nil不同,NSNull是一个OC对象,也有计数器和内存管理,这个NSNull对象是全局单例的,它只有一个方法:

    NSNull *n = [NSNull null];

     

    NSDate表示时间,返回当前时间是:

    NSDate *date = [NSDate date];

    从当前时间再增加一些秒数返回:

    NSDate *date = [NSDate dateWithTimeIntervalSinceNow:5];

    获取时间间隔:

    NSTimeInterval interval = [date1 timeIntervalSinceDate:date2];

     

    NSObject是所有类的父类,有很多常用方法:

    判断一个类是不是一个类(的子类),第二个参数class是一个指向结构体的指针:

    [date1 isKindOfClass:[NSDate class]];

    相比之下,isMemberOfClass方法的范围更小,只能判断是不是这个类,而不能判断是不是子类。

    conformsToProtocol方法判断是否实现了某个协议。

    间接调用方法:

    [stu performSelector:@selector(test)];

    这种间接调用可以传OC对象类型的参数,但是最多支持到两个参数:

    [stu performSelector:@selector(test2) withObject:@"abc"];

     

    OC支持反射创建类的对象:

    Student *stu = [[NSClassFromString(@"Student") alloc]init];

    也可以把一个类型变成字符串:

    NSString *str = NSStringFromClass([Student class]);

    根据字符串调用方法:

    [stu performSelector:NSSelectorFromString(@"test")];

    将方法名转为字符串:

    NSString *str2 = NSStringFromSelector(selector);

     

    copy让一个对象产生一个副本,修改副本不会修改原先的对象,需要支持copy的类需要实现NSCopying/NSMutableCopying协议,这两个协议分别用来创建不可变/可变副本。使用mutableCopy语法会返回新对象,但是对NSString对象使用copy语法会返回对象本身,因为它本身就是不能改的。copy是浅拷贝,mutableCopy是深拷贝。如果反过来用copy复制一个NSMutableString的话,会返回一个NSString,是深拷贝。也就是说,只有不可变拷贝为不可变时,才是浅拷贝。

     

    一个类的属性参数可以设置retain让set方法自动管理内存,比如:

    @property (nonatomic, retain) NSString *name;

    这里retain可以改成copy,则会在set方法内copy,而不是retain,如果是浅拷贝,那么就相当于retain了。这时set方法就会先release旧对象,之后copy新对象。

    如果一个对象里的属性retain了一个外部对象作为成员,那么外部的改变就会影响这个对象的属性,为了防止这种影响,需要使用copy策略,如果是NSString类型的成员,最常用的就是copy策略。

     

    让自己的类可以copy的话,需要实现NSCopying协议,实现copyWithZone方法,zone就是新的存储空间,这里创建的副本不要求释放。

     1 @interface Student : NSObject <NSCopying>
     2 
     3 @property (nonatomic, copy) NSString *name;
     4 
     5 - (id)copyWithZone:(NSZone *)zone;
     6 
     7 @end
     8 
     9 #import "Student.h"
    10 
    11 @implementation Student
    12 
    13 - (id)copyWithZone:(NSZone *)zone{
    14 
    15     Student *copy = [[Student allocWithZone:zone] init];
    16 
    17     copy.name = self.name;  //拷贝成员
    18 
    19     return copy;
    20 
    21 }
    22 
    23 @end

    如果说子类也需要copy的话,则需要复写父类的copy方法,在其中调用父类的copy方法之后给子类成员赋值。完整代码示例如下:

     1 //GoodStudent.m
     2 
     3 #import "GoodStudent.h"
     4 
     5 @implementation GoodStudent
     6 
     7 +(id)goodStudentWithAge:(int)age name:(NSString *)name {
     8 
     9     GoodStudent *good = [GoodStudent studentWithName:name];
    10 
    11     good.age = age;
    12 
    13     return good;
    14 
    15 }
    16 
    17 -(id)copyWithZone:(NSZone *)zone {
    18 
    19     GoodStudent *copy = [super copyWithZone:zone];
    20 
    21     copy.age = self.age;
    22 
    23     return copy;
    24 
    25 }
    26 
    27 @end
    28 
    29 //GoodStudent.m
    30 
    31 #import "GoodStudent.h"
    32 
    33 @implementation GoodStudent
    34 
    35 +(id)goodStudentWithAge:(int)age name:(NSString *)name {
    36 
    37     GoodStudent *good = [GoodStudent studentWithName:name];
    38 
    39     good.age = age;
    40 
    41     return good;
    42 
    43 }
    44 
    45 -(id)copyWithZone:(NSZone *)zone {
    46 
    47     GoodStudent *copy = [super copyWithZone:zone];
    48 
    49     copy.age = self.age;
    50 
    51     return copy;
    52 
    53 }
    54 
    55 @end
    56 
    57 //GoodStudent.h
    58 
    59 #import <Foundation/Foundation.h>
    60 
    61 #import "Student.h"
    62 
    63 @interface GoodStudent : Student
    64 
    65 @property (nonatomic, assign) int age;
    66 
    67 +(id)goodStudentWithAge:(int)age name:(NSString *)name;
    68 
    69 @end
    70 
    71 //GoodStudent.m
    72 
    73 #import "GoodStudent.h"
    74 
    75 @implementation GoodStudent
    76 
    77 +(id)goodStudentWithAge:(int)age name:(NSString *)name {
    78 
    79     GoodStudent *good = [GoodStudent studentWithName:name];
    80 
    81     good.age = age;
    82 
    83     return good;
    84 
    85 }
    86 
    87 -(id)copyWithZone:(NSZone *)zone {
    88 
    89     GoodStudent *copy = [super copyWithZone:zone];
    90 
    91     copy.age = self.age;
    92 
    93     return copy;
    94 
    95 }
    96 
    97 @end

     

    KVC

    是一种无须知道类型的间接属性访问方式,解除了赋值取值操作与类型数据结构的耦合,

    为了防止错误尽量用keyPath方法就好了。

    1 [p setValue:@10 forKey:@"age"];
    2 
    3 NSNumber *age = [p valueForKey:@"age"];
    4 
    5 [p setValue:@"100" forKeyPath:@"card.no"];
    6 
    7 NSString *cardno = [p valueForKeyPath:@"card.no"];

    KVC与词典有容易混淆的地方:

    1  NSMutableDictionary *dic = nil;
    2 
    3 //词典语法
    4 [dic setObject:<#(id)#> forKey:<#(id<NSCopying>)#>];
    5 
    6 //kvc语法
    7 
    8 [dic setValue:<#(id)#> forKey:<#(NSString *)#>];

    其他用法如:

    通过路径快速抽取一个book数组中的所有价格(类似C#的linq)

    NSMutableArray *prices = [books valueForKeyPath:@"price"];

     

    KVO是用来监听对象值改变的监听器机制,首先定义监听器:

     1 #import "PersonObserver.h"
     2 
     3 @implementation PersonObserver
     4 
     5 //当监听的某个属性发生改变时调用
     6 
     7 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{    
     8 
     9 }
    10 
    11 @end

    主函数:

     

     1     @autoreleasepool {
     2 
     3          Person *p = [[Person alloc]init];
     4 
     5          PersonObserver *po = [[PersonObserver alloc]init];
     6 
     7          [p addObserver:po forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
     8 
     9          
    10 
    11          //...
    12 
    13          
    14 
    15          [p removeObserver:po forKeyPath:@"name"];
    16 
    17     }

    这段代码定义了p对象并且给p的name属性设定了一个监听器,第三个参数是用来传递给监听器的change参数,能获取到改变前后的值,context是可以传入的自定义对象。

    监听器需要及时释放(否则会提示警告信息)。

  • 相关阅读:
    举例阐述游戏战斗系统设计的基本规则
    角色动作控制接口的设计
    游戏动作感设计初探
    c#扩展方法
    Bundle Adjustment光束平差法概述
    Levenberg-Marquardt
    OpenCV2:Mat属性type,depth,step
    opencv surf特征点匹配拼接源码
    OpenCV仿射变换+投射变换+单应性矩阵
    在OpenCV for Android 2.4.5中使用SURF(nonfree module)
  • 原文地址:https://www.cnblogs.com/vijozsoft/p/5110937.html
Copyright © 2011-2022 走看看