黑马程序员——OC核心语法

时间:2023-02-18 20:52:40

点语法

简单的一句话,点语法的本质就是方法调用。
set方法[p setAge:10]; 用点语法可以表达为:p.age = 10;;get方法int a = p.age; 用点语法可以表达为:[p age];
在使用点语法的时候我们需要注意逻辑关系,在方法的声明中引入不当的点语法会造成死循环:

- (void)setAge:(int)age
{
// _age = age;
self.age = age;
}
- (int)age
{
// return _age;
return self.age;
}

在这个程序中,两个点语法都会造成死循环的产生。第一个点语法可以写成[self setAge:age];,第二个点语法可以写成[self age];

成员变量的作用域

成员变量的作用域有四种方式定义
1> @public : 在任何地方都能直接访问对象的成员变量;
2> @private : 只能在当前类的对象方法中直接访问,在@implementation中默认的就是@private;
3> @protected : 可以在当前类及其子类的对象方法中直接访问 在@interface中默认就是@protected;
4> 第四种是不常用的一种方式,@package : 只要处在同一个框架中,就能直接访问对象的成员变量。
除了作用域我们还需要注意的是在@implementation中不能定义和@interface中同名的成员变量;同时在main函数后面的成员变量都不能直接访问的。

@property和@synthesize

首先我们做一个练习:
设计一个类Circle,用来表示二维平面中的圆
1> 属性
* double _radius (半径)
* Point2D *_point (圆心)

2> 方法
* 属性相应的set和get方法
* 设计一个对象判断跟其他圆是否重叠(重叠返回YES,否则返回NO)
* 设计一个类方法判断两个圆是否重叠(重叠返回YES,否则返回NO)

我们可以先设计一个点作为圆心,通过圆心距离和半径和的大小来判断圆是否重叠

#import <Foundation/Foundation.h>
#import <math.h>
// 点
@interface Point2D : NSObject
{
double _x;
double _y;
}

// x的getter和setter
- (void)setX:(double)x;
- (double)x;

// y的getter和setter
- (void)setY:(double)y;
- (double)y;

//同时设置x和y
- (void)setX:(double)x andY:(double)y;

// 方法对象计算和其他点得距离
- (double)distanceWithOther:(Point2D *)other;

// 类方法计算两个点之间的距离
+ (double)distanceBetweenPoint1:(Point2D *)p1 andPoint2:(Point2D *)p2;
@end

@implementation Point2D
// x的getter和setter的实现
- (void)setX:(double)x
{
_x = x;
}
- (double)x
{
return _x;
}
//y的getter和setter的实现
- (void)setY:(double)y
{
_y = y;
}
- (double)y
{
return _y;
}

//同时设置x和y的实现
- (void)setX:(double)x andY:(double)y
{
_x = x;
_y = y;
}

- (double)distanceWithOther:(Point2D *)other
{
return [Point2D distanceBetweenPoint1:self andPoint2:other];
}

+ (double)distanceBetweenPoint1:(Point2D *)p1 andPoint2:(Point2D *)p2
{
// (x1-x2)的平方
double X = pow(([p1 x] - [p2 x]), 2);
// (y1-y2)的平方
double Y = pow(([p1 y] - [p2 y]), 2);
// 对两个平方和开根号
return sqrt(X + Y);
}
@end

// 圆
@interface Circle : NSObject
{
double _radius;
Point2D *_point;
}
// 半径的getter和setter
- (void)setRadius:(double)radius;
- (double)radius;
// 圆心的getter和setter
- (void)setPoint:(Point2D *)point;
- (Point2D *)point;

// 对象方法判断和其他圆是否重叠
- (BOOL)intersectWithOther:(Circle *)other;
// 类方法判断和其他圆是否重叠
+ (BOOL)isIntersectCircle1:(Circle *)c1 andCircle2:(Circle *)c2;
@end

@implementation Circle
// 半径getter和setter的实现
- (void)setRadius:(double)radius
{
_radius = radius;
}
- (double)radius
{
return _radius;
}
// 圆心getter和setter的实现
- (void)setPoint:(Point2D *)point
{
_point = point;
}
- (Point2D *)point
{
return _point;
}

// 对象方法实现
- (BOOL)intersectWithOther:(Circle *)other
{
return [Circle isIntersectCircle1:self andCircle2:other];
}
// 类方法实现
+ (BOOL)isIntersectCircle1:(Circle *)c1 andCircle2:(Circle *)c2
{
// 两个圆心
Point2D *p1 = [c1 point];
Point2D *p2 = [c2 point];
// 两个圆心的距离
double distance = [Point2D distanceBetweenPoint1:p1 andPoint2:p2];

// 半径的和
double sumOfRadius = [c1 radius] + [c2 radius];

return distance < sumOfRadius;
}
@end

int main()
{
Circle *c1 = [Circle new];
// 设置半径
[c1 setRadius:5];
// 设置圆心
Point2D *p1 = [Point2D new];
[p1 setX:5 andY:5];
[c1 setPoint:p1];

Circle *c2 = [Circle new];
// 设置半径
[c2 setRadius:4];
// 设置圆心
Point2D *p2 = [Point2D new];
[p2 setX:8 andY:6];
[c2 setPoint:p2];

BOOL i1 = [c1 intersectWithOther:c2];

BOOL i2 = [Circle isIntersectCircle1:c1 andCircle2:c2];

NSLog(@"%d %d", i1, i2);

return 0;
}

虽然我们实现了功能,但是根据程序我们可以看出来,其中写了很多重复的代码,比如各成员变量的setter和getter方法。Xcode为了让我们不需要考虑这些重复代码,腾出心思来更好的考虑功能,特意定义了@property和@synthesize。
@property:可以自动生成某个成员变量的setter和getter声明。如下:

@property int age;
//- (void)setAge:(int)age;
//- (int)age;

@property int height;
//- (void)setHeight:(int)height;
//- (int)height;

而@synthesize自动生成age的setter和getter实现,并且会访问_age这个成员变量,如下:

@synthesize age = _age;

@synthesize height = _height;

同时当在@synthesize中访问某个不存在的成员变量时,系统会自动生成@private类型的变量。

虽然现在非常大的简化了我们的程序,但是并不是最简单的写法,最简单的可以写成:

@property int age;

这一行虽然简单,但是它又三个作用
第一、会自动生成setter和getter的方法
第二、会自动生成setter和getter的实现
第三、可以自动生成_age成员变量
根据这些我们就可以很大的简化程序,更好的去考虑功能。

构造方法

在说构造之前我们先说一个关键字id。id是一个万能指针,能指向\操作任何OC对象,id其实就是NSObject *,它只适用于OC对象。
在程序中初始化的成员变量默认都是0,有时候我们为了让对象创建出来,成员变量就会有一些固定的值,这个时候我们就用到了构造方法。
构造方法:用来初始化对象的方法,是个对象方法,-开头。
我们先来看一下完整地创建一个可用的对象是如何进行的:
Person *p = [Person new];
1.调用+alloc分配存储空间 Person *p1 = [Person alloc];
2.调用-init进行初始化 Person *p2 = [p1 init];
这两个步骤我们也可以等价为:
Person *p4 = [[Person alloc] init];
在”.m”文件中实现初始化,也就是重写init写法:

- (id)init
{
// 1.一定要调用回super的init方法:初始化父类中声明的一些成员变量和其他属性
self = [super init]; // 当前对象 self

// 2.如果对象初始化成功,才有必要进行接下来的初始化
if (self != nil)
{ // 初始化成功
_age = 10;
}

// 3.返回一个已经初始化完毕的对象
return self;
}

以上的代码我们可以更加简化的书写:

 - (id)init
{
if(self = [super init])
{
_age = 10;
}
return self;
}

可以实现同样的初始化功能,而且更加简单便于记忆。不过这里可能有些人会对super有些忘记。super是直接调用父类的某个方法,当子类需要重写父类的方法的同时想保留父类的一些行为时使用super。
注意点:
1> 先调用父类的构造方法([super init])
2> 再进行子类内部成员变量的初始化
- 自定义构造方法
自定义构造方法的规范
1.一定是对象方法,一定以 - 开头
2.返回值一般是id类型
3.方法名一般以initWith开头
如:- (id)initWithName:(NSString *)name;
当在使用自定义构造方法的时候,需要注意一点:当子类的构造方法中包含父类的属性,在这个构造方法的实现中,父类的属性交给父类方法去处理,子类方法处理子类自己的属性,如下将name、age传递到父类方法中进行初始化:

- (id)initWithName:(NSString *)name andAge:(int)age andNo:(int)no
{
if ( self = [super initWithName:name andAge:age])
{
_no = no;
}

return self;
}

分类-Category

分类的作用:在不改变原来类内容的基础上,可以为类增加一些方法。

  • 格式
    分类的声明
@interface 类名 (分类名称)
// 方法声明
@end

分类的实现

@implementation 类名 (分类名称)
// 方法实现
@end

分类可以将一个庞大的类可以由多个人来编写,更有利于团队合作。
分类的使用注意:
1.分类只能增加方法,不能增加成员变量
2.分类方法实现中可以访问原来类中声明的成员变量
3.分类可以重新实现原来类中的方法,但是会覆盖掉原来的方法,会导致原来的方法没法再使用
4.方法调用的优先级:分类(最后参与编译的分类优先) –> 原来类 –> 父类
除了上述的一些作用外,我们可以利用分类来写一些类没有的功能。
比如给NSString
增加一个类方法计算某个字符串内部英文字母的个数
增加一个对象方法计算当前字符串内部英文字母的个数
增加一个类方法比较两个字符串的长度,返回长度差
增加一个对象方法和其他字符串比较长度,返回长度差
声明:

#import <Foundation/Foundation.h>

@interface NSString (NumberOfLetter)
+ (int)letterNumberOfString:(NSString *)str;
- (int)letterNumber;

+ (int)compareLengthBetweenString1:(NSString *)str1 andString2:(NSString *)str2;
- (int)compareLengthWithOther:(NSString *)other;
@end

实现:

#import "NSString+NumberOfLetter.h"

@implementation NSString (NumberOfLetter)
+ (int)letterNumberOfString:(NSString *)str
{
return [str letterNumber];
}
- (int)letterNumber
{
int count = 0;

for(int i = 0 ; i<self.length; i++)
{
// 取出i这个位置对应的字符
unichar c = [self characterAtIndex:i];

// 如果这个字符是英文字母
if( (c>='a' && c<='z') || (c>='A' && c<='Z') )
{
count++;
}
}
return count;

}

+ (int)compareLengthBetweenString1:(NSString *)str1 andString2:(NSString *)str2
{
// return ([str1 length]-[str2 length]);
return [str1 compareLengthWithOther:str2];
}
- (int)compareLengthWithOther:(NSString *)other
{
return ([self length]-[other length]);
}
@end

类的本质

  • 类也是一个对象
    其实类也是一个对象,是Class类型的对象,简称“类对象”。Class类型的定义:typedef struct objc_class *Class;;类名就代表着类对象,每个类只有一个类对象。

  • +load和+initialize

    1.当程序启动时,就会加载项目中所有的类和分类,而且加载后会调用每个类和分类的+load方法。只会调用一次。
    2.当第一次使用某个类时,就会调用当前类的+initialize方法
    3.先加载父类,再加载子类(先调用父类的+load方法,再调用子类的+load方法)
    先初始化父类,再初始化子类(先调用父类的+initialize方法,再调用子类的+initialize方法)

  • 获取类对象的2种方式

Class c = [Person class]; // 类方法

或者

Person *p = [Person new];
Class c2 = [p class]; // 对象方法
  • 类对象调用类方法
Class c = [Person class];
Person *p2 = [c new];

description方法

  • -description方法
    使用NSLog和%@输出某个对象时,会调用对象的-description方法,并拿到返回值进行输出

  • +description方法
    使用NSLog和%@输出某个类对象时,会调用类对象+description方法,并拿到返回值进行输出

默认情况下,利用NSLog和%@输出对象时,结果是:<类名:内存地址>
1.会调用对象p的-description方法
2.拿到-description方法的返回值(NSString *)显示到屏幕上
3.-description方法默认返回的是“类名+内存地址”

如果在-description方法中使用NSLog打印self时会形成死循环:

- (NSString *)description
{
// 下面代码会引发死循环
NSLog(@"%@", self);
}

SEL

SEL其实是对方法的一种包装,将方法包装成一个SEL类型的数据,去找对应的方法地址。找到方法地址就可以调用方法。

  • SEL对象的创建
SEL s = @selector(test);
SEL s2 = NSSelectorFromString(@"test");
  • SEL对象的其他用法
// 将SEL对象转为NSString对象
NSString *str = NSStringFromSelector(@selector(test));

Person *p = [Person new];
// 调用对象p的test方法
[p performSelector:@selector(test)];

_cmd代表着当前方法,使用时也会引发死循环:[self performSelector:_cmd];