类(class)和对象(object,也称为实例,instance),其中类是某一批对象的抽象,可以把类理解成某种概念;对象才是一个具体存在的实体,从这个意义上看,日常所说的人其实都是人的实例,而不是人类。
定义类
接口部分:
成员变量:用于描述该类的对象的状态数据
方法:用于描述该类的行为
定义成员变量:
类型 成员变量名
类型:OC允许的任何数据类型
成员变量名:以下划线开头_name;
声明方法的语法格式
方法类型标识符:+代表类方法,直接用类名调用,-代表实例方法,必须用对象调用。
方法返回值类型:可以是OC允许的任何数据类型。
方法签名关键字:由方法名、形参标签和冒号组成。
我们一般把类的接口部分放在.h文件
@interface MyClass : NSObject
{
NSString* _name;
int _age;
}
- (void) setName:(NSString*) name andAge: (int) age;
- (void) info;
- (void) foo;
- (MyClass*) grow;
+ (void) fc;
- (void) test: (NSString*) name, ...;
@end
类的实现部分:
类实现部分的类名必须与类接口部分的类名相同。
类实现部分也可以声明自己的成员变量,但只能在当前类里访问。
类实现部分必须为类声明部分的每个方法提供方法定义
类实现部分放在.m文件
#import "MyClass.h"
@implementation MyClass
{
int _testAttr;
}
- (void) setName: (NSString*) n andAge: (int) a
{
_name = n;
_age = a;
}
- (void) info
{
[self foo];
NSLog(@"我是一个人, 名字为:%@, 年龄为%d。", _name, _age);
}
- (void) foo
{
NSLog(@"输出结果为");
}
- (MyClass*) grow
{
_age++;
NSLog(@"年龄为%d", _age);
return self;
}
+ (void) fc
{
NSLog(@"fuchuang688");
}
- (void) test: (NSString*) name, ...
{
va_list argList;
if (name)
{
NSLog(@"%@", name);
va_start(argList, name);
NSString* arg = va_arg(argList, id);
while (arg) {
//打印每一个参数
NSLog(@"%@", arg);
arg = va_arg(argList, id);
}
}
va_end(argList);
}
@end
main函数
#import <Foundation/Foundation.h>
#import "MyClass.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyClass* class = [[MyClass alloc] init];
[class setName: @"付闯" andAge: 19];
[class info];
[[[class grow] grow] grow];
id p = [[MyClass alloc] init];
[p setName: @"fuchuang" andAge: 20];
[p info];
[MyClass fc];
[p test: @"付闯", @"fuchuang", @"fc"];
}
return 0;
}
对象和指针
Fraction *myFraction = [[Fraction alloc] init];
这段代码中,创建了一个Fraction对象,这个对象被赋给myFraction变量。其实,myFraction变量仅仅保存了Fraction对象在内存中的首地址。
注意:如果堆内存里的对象没有任何变量指向它,那么程序将无法再访问该对象。
self关键字
self关键字总是指向该方法的调用者(对象或类),当self出现在实例方法中时,self代表调用该方法的对象;self不能出现在类方法中。
self关键字最大的作用是让类中的一个方法访问该类的另一个方法或成员变量。
id类型
OC提供了一个id类型,任意类的对象都可以赋值给id类型的变量。当通过id类型的变量来调用方法时,OC将会执行动态绑定,它会在运行时判断该对象所属的类,并在运行时确定需要动态调用的方法。
形参个数可变方法
如果在定义方法时,在最后一个形参名后增加逗号和三点(,…),则表明该形参可以接受多个参数值。需要用到如下关键字:
va_list:一个类型,用于定义指向可变参数列表的指针变量。
va_start:一个函数,指定开始处理可变形参的列表,并让指针变量指向可变形参列表的第一个参数。
va_end:结束处理可变形参,释放指针变量。
va_arg:返回获取指针当前指向的参数的值,并将指针移动到下一个参数。
这这些内容在上面代码都有实现。
单例模式
在某些时候,程序多次创建某个类的对象没有任何意义,此时程序需要保证该类只有一个实例。如果一个类始终只能创建一个实例,则这个类被称为单例类。单例类可以通过static全局变量来实现,每次程序需要获取该实例时,都要先判断该static全局变量是否为nil,如果该全局变量为nil,则初始化一个实例并赋值给static全局变量;如果该全局变量不为nil,那么程序直接返回该全局变量指向的实例即可。
#import <Foundation/Foundation.h>
@interface FKSingleton : NSObject
+(id) instance;
@end
#import "FKSingleton.h"
@implementation FKSingleton
static id instance = nil;
+(id) instance {
//如果instance全局变量为nil
if (!instance) {
//创建一个Singleton实例,并将该实例赋给instance全局变量
instance = [[super alloc] init];
}
return instance;
}
@end
#import "FKSingleton.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//判断两次获取的实例是否相等,将返回1代表真
NSLog(@"%d", [FKSingleton instance] == [FKSingleton instance]);
}
return 0;
}
使用访问控制符
OC提供了4个访问控制符:@private、@package、@protected 和 @public。
@private(当前类访问权限):该成员变量只能在当前类的内部访问,在类的实现部分定义的成员变量默认为@private。
@package(相同映像访问权限):该成员变量可以在当前类以及当前类的同一个映像的任意地方访问。
@protected(子类访问权限):该成员变量可以在当前类、当前类的子类的任意地方访问,在类的接口部分定义的成员变量默认为@protected。
@public(公共访问权限):使成员变量可以在任意地方访问。
@private
@private访问控制符的作用非常明确,它将受该访问控制符限制的成员变量限制在当前内部(与在类实现部分定义的成员变量的作用域类似)
#import <Foundation/Foundation.h>
@interface FKPerson : NSObject {
//使用private限制成员变量
@private
NSString* _name;
int _age;
}
-(void) setName: (NSString*) name;//设置_name成员变量的值
-(NSString*) name;//设置_name成员变量的值
-(void) setAge: (int) age;//设置_age成员变量的值
-(int) age;//获取_age成员变量的值
@end
main()函数不可再直接修改FKPerson对象的_name和_age 两个成员变量,只能通过各自对应的setter方法来设置这两个成员变量的值
#import "FKPerson.h"
@implementation FKPerson
-(void) setName:(NSString *)name {
if([name length] >6 || [name length] < 2) {
NSLog(@"您设置的人名不符合要求");
return;
} else {
_name = name;
}
}
- (NSString*) name {
return _name;
}
-(void) setAge:(int)age {
if(_age != age) {
if (age > 100 || age < 0) {
NSLog(@"您设置的年龄不合法");
return;
} else {
_age = age;
}
}
}
- (int) age {
return _age;
}
@end
#import "FKPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//因为_age成员变量已隐藏,所以下面的语句编译错误
//p->_age = 1000;
FKPerson* p = [[FKPerson alloc] init];
[p setAge: 1000];
NSLog(@"未能设置_age成员变量时:%d", [p age]);
[p setAge: 30];
NSLog(@"成功设置_age成员变量后:%d", [p age]);
[p setName:@"李刚"];
NSLog(@"成功设置_name成员变量后:%@", [p name]);
}
return 0;
}
理解@package访问控制符
@package让那些受它控制的成员变量不仅可以在当前类中访问,也可以在同一映像的其他程序中访问。
所谓同一映像,就是编译后生成的同一个框架或同一个执行文件。当编译器最后把@private限制的成员变量所在的类、其他类和函数编译成一个框架库之后,这些类、函数都在同一个映像中,由于其他程序只是依赖这个框架库,其他程序与该框架库就不在同一个映像中。
#import <Foundation/Foundation.h>
@interface FKApple : NSObject {
@package
double _weight;
}
@end
#import "FKApple.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
FKApple* apple = [[FKApple alloc] init];
apple->_weight = 30;
NSLog(@"APPLE的质量为:%g", apple->_weight);
}
return 0;
}
上面命令将会编译FKApple.h、FKApple.m、FKAppleTest.m文件并生成一个a.out执行文
件,由于FKApple类和main()函数位于同一个映像中,因此main()函数可以自由访问FKApple
类的_weight成员变量。
合成存取方法
为实现成员变量的存取,我们可以为每个成员变量提供setter和getter方法。但当成员变量很多的时候,这种做法过于繁琐。因此,OC为我们提供了合成存取方法,它分为两步:
在类接口部分使用@property定义属性
在类实现部分用@synthesize声明该属性
完成这两步之后,不仅会合成成对的setter和getter方法,还会自动在类实现部分定义一个与getter方法同名的成员变量。如果某一个类定义了一个成员变量,并提供了相应的setter、getter方法,那么可以称作定义了一个属性。
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface person : NSObject
@property (nonatomic) NSString* name;
@property NSString* pass;
@property NSString* birth;
@end
NS_ASSUME_NONNULL_END
#import "person.h"
@implementation person
@synthesize name = _name;
@synthesize pass;
@synthesize birth;
@end
#import <Foundation/Foundation.h>
#import "person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
person* p = [[person alloc] init];
[p setName:@"fuchuang"];
[p setPass:@"fu135453"];
[p setBirth:@"2-14"];
NSLog(@"他的账户为:%@,密码为:%@,生日:%@", [p name], [p pass], [p birth]);
}
return 0;
}
当使用@property定义property时,还可以在@property和类型中用括号添加一些额外的指示符:
assign:该指示符指定对属性只是进行简单的赋值,不更改对所赋值的引用计数。这个指示符主要适用于NSInteger等基础类型,以及int、double、结构体等C语言的数据类型。
(引用计数是OC内存回收的概念:当一个对象的引用计数大于0时,表明该对象还不应该被回收;assign适用的指定对象都不存在回收问题,故用assign)
atomic(nonatomic):指定合成的存取方法是否为原子操作。
copy:使用copy指示符时,当调用setter方法对成员变量赋值时,会被赋值对象复制一个副本,再将该副本赋值给成员变量。copy指示符会将原成员变量所引用对象的引用计数减1。当成员变量的类型是可变类型,或其子类是可变类型时,被赋值的对象有可能在赋值之后被修改;如果程序不需要这种修改影响setter方法设置的成员变量的值,此时就可以考虑使用copy指示符。
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface book : NSObject
@property (nonatomic) NSString* name;
@end
NS_ASSUME_NONNULL_END
#import "book.h"
@implementation book
@synthesize name;
@end
#import <Foundation/Foundation.h>
#import "book.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
book* p = [[book alloc] init];
NSMutableString* s = [NSMutableString stringWithString:@"iOS"];
[p setName:s];
NSLog(@"book的name是%@", [p name]);
//修改字符串 book的name属性也会修改 若是 @property (nonatomic, copy) NSString* name; 则不会修改,原因是当程序执行[book setName:str]时,程序会将str指向的NSMutableString对象复制一个副本,再将副本作为setName的参数值,因此通过修改str修改NSMutableString时,Test的name属性并不会改变。
[s appendString:@"讲义"];
NSLog(@"book的name是%@", [p name]);
}
return 0;
}
- getter、setter:为合成的getter、setter方法自定义方法名。
用法:getter = 方法名 setter = 方法名: (setter要带参数,故有冒号)
#import <Foundation/Foundation.h>
@interface Test : NSObject
@property (assign, nonatomic, getter = get, setter = set:)int price;
@end
@implementation Test
@synthesize price;
@end
int main() {
@autoreleasepool {
Test *test = [[Test alloc] init];
[test set:30];
NSLog(@"price:%d", [test get]);
}
}
readonly、readwrite:readonly指示系统只合成getter方法,不再合成setter方法;readwrite是默认值,让系统合成setter和getter方法。
retain:使用retain定义的属性,当被某个对象赋值后,该属性原来所引用对象的引用计数减1,被赋值对象的引用计数加1
使用点语法访问属性
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface card : NSObject
@property (nonatomic , copy) NSString* a;
@property (nonatomic , copy) NSString* b;
@end
NS_ASSUME_NONNULL_END
#import "card.h"
@implementation card
@synthesize a;
@synthesize b;
@end
#import <Foundation/Foundation.h>
#import "card.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
card* p = [[card alloc] init];
p.a = @"fu";
p.b = @"chuang";
NSLog(@"卡片的名字为:%@%@", p.a, p.b);
}
return 0;
}