iOS 应用开发

iOS 应用开发(四)OC 面向对象之核心语法

2022-12-17  本文已影响0人  HiCloudKK

(一)点语法

点语法的本质是方法调用。


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__);
上一篇 下一篇

猜你喜欢

热点阅读