(一)Xcode功能概述:
在之前的学习中,我们普遍多用终端生成.m,在里面进行编程。在接触到Xcode后,新建project,便可以使用Xcode的
诸多辅助功能。Xcode是自动保存,并且写一行代码编译一行代码的。所以当你写完代码,就会自动检测你的语法是否
正确,给予你相应的警告或者报错。并且Xcode可以省去大量写垃圾代码的时间,让程序员把工作重心放到别处。
- Use of undeclared identifier 'a' --->报错原因:使用了未定义的标示符a。
- Unused variable 'b' --->常见警告,提示你变量b没有使用到。
- 新建类 --->new fire --->Cocoa Class ---> SubClass of(继承) --->Xcode新版本和视频稍许有出入,创建方式和框架选择均不同。
- Class --->类,类名。
- Command +R快捷键,运行程序。
- 如果发现运行失败,但是代码都没有报错(没有红色),那么就是链接出错(linker error),也许是包含了.m文件重复定义,也许是缺少了.h文件找不到定义。
- Xcode可以自动生成成员变量的getter和setter(@property和@synthesize)。
- 断点调试,可以卡主某几行代码,选择不执行。然后点击运行,查看这时候变量,类的内存情况。
- 代码块保存,可以把一些常用的代码块保存起来,变成自己的代码块,方便随时调用。
- 注释标记, #pragma mark 今天就写到这里。 #pragma mark - 今天就写到这里。一般会选择把同一类的方法做一个注释。然后同一类方法的单独小方法在分别注释。
Xcode还有很多让人嗔目结舌的高级功能,目前先总结这些。以后就可以不需要用终端来控制程序了。下面我们投入
到OC语言核心与法的学习中。
(二)OC核心与法:
1.点语法:
OC中,直接访问成员变量只有一种方法,就是p->_age,用箭头直接访问,这要求_age相应的作用域可以被p访问到时
才能使用,如果作用域不对,_age会被Xcode画上一道横线告诉你无法直接访问。而点语法的核心,还是setter和getter
方法的调用。点语法的作用是为了让程序员更好的上手,苹果官方设计的语法。
p.age =10 == [p setAge:10] p.age == [p age];
当点语法赋值时,就会自动展开为set方法,没有赋值就展开为get方法,如果一个成员变量没有写setter和getter,那么
点语法也无法使用。我们在Xcode中实现以下点语法和注释功能。新建一个Car类,声明代码如下:
#import <Foundation/Foundation.h>
@interface Car : NSObject
{
NSString *_name;
int _no;
}
#pragma mark - 成员变量的setter和getter
#pragma mark name的setter和getter
- (void)setName:(NSString *)name;
- (NSString *)name;
#pragma mark no的setter和getter
- (void)setNo:(int)no;
- (int)no;
@end
方法实现如下:
#import "Car.h"
@implementation Car
- (void)setName:(NSString *)name
{
_name = name;
}
- (NSString *)name
{
return _name;
}
@end
主函数:
分析:进入主函数main,通过Car类的new方法(alloc init)创建一个新的Car对象c,通过点语法的setter赋值jack名
字,然后通过点语法的getter把拿到的名字赋值给字符串对象p,然后输出字符串对象p,得到结果jack。可见点语法不
是访问成员变量,只是单纯的变成setter和getter。
死循环:
#import "Car.h"
@implementation Car
- (void)setName:(NSString *)name
{
NSLog(@"%@------",self.name);
self.name=name;
}
- (NSString *)name
{
return _name;
//return self.name
}
@end
在getter和setter中使用self调用自己会引发死循环,因为无限进入这个setter或者getter。点语法的本质还是getter和setter。
2.成员变量作用域:
定义成员变量时出现了@public-->这是成员变量的一种作用域。代表任何地方都可以直接访问。成员变量一共有四种作用域。
@protected-->可以在当前类和子类的对象方法中直接访问。
@package-->同一个框架内可以访问。
注:
当成员变量定义在@interface中,什么都不写默认是prorected。当成员变量定义在@implementation中,什么都不写默
认是private。哪怕是写@public也没有用。因为文件都是包含.h头文件的。没有人包含.m文件,所以@implementation
中得成员变量只能是私有(除非和main函数写一起,能被调用者看到)。
当使用protected时,子类也可以直接访问,但是如果父类有@private类型,子类也是完全继承过来的,是也有这个类型的成员变量的。只不过是无法直接访问,但是可以通过点语法setter和getter访问。
一个类是允许没有声明只有定义的,但不推荐。类的@implementation和@interface中不允许定义同名的成员变量。会报错-->重复定义。
3.编译器特性--自动生成setter和getter
①:@property 首先property关键字可以帮助我们自动生成成员变量的声明,它和点语法一样,都是编译器特性,你让
他怎么做,他就怎么做。
当编译器遇到 @property int age时,会自动变成相应的getter和setter声明: - (void) setAge:(int)age; - (int)age;
当编译器遇到 @property int _age时,会自动变成相应的getter和setter声明: - (void) set_age:(int)_age; -(int)_age;
说白了,编译器会按照规格这么”抄写“下去。所以为了规格规范,不要在使用property时用_,这样生成的set方法不是标准的方法。
②:@synthesize synthesize后面跟的是一个property,目的是实现property生成的这个成员变量的setter和getter方法
的实现。写在@implementation和@end之间。 标准规格: @synthesize age = _age;其中,age指的是方法名字,后面
的_age指的是要访问的成员变量。这样,一行代码就可以实现对应成员半两的setter和getter方法的实现。如果只写
@synthesize age;那么就会默认访问age而不是_age,如果没有成员变量age,那么就会生成相应的私有的age成员变
量。当没有写成员变量时,Xcode就会自动生成setter和getter中要访问的成员变量,如上为生成int _age。但是这个
_age是私有的。是在@implementation中生成的。所以子类和main无法直接访问。
③:Xcode在4.x版本后,更新了功能。@property既可以生成成员变量的setter,getter方法声明,也可以生成相应方法
的实现和相应的成员变量。就是说 @property int age; --->做了三件事,生成成员变量_age(在@implementation中,
方法私用),生成_age的getter和setter方法的声明和实现。如果想让子类也能直接访问成员变量,只能在@interface中直
接定义成员变量。当定义了成员变量后Xcode就不会再自动生成。Xcode的原则是没有就生成,有就拿来用。
④:当同时写了set和get方法实现后,系统就不会自动生成成员变量了。系统生成的只是你没有的,当只写了set方法实
现,就会生成get方法实现和相应的成员变量。反之亦然。
4.万能指针id:
id类型是一个指针,某种意义上其实就是NSObject *,指针是用来操纵对象,或者保存对象的。所以id能够操纵任何OC
对象,并且不需要*(内部已经定义了)。
void (id a){ } 意味着这个函数可以传入任何OC对象作为参数。
@property id obj ;--->定义了一个id类型的_obj成员变量,并且实现了相应的getter和setter,此时,可以传入任何对象作为参数。
5.OC特有方法:构造方法
①new方法剖析
Person *p = [[Person alloc] init];
[person new]可以拆分为两句,通过类方法alloc分配存储空间给对象,通过对象方法init给这个分配好存储空间的对象赋
值。由此可见,alloc是+的类方法,init是 -的对象方法,他们联合起来就是类方法+new。但是new一般很少用,因为太
死板。new的初始化只有可能是所有成员变量值都为0,但有时候我们需求需要对象一创造出来就有属于自己的值。这
时候就用到了初始化init的重写。这也就是OC的构造方法。
[类 alloc] :返回一个分配了存储空间但是没有进行初始化的对象。
[对象 init] :返回初始化成功的对象。
②init
nit其实就是构造方法。构造方法是用来初始化对象的方法。init是写在NSObject中得对象方法,默认初始化所有成员变
量为0,如果想初始化时候就赋值,就要用到init的重写。去.m文件中重写init这个对象方法。
#import "Person.h"
@implementation Person
- (id)init
{
if(self = [super init])
{
_no =100; // 成员变量初始化
}
return self;
}
@end
如上为重写的标准规格。分析如下:首先一个对象初始化,一定是要先初始化父类的东西,在初始化自己的。因为它是
继承父类的,拥有父类的所有东西。所以要先执行[super init],初始化完毕父类对象时候,在赋值给自己self,这样就
拿到了父类的初始化对象。然后如果这时候self这个内存不为空,就是说确实初始化父类对象成功后,就可以在进行自
己成员变量的初始化。然后return这个初始化成功self本身对象。完成构造方法的重写。
由上我们可以得出结论:重写构造方法的目的就是为了让对象一创建出来成员变量就有一些固定的值。
③自定义构造方法
所谓自定义构造方法,那就是自定义一个关乎inti的方法,而不再是重写原有的init。这时候我们要遵循一个原则,父类
的属性交给父类方法去处理,子类的属性交给子类来处理。
自定义构造方法规范:
- 一定是对象方法,以 - 开头。
- 由于不知道传入参数是什么对象,返回值一般都是id类型
- 方法名一般都以initWith开头,init让人看到就知道是构造方法,With表示以什么来进行构造。
·现在我们通过代码来分析这种自定义构造方法的思想。创建Person类继承NSObject,创建Student类继承Person。
Person类中有_no._name两个成员变量,当我想每次定义person对象时候都可以自己传入一个名字,no进行初始化
时,需要自定义构造方法。
Person.h:
#import <Foundation/Foundation.h>
@interface Person :NSObject
@property int no;
@property NSString *name;
- (id)initWithNO:(int)no andName:(NSString *)name;
@end
Person.m:
#import "Person.h"
@implementation Person
- (id)init
{
if(self = [super init])
{
_no =100; // 成员变量初始化
_name =@"jack";
}
return self;
}
- (id)initWithNO:(int)no andName:(NSString *)name
{
if (self = [super init])
{
_no = no;
_name = name;
}
return self;
}
@end
main():
#import <Foundation/Foundation.h>
#import "Person.h"
int main()
{
Person *p = [[Person alloc] initWithNO:99 andName:@"梁萧"];
NSLog(@"%d----%@",p.no,p.name);
return 0;
}
count:
2015-04-0313:16:49.676 Person[1688:432254]99----梁萧
由此可见,自定义构造方法初始化成功。这是一个新的方法initWithNO : andName: --->传入相应的参数就可以进行初
始化。这个方法和之前的init是不冲突的。
下面,如果我们想让学生类拥有自己的一个_age属性,并且希望可以同时初始化Stundent类的年龄,no和姓名。这时
候需要在Student.m中构造一个方法来完成如上功能。
Student.h:
#import "Person.h"
@interface Student : Person
@property int age;
- (id) initWithNO:(int)no andName:(NSString *)name andAge:(int)age;
@end
Student.m:
#import "Student.h"
@implementation Student
- (id)initWithNO:(int)no andName:(NSString *)name andAge:(int)age
{
if(self = [super initWithNO:no andName:name])
{
_age = age;
}
return self;
}
@end
main():
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Student.h"
int main()
{
Student *s = [[Student alloc] initWithNO:19 andName:@"梁萧" andAge:28];
NSLog(@"%d---%d---%@",s.no,s.age,s.name);
return 0;
}
count:
2015-04-0313:28:03.437 Person[1710:439415]19---28---梁萧
由上构造类型的创建,我们体验到了这个思想,父类的属性交给父类方法去处理,子类的属性交给子类来处理。我的no
和name都是父类的成员变量,那重写初始化时候也调用父类的方法。体现出了面向对象的思想。
6.更改Xcode模板和注释:
注:我的Xcode一直提示我没有权限。哪怕我更改了Xcode所有属性为可读可改并且添加了管理员权限。还是无法更
改。此段方法暂且略过。
7.OC特有语法:分类
在不更改原类.h .m属性,并且不用继承的同时,想要给类增加一些方法,则是用到了分类。分类的使用非常广泛,分类
依赖于类,可以给某个类扩充方法。
一个完整的分类需要定义声明和实现。
声明: @interface 类(分类名)
实现: @implementation 类 (分类名)
分类作用:在不改变原有类的基础上,为类增加方法(包括对象方法和类方法)。
使用注意:
- 分类只能增加方法不能增加成员变量。
- 分类方法实现中可以访问原类中声明的成员变量。
- 分类可以重新实现原来类中得方法,但是由于分类优先级最高,会覆盖掉原方法。会警告。
- 方法调用优先级:分类(最后参与编译的)--->原来类--->父类
- .h是用来拷贝的,.m是参与编译的源代码文件,编译的都是.m文件。并且所有.m同时编译。
- 也可以给系统自带的类扩充分类,经常会用到这种功能,网上也有很多分类包供下载。
下面我们利用分类为NSString编写一个获取字符串数字个数的方法:
#import "NSString+num.h"
@implementation NSString (num)
+(int)numOfNsstring:(NSString *)str
{
int count = 0;
for (int i=0;i<str.length;i++)
{
unichar c = [str characterAtIndex:i];
if (c>'0'&& c<'9')
count++;
}
return count;
}
@end
分析代码,str.length是调用了NSString自己的对象方法获取字符串长度。[str characterAtIndex:]的意思是获取:位置的
单个字符。那么用FOr循环遍历这个字符串的每一个位置,如果是0到9之间则数量+1,成功实现了方法需求。
但是这不是最好的方法。面向对象的思想是谁干什么 就用相应的方法。如果我要获取一个字符串长度,那么最好的思想
是直接调用这个字符串的获取长度方法。
- (int)getNsstringLongth
{
return [NSString numOfNsstring:self];
}
@end
此时,调用只需:
int c = [@"dsdas1321" getNsstringLongth];
NSLog(@"%d---",c);
return 0;