iOS 应用开发(四)OC 面向对象之核心语法
(一)点语法
点语法的本质是方法调用。
person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject {
int _age;
}
- (void)setAge: (int)age;
- (int)age;
@end
person.m
#import "Person.h"
// 注意:@implementation 中不能定义和@interface 同名的变量。
@implementation Person
- (void)setAge: (int)age {
_age = age;
// self->_age = age;
}
- (int)age {
return _age;
}
@end
main.m 测试:
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
Person *person = [Person new];
// person.age 调用 setAge 方法
// 等价于 [person setAge: 20];
person.age = 20;
// person.age 调用 age 方法
// 等价于 [person age];
NSLog(@"age=%d",person.age);
return 0;
}
OC 访问修饰符说明:
@public 可以在任何地方直接访问;
@private 只能在当前类的对象方法中直接访问;
@protected 能在当前类和子类的对象方法中直接访问;
@package 只能在框架(包)内直接访问。
注意:@interface 中变量成员 默认为 @protected, @implementation 中成员变量 默认为 @privated。
(二)@property 属性
#import <Foundation/Foundation.h>
@interface Student : NSObject
// @property: Xcode 4.5之后
// 自动生成私有属性;
// 生成getter+setter声明;
// 自动生成getter+setter的实现;
@property int age,no;// 同类型可写一起
@property double weight;
@property NSString *name;
- (void)test;
@end
@implementation Student
- (void)test {
NSLog(@"age=%d,no=%d",self->_age,self->_no);
}
@end
int main(int argc, const char * argv[]) {
Student *p = [Student new];
p.no = 1;
p.age = 18;
p.weight = 120.0;
p.name = @"CloudKK";
NSLog(@"age=%d and weight=%f,name=%@",p.age,p.weight,p.name);
[p test];
return 0;
}
如果想让子类直接访问父类成员变量,只需如下定义:
@interface Person : NSObject {
// 在这 单独定义一下(加下划线,默认 proctected 修饰)
int _age;
}
// 自动生成getter&setter的声明和实现
@property int age;
@end
(三)id 万能指针
id 相当于 NSObject *。id类型的定义:
typedef struct objc_object {
Class isa;
} *id;
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Person *p = [Person new];
// p.age = 20;
// NSLog(@"age=%d",p.age);
// 万能指针,可以执行和操作任何oc对象
// 可认为 id 相当于 NSObject *
id d = [Person new];
[d setAge: 20];
// @property id obj;
[d setObj: @"CloudKK"];
NSLog(@"id---age=%d,obj=%@",[d age],[d obj]);
}
return 0;
}
(四)构造方法
创建对象:先分配存储空间: +alloc,再初始化(构造方法): -init。
------------------------------------------------------
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property int age;
@end
------------------------------------------------------
#import "Person.h"
@implementation Person
// - init 对象的构造方法
-(id)init {
// 初始化成功, 进行一些成员变量初始化操作
// 这里会先执行父类的初始化
if (self = [super init]) {// self!=nil
NSLog(@"person init successed!");
_age = 18;
}
return self;
}
@end
------------------------------------------------------
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// This method is a combination of alloc and init
// 也就是 new 方法包含了 alloc 和 init 方法。
// Person *p = [Person new];
// 构造对象分2步:
// 1. 分配存储空间: +alloc
// 2. 初始化(构造方法): -init
// 初始化完成后,对象的isa指针才有值
Person *p1 = [[Person alloc] init];
NSLog(@"age=%d",p1.age);
}
return 0;
}
自定义构造方法,返回值一般是id类型且方法名以init开头。
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property int age;
@property NSString *name;
// 自定义构造方法,返回值一般是id类型且方法名以init开头
- (id)init:(int)age andName:(NSString *)name;
@end
------------------------------------------------------
#import "Person.h"
@implementation Person
- (id)init:(int)age andName:(NSString *)name {
if (self = [super init]) {// 这里 super init 也可以在父类自定义(传参)
_age = age;
_name = name;
}
return self;
}
@end
------------------------------------------------------
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init:20 andName:@"CloudKK"];
NSLog(@"age=%d,name=%@",p.age,p.name);
}
return 0;
}
(五)category 分类
category 作用是为某个类添加方法(可以访问原来类中的成员变量,但不能添加新的成员变量),但不修改原来类的代码。好处就是减少类的体积,将类中的不同功能进行拆分成不同分类文件。
#import "Person.h"
// 为Person添加新功能(KK)
@interface Person (KK)
- (void)study;
@end
------------------------------------------------------
#import "Person+KK.h"
// KK:模块(功能)名字
@implementation Person (KK)
// 仅添加(不推荐覆盖掉原来的方法)
- (void)study {
NSLog(@"study...");
}
@end
------------------------------------------------------
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Person+KK.h"
int main(int argc, const char * argv[]) {
Person *p = [[Person alloc] init];
[p study];
return 0;
}
注意:如果多个 Category 中实现了相同的方法,那么只有最后一个参与编译有效。由于 Category 优先级比较高,如果它替换了原来的方法,那么对象将不能访问原来的方法。
为系统自带的 NSString 扩展方法(有点类似kotlin的扩展方法):
// NSString+Ext.h
#import <Foundation/Foundation.h>
@interface NSString (Ext)
#pragma mark - 计算字符串中数字的个数
- (int)countNumberOfString;
@end
------------------------------------------------------
// NSString+Ext.m
#import "NSString+Ext.h"
@implementation NSString (Ext)
// 程序一启动类被加载时调用(只会调用一次),不管运行过程中是否用到这个类,都会调用+load,而且是先加载类 再加载 category 分类。
+ (void)load {
NSLog(@"NSString---load");
}
// 当第一次使用类(如创建对象)时调用,一个类只会调用一次+initialize方法,先调用父类的,再调用子类的。
+ (void)initialize {
NSLog(@"NSString---initialize");
}
- (int)countNumberOfString {
int count = 0;
for (int i = 0; i < self.length; i++) {
int number = [self characterAtIndex:i] - '0';
if (number >= 0 && number <= 9) {
count++;
}
}
return count;
}
@end
------------------------------------------------------
#import <Foundation/Foundation.h>
#import "NSString+Ext.h"
// test...
int count = @"fddfh21343daj52ds".countNumberOfString;
NSLog(@"count=%d",count);// 7
OC类的本质也是对象(利用 Class 创建类对象,而且内存中只会有一份类对象)。创建对象时,会先申请内存 把类加载到内存中 再初始化创建对象(实例对象,且所有对象都有个isa指针指向类)。
typedef struct objc_class *Class;// oc 类对象
------------------ 第一种方式 ----------------------
NSString *str1 = @"CloudKK";
NSString *str2 = @"HiCloudKK";
// 获取内存中的类对象(Class 类型数据)
Class c1 = [str1 class];
Class c2 = [str2 class];
// c1=0x1f2ec5458,c2=0x1f2ec5458
// 说明是同一个类对象,也就是内存中只有一份类对象
NSLog(@"c1=%p,c2=%p",c1,c2);
------------------ 第二种方式 ----------------------
// 获取内存中的类对象
Class c1 = [NSString class];
Class c2 = [NSString class];
// c1=0x1f2ef45a8,c2=0x1f2ef45a8
// 说明是同一个类对象,也就是内存中只有一份类对象
NSLog(@"c1=%p,c2=%p",c1,c2);
OC中,类就是类对象,类对象就是类,且每个类只有一个类对象。
(六)description 方法
description 方法 相同于 java 中的 toString
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property int age;
@property NSString *name;
@end
------------------------------------------------------
#import "Person.h"
@implementation Person
// 相当于 java 中的 toString 方法
// 注意这里不要打印self(会死循环)
- (NSString *)description {
return [NSString stringWithFormat:@"<Person: age=%d,name=%@>",_age,_name];
}
@end
------------------------------------------------------
//test...
#import <Foundation/Foundation.h>
#import "Person.h"
int main() {
Person *p = [[Person alloc] init];
p.age = 20;
p.name = @"CloudKK";
// <Person: age=20,name=CloudKK>
NSLog(@"%@",p);// 等价于 p.description
// NSLog(@"%@",p.description);
return 0;
}
(七)SEL
每个类的方法列表都存储在类对象中,且每个方法都有一个与之对应的 SEL 类型的对象;根据一个 SEL 对象就可以找到方法的地址,进而调用方法。SEL 本质就是把一个方法包装成一个对象,消息的本质就是 SEL。
// SEL 对象的定义
typedef struct objc_selector *SEL;
// SEL 对象的创建
SEL s = @selector(test);
SEL s = @selector(test);
// 将 SEL 对象转换成NSString 对象
NSString *str = NSStringFromSelector(@selector(test2));
--------------------- SEL 调用 -----------------------------
Person *p = [[Person alloc] init];
// 把 test2 方法 包装成 SEL 类型的数据,
// 再根据 SEL 数据找到对应的方法地址,最后根据方法地址调用对应的方法。
// (即:一个 SEL 数据对应一个方法地址)
[p test2];
// 间接调用 test2
[p performSelector:@selector(test2)];
// 间接调用 test3 (带参,注意要带上冒号,因为带参方法的冒号也属于方法名)
[p performSelector:@selector(test3:) withObject:@"CloudKK"];
// 间接调用 test4 (带2参,注意要带上冒号)
[p performSelector:@selector(test4:andParams2:) withObject:@"Zhangsan" withObject:@"Lisi"];
------------------ 方法名封装成 SEL对象 ------------------------
// 定义方法名
NSString *name = @"test3:andParams2:";
// 把方法名包装成 SEL 类型对象
SEL s = NSSelectorFromString(name);
// 再根据 SEL 对象找到方法的地址来调用方法。
[p performSelector:s withObject:@"Zhangsan" withObject:@"Lisi"];
------------------ _cmd 代表当前方法 ------------------------
// _cmd 等价于 @selector(test2),也代表了 test2 方法的 SEL 对象。
- (void)test2 {
NSString *str = NSStringFromSelector(_cmd);
// test2...test2
NSLog(@"test2...%@",str);
}
Xcode 提供的常用工具:
// 方法名:main
NSLog(@"func=%s",__func__);
// 行号:36
NSLog(@"LINE=%d",__LINE__);
// 文件名:main.m
NSLog(@"FILE_NAME=%s",__FILE_NAME__);