第四章、oc的语言初级阶段

Day11.oc的入门
// Foundation.h我们称之为主头文件, 主头文件中又拷贝了该工具箱中所有工具的头文件, 我们只需要导入主头文件就可以使用该工具箱中所有的工具, 避免了每次使用都要导入一个对应的头文件
// 工具箱的地址: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks
/*
 因为OC完全兼容C, 所以可以在OC程序中编写C语言代码
 并且可以将C语言的源文件和OC的源文件组合在一起生成可执行文件
 */

1,第一个类

// 1.如何编写类的声明
// 以@interface开头 , 以@end结尾, 然后再class name对应的地方写上 事物名称, 也就是类名即可
// 注意: 类名的首字符必须大写
// 声明一个类的目的就是为了告诉系统, 我们这个类中有哪些属性和行为

// OC类声明中属性只能在写@interface和@end之间的{}中
// 注意: 编写OC类属性的时, 建议将所有属性的名称前面都加上_


// 类名后面的 :NSObject 是为了让我们的Iphone类具备创建对象的能力
@interface Iphone : NSObject
{
//    注意: 默认情况下, OC对象中的属性是不能直接访问的
    @public  // 只要让类中的属性公开, 以后就可以直接通过一个指向结构体的指针来操作对象中的属性
    float _model; // 型号  0
    int _cpu; // cup   0
    double _size; // 尺寸  0
    int _color; // 颜色  0
}

// 行为
@end

// 2.如何编写类的实现
// 以@implementation开头, 以@end结尾, 然后在class对应的地方写上声明时声明的类的名称, 必须和声明的类名一模一样
@implementation Iphone
// 行为的实现

@end


int main(int argc, const char * argv[]) {
    // 如何通过一个类来创建对象
    // 在OC中想要通过一个类来创建一个对象, 必须给类发送一个消息(好比C语言中调用函数一样)
    // 如何发送消息?   在OC中只要想要发送消息就先写上 [类名称/对象名称 方法名称];
    // 发送什么消息(调用什么方法)可以创建一个对象呢? new
    
    /*
     只要通过一个类调用类的new方法, 也就是给类发送一个叫做new的消息之后
     系统内部就会做3件事情
     1. 为Iphone类创建出来得对象分配存储空间
     2. 初始化Iphone类创建出来的对象中的属性
     3. 返回Iphone类创建出来的对象对应的地址
     */
    
    // 通过一个Iphone类型的指针接收了 Iphone对象的地址
    // 如果使用给一个指针保存了某一个对象的地址, 那么我们就称这个指针位之为某个类型的对象
    // 利用Iphone类型的指针保存了Iphone对象的地址, 那么我们就称Iphone类型的指针p之为Iphone对象
   Iphone *p = [Iphone new];
    p->_size = 3.5;
    p->_color = 0;
    p->_model = 4;
    p->_cpu = 1;
    
    // OC中的类其实本质就是一个结构体, 所以p这个指针其实就是指向了一个结构体
    NSLog(@"size = %f, color = %i, model = %f, cpu = %i", p->_size, p->_color, p->_model, p->_cpu);
    /*
    struct Person
    {
        int age;
        char *name;
    };
    struct Person sp;
    struct Person *sip = &sp;
    
//    (*sip).age = 30;
//    (*sip).name = "lnj";
    
    sip->age = 30;
    sip->name = "lnj";
    
    printf("age = %i, name = %s\n", sip->age,  sip->name );
    */
    
    // 什么是用于保存地址的? 指针

    return 0;
}

2,类-方法

对象方法:
// 注意: 当前这个有参数的方法它的方法名称是  signal:
//       冒号也是方法名称的一部分
- (int)signal:(int)number;
// 为了提高我们的阅读性, OC方法允许我们给每个参数添加一个标签来说明当前参数的含义
// 注意: 标签也是方法名的一部分
// 方法名是 sendMessageWithNumber:andContent:
- (int)sendMessageWithNumber:(int)number andContent:(char *)content;
类方法:
// 如果你不想每次使用方法都需要创建对象开辟存储空间
// 并且如果该方法中没有使用到属性(成员变量), 那么你可以把这个方法定义为类方法
// 对象方法用对象调用  类方法用类调用
//- (int)sumWithValue1:(int)value1 andValue2:(int)value2;

// 如果定义类方法, 类方法的写法和对象方法一模一样, 除了前面的-号不同以外 \
只需要将对象方法的-号换成+, 那么就定义了一个类方法
+ (int)sumWithValue1:(int)value1 andValue2:(int)value2;

// 注意: 如果声明的是对象方法那么就必须实现对象方法
//      如果声明的是类方法那么就必须实现类方法

/*
 类方法和对象方法的区别
 0. 对象方法以-开头
    类方法以+开头
 
 1. 对象方法必须用对象调用
    类方法必须用类来调用
 
 2. 对象方法中可以直接访问属性(成员变量)
    类方法中不可以直接访问属性(成员变量)
 
 3. 类方法和对象方法可以进行相互调用
    4.1对象方法中可以直接调用类方法
    4.2类方法中间接调用对象方法  (注意: 不建议这样使用)
    4.3类方法中可以直接调用其它类方法
    4.4对象方法中可以直接调用对象方法
 
 类方法的应用场景
 如果方法中没有使用到属性(成员变量), 那么能用类方法就用类方法
 类方法的执行效率比对象方法高
 
 类方法一般用于定义工具方法
    字符串查找
    文件操作
    数据库操作
 */

类方法中可以直接调用类方法
 类方法中不可以直接调用对象方法
 类方法中不能访问成员变量

3,类对象

1.开辟存储空间, 通过new方法创建对象会在堆 内存中开辟一块存储空间
     2.初始化所有属性
     3.返回指针地址
     
     创建对象的时候返回的地址其实就是类的第0个属性的地址
     但是需要注意的是: 类的第0个属性并不是我们编写的_age, 而是一个叫做isa的属性
     isa是一个指针, 占8个字节
     
     其实类也是一个对象, 也就意味着Person也是一个对象
     平时我们所说的创建对象其实就是通过一个 类对象 来创建一个 新的对象
     类对象是系统自动帮我们创建的, 里面保存了当前对象的所有方法
     而实例对象是程序自己手动通过new来创建的, 而实例对象中有一个isa指针就指向了创建它的那个类对象

4,局部变量和全局变量以及成员变量的区别

@interface Person : NSObject
{
    // 写在类声明的大括号中的变量, 我们称之为 成员变量(属性, 实例变量)
    // 成员变量只能通过对象来访问
    // 注意: 成员变量不能离开类, 离开类之后就不是成员变量 \
            成员变量不能在定义的同时进行初始化
    // 存储: 堆(当前对象对应的堆的存储空间中)
    // 存储在堆中的数据, 不会被自动释放, 只能程序员手动释放
    int age;
}
@end

// 写在函数和大括号外部的变量, 我们称之为全局变量
// 作用域: 从定义的那一行开始, 一直到文件末尾
// 局部变量可以先定义在初始化, 也可以定义的同时初始化
// 存储: 静态区
// 程序一启动就会分配存储空间, 直到程序结束才会释放
int a;
int b = 10;

int main(int argc, const char * argv[]) {
    // 写在函数或者代码块中的变量, 我们称之为局部变量
    // 作用域: 从定义的那一行开始, 一直到遇到大括号或者return
    // 局部变量可以先定义再初始化, 也可以定义的同时初始化
    // 存储 : 栈
    // 存储在栈中的数据有一个特点, 系统会自动给我们释放
    int num = 10;
    {
        int value;
    }
    return 0;
}
Day12.面向对象

1,NSString基本使用

NSString *str = @"李南江";
NSUInteger len = [str length];
    NSLog(@"len = %lu", len);

2,成员变量是结构体

    Student *stu = [Student new];

    // 2.设置学生对象的属性
    stu->_name = @"lnj";
    // 1.结构体只能在定义的时候初始化
    // 2.系统并不清楚它是数组还是结构体
    
    //初始化结构体属性
    //方法一:强制转换
//    stu->_birthday = (Date){1986, 1, 15};
    
    //方法二:定义一个新的结构体,给d赋值,将d赋值给_birthday
    Date d = {1986, 1, 15};
    stu->_birthday = d;
    
    //方法三:分别赋值
//    stu->_birthday.year = 1986;
//    stu->_birthday.month = 1;
//    stu->_birthday.day = 15;

3,pragma mark用法
//#pragma mark -
//#pragma mark 枪
井号pragma mark - 枪
4,修改项目模板

/*
     修改项目模板以及main函数中的内容
     /Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/Project Templates/Mac/Application/Command Line Tool.xctemplate/
     
     修改OC文件头部的描述信息
     /Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/File Templates/Source/Cocoa Class.xctemplate
     */
    /*
     Xcode文档安装的位置1:
     /Applications/Xcode.app/Contents/Developer/Documentation/DocSets
     注意: 拷贝之前最好将默认的文档删除, 因为如果同时存在高版本和低版本的文档, 那么低版本的不会显示
     Xcode文档安装的位置2:
     /Users/你的用户名/Library/Developer/Shared/Documentation/DocSets
     如果没有该文件夹可以自己创建一个
     */

5,setter和getter方法

/*
 setter方法:
 作用: 设置成员变量的值
 格式:
 1. setter方法一定是对象方法
 2. 一定没有返回值
 3. 一定以set开头, 并且set后面跟上需要设置的成员变量的名称去掉下划线, 并且首字母大写
 4. 一定有参数, 参数类型一定和需要设置的成员变量的类型一致, 并且参数名称就是成员变量的名称去掉下划线
*/
- (void)setSize:(int)size;

/*
 getter方法:
 作用: 获取成员变量的值
 格式:
 1. getter方法一定是对象方法
 2.一定有返回值, 而且返回值一定和获取的成员变量的类型一致
 3.方法名称就是获取的成员变量的名称去掉下划线
 4. 一定没有参数
 */
- (int)size;

6,点语法

// 如果给属性提供了getter和setter方法, 那么访问属性就又多了一种访问方式 , 点语法
    // 点语法其实它的本质是调用了我们的setter和getter方法
    // 点语法是一个编译器的特性, 会在程序翻译成二进制的时候将.语法自动转换为setter和getter方法
    // 如果点语法在=号的左边, 那么编译器会自动转换为setter方法
    // 如果点语法在=号的右边, 或者没有等号, 那么编译器就会自动转换为getter方法
点语法的注意点:
     点语法一般用于给成员变量赋值, 如果不是给成员变量赋值一般情况下不建议使用, 但是也可以使用

继承

Day13.类的进阶

1,私有的方法和变量

Person *p = [Person new];
    // 无论使用什么成语变量修饰符修饰成员变量, 我们都可以在其它类中看到这个变量
    // 只不过有得修饰符修饰的变量我们不能操作而已
//    p->_age;
//    p->_height;
//    p->_name;
//    p->_weight;
//    [p test];
//    id pp = [Person new];
//    [pp test];
    
    [p performSelector:@selector(test)];
Person类:
// 如果只有方法的实现, 没有方法的声明, 那么该方法就是私有方法
// 在OC中没有真正的私有方法, 因为OC是消息机制
//- (void)test;

@implementation Person
{
    // 实例变量(成员变量)既可以在@interface中定义, 也可以在@implementation中定义
    // 写在@implementation中的成员变量, 默认就是私有的成员变量, 并且和利用@private修饰的不太一样, 在@implementation中定义的成员变量在其它类中无法查看, 也无法访问
    // 在@implementation中定义的私有变量只能在本类中访问
    @public
    double _score;
}

2,Property, synthesize基本使用

/*
@porperty是一个编译器指令
 在Xocde4.4之前, 可以使用@porperty来代替getter/setter方法的声明
 也就是说我们只需要写上@porperty就不用写getter/setter方法的声明
 
 编译器只要看到@property, 就知道我们要生成某一个属性的getter/setter方法的声明
 
 - (void)setAge:(int)age;
 - (int)age;
 */
@property int age;
/*
 - (void)set_age:(int)_age;
 - (int)_age;
 */
@property int _age;


/*
 @synthesize是一个编译器指令, 它可以简化我们getter/setter方法的实现
 
 什么是实现:
 在声明后面写上大括号就代表着实现
 
 1.在@synthesize后面告诉编译器, 需要实现哪个@property生成的声明
 2. 告诉@synthesize, 需要将传入的值赋值给谁和返回谁的值给调用者
 
 - (void)setAge:(int)age
 {
    _age = age;
 }
 - (int)age
 {
    return _age;
 }
 */
//@synthesize age = _age;

/*
 - (void)setAge:(int)age
 {
    _number = age;
 }
 - (int)age
 {
    return _number
 ;
 }
 */
//@synthesize age = _number;


// 如果在@synthesize后面没有告诉系统将传入的值赋值给谁, 系统默认会赋值给和@synthesize后面写得名称相同的成员变量
// _age? age;
@synthesize age;

/*
- (void)setAge:(int)age
{
    _age = age;
}

- (int)age
{
    return _age;
}
 */

3,*****重点要记住

/*
 从Xcode4.4以后apple对@property进行了一个增强, 以后只要利用一个@property就可以同时生成setter/getter方法的声明和实现
 没有告诉@property要将传入的参数赋值给谁, 默认@property会将传入的属性赋值给_开头的成员变量
 
 @property有一个弊端: 它只会生成最简单的getter/setter方法的声明和实现, 并不会对传入的数据进行过滤
 如果想对传入的数据进行过滤, 那么我们就必须重写getter/setter方法
 如果不想对传入的数据进行过滤, 仅仅是提供一个方法给外界操作成员变量, 那么就可以使用@property
 
 如果利用@property来生成getter/setter方法, 那么我们可以不写成员变量, 系统会自动给我们生成一个_开头的成员变量
 注意: @property自动帮我们生成的成员变量是一个私有的成员变量, 也就是说是在.m文件中生成的, 而不是在.h文件中生成的
 */
// age? _age;
/*
 - (void)setAge:(int)age;
 - (int)age;
 */
@property int age;

// 如果重写了setter方法实现, 那么property就只会生成getter方法
// 如果重写了getter方法实现, 那么property就只会生成setter方法
// 如果同时重写了getter/setter方法实现, 那么property就不会自动帮我们生成私有的成员变量

4,属性修饰符

格式:
 @property(属性修饰符) 数据类型 变量名称;
 */
// readwrite: 代表既生成getter方法 , 也生成setter方法
// 默认情况下 @property就是readwrite的
@property(readwrite) int age;
/*
 - (void)setHeight:(double)height;
 - (double)height;
 
 - (void)setHeight:(double)height;
 - (double)abc;
 */
@property(getter=abc) double height;

/*
 - (void)setWeight:(double)weight;
 - (void)tiZhong:(double)weight;
 */
@property(setter=tiZhong:) double weight;
// readonly: 代表只生成getter方法不生成setter方法
@property(readonly) NSString * name;

// 是否已婚
// 程序员之间有一个约定, 一般情况下获取BOOL类型的属性的值, 我们都会将获取的方法名称改为isXXX
@property(getter=isMarried) BOOL married;

5,id类型

/*
     id是一个数据类型, 并且是一个动态数据类型
     既然是数据类型, 所以就可以用来
     1.定义变量
     2.作为函数的参数
     3.作为函数的返回值
     
     默认情况下所有的数据类型都是静态数据类型
     静态数据类型的特点: 
     在编译时就知道变量的类型, 
     知道变量中有哪些属性和方法
     在编译的时候就可以访问这些属性和方法, 
     并且如果是通过静态数据类型定义变量, 如果访问了不属于静态数据类型的属性和方法, 那么编译器就会报错
     
     动态数据类型的特点:
     在编译的时候编译器并不知道变量的真实类型, 只有在运行的时候才知道它的真实类型
     并且如果通过动态数据类型定义变量, 如果访问了不属于动态数据类型的属性和方法, 编译器不会报错
     
     id == NSObject * 万能指针
     id和NSObject *的区别: 
     NSObject *是一个静态数据类型
     id  是一个动态数据类型
     */

// 通过静态数据类型定义变量, 不能调用子类特有的方法
    // 通过动态数据类型定义变量, 可以调用子类特有的方法
    // 通过动态数据类型定义的变量, 可以调用私有方法
    
    // 弊端: 由于动态数据类型可以调用任意方法, 所以有可能调用到不属于自己的方法, 而编译时又不会报错, 所以可能导致运行时的错误
    // 应用场景: 多态, 可以减少代码量, 避免调用子类特有的方法需要强制类型转换

// 为了避免动态数据类型引发的运行时的错误, 一般情况下如果使用动态数据类型定义一个变量, 在调用这个对象的方法之前会进行一次判断, 判断当前对象是否能够调用这个方法
//    id obj = [Person new];
    id obj = [Student new];
    /*
    if ([obj isKindOfClass:[Student class]]) {
        // isKindOfClass , 判断指定的对象是否是某一个类, 或者是某一个类的子类
        [obj eat];
    }
     */
   
    if ([obj isMemberOfClass:[Student class]]) {
        // isMemberOfClass : 判断指定的对象是否是当前指定的类的实例
        [obj eat];
    }

6,new方法

/*
     new做了三件事情
     1.开辟存储空间  + alloc 方法
     2.初始化所有的属性(成员变量) - init 方法
     3.返回对象的地址
     */
//    Person *p = [Person new];
    // alloc做了什么事情: 1.开辟存储空间 2.将所有的属性设置为0 3.返回当前实例对象的地址
    Person *p1 = [Person alloc];
    // 1.初始化成员变量, 但是默认情况下init的实现是什么都没有做 2.返回初始化后的实例对象地址
    Person *p2 =  [p1 init];
    // [[Person alloc] init];
    
    // 注意: alloc返回的地址, 和init返回的地址是同一个地址
    NSLog(@"p1 = %p, p2 = %p", p1, p2);
    
    // [[Person alloc] init]; == [Person new];
    // 建议大家以后创建一个对象都使用 alloc init, 这样可以统一编码格式

7,构造方法

在OC中init开头的方法, 我们称之为构造方法
     构造方法的用途: 用于初始化一个对象, 让某个对象一创建出来就拥有某些属性和值
/*
// 重写init方法, 在init方法中初始化成员变量
// 注意: 重写init方法必须按照苹果规定的格式重写, 如果不按照规定会引发一些未知的错误
// 1.必须先初始化父类, 再初始化子类
// 2.必须判断父类是否初始化成功, 只有父类初始化成功才能继续初始化子类
// 3.返回当前对象的地址
- (instancetype)init
{
    // 1.初始化父类
    // 只要父类初始化成功 , 就会返回对应的地址, 如果初始化失败, 就会返回nil
    // nil == 0 == 假 == 没有初始化成功
    self = [super init];
    // 2.判断父类是否初始化成功
    if (self != nil) {
        // 3.初始化子类
        // 设置属性的值
        _age = 6;
        
    }
    // 4.返回地址
    return self;
}
 */

/*
- (instancetype)init
{
    self = [super init]; // self == nil == 0
    if (self) {
        // 初始化子类
        _age = 6;
    }
    return self;
}
 */
- (instancetype)init
{
//    self = [super init];
    // 注意: 不要把 = 号写为 ==
    // 一定要将[super init]的返回值赋值给self
    if (self = [super init]) {
        // 初始化子类
        _age = 6;
    }
    return self;
}

8,instancetype和id的区别

// instancetype == id == 万能指针 == 指向一个对象
// id在编译的时候不能判断对象的真实类型
// instancetype在编译的时候可以判断对象的真实类型

// id和instancetype除了一个在编译时不知道真实类型, 一个在编译时知道真实类型以外, 还有一个区别
// id可以用来定义变量, 可以作为返回值, 可以作为形参
// instancetype只能用于作为返回值

// 注意: 以后但凡自定义构造方法, 返回值尽量使用instancetype, 不要使用id
- (instancetype)init
//- (id)init
{
    if (self = [super init]) {
        _age = 5;
    }
    return self;
}

9,自定义构造方法

/*
 自定义构造方法:
 其实就是自定义一个init方法
 1.一定是对象方法
 2.一定返回id/instancetype
 3.方法名称一定以init开头
*/
- (instancetype)initWithAge:(int)age;

// 一个类可以有0个或者多个自定义构造方法
- (instancetype)initWithName:(NSString *)name;

// 自定义构造方法可以有1个或多个参数
- (instancetype)initWithAge:(int)age andName:(NSString *)name;

10,类工厂方法

@interface Person : NSObject

@property int age;
/*
 什么是类工厂方法:
 用于快速创建对象的类方法, 我们称之为类工厂方法
 类工厂方法中主要用于 给对象分配存储空间和初始化这块存储空间
 
 规范:
 1.一定是类方法 +
 2.方法名称以类的名称开头, 首字母小写
 3.一定有返回值, 返回值是id/instancetype
*/
+ (instancetype)person;

+ (instancetype)personWithAge:(int)age;
@end
@implementation Person

+ (instancetype)person
{
//    return [[Person alloc] init];
    // 注意: 以后但凡自定义类工厂方法, 在类工厂方法中创建对象一定不要使用类名来创建
    // 一定要使用self来创建
    // self在类方法中就代表类对象, 到底代表哪一个类对象呢?
    // 谁调用当前方法, self就代表谁
    return [[self alloc] init];
}

+ (instancetype)personWithAge:(int)age
{
//    Person *p = [[Person alloc] init];
    Person *p = [[self alloc] init];
    p.age = age;
    return p;
}
@end


@interface Student : Person

@property int no;
@end

用法:
/*
    Student *stu = [Student person]; // [[Person alloc] init];
    Person *p = [Person person];
//    stu.age = 55;
//    NSLog(@"age = %i", stu.age);
    stu.no = 888;
    NSLog(@"no = %i", stu.no);
     */
    Student *stu = [Student personWithAge:30];
    Person *p = [Person personWithAge:30];
    stu.no = 888;

11,类的本质

/*
     类的本质:
     类其实也是一个对象, 这个对象会在这个类第一次被使用的时候创建
     只要有了类对象, 将来就可以通过类对象来创建实例对象
     实例对象中有一个isa指针, 指向创建自己的类对象
     
     类对象中保存了当前对象所有的对象方法
     当给一个实例对象发送消息的时候, 会根据实例对象中的isa指针去对应的类对象中查找
     */
    /*
    Person *p = [[Person alloc] init];
    [p setAge:30];
    [Person test];
     */
    
    // 1.如何获取类对象
    // [实例对象 class];  [类名 class];
    Person *p1 = [[Person alloc] init];
    Person *p2 = [[Person alloc] init];
    // 一个类再内存中只有一份类对象
    Class c1 = [p1 class];
    Class c2 = [p2 class];
    Class c3 = [Person class];
    NSLog(@"c1 = %p, c2 = %p, c3 = %p", c1, c2, c3);
    
    // 2.类对象的应用场景
    // 2.1用于创建实例对象
    Person *p3 = [[c1 alloc] init];
    p3.age = 30;
    NSLog(@"%i", p3.age);
    // 2.2用于调用类方法
//    [Person test];
    [c1 test];
    
    demo(c1);
    demo([Car class]);
    return 0;
}

void demo(Class c)
{
    id obj = [[c alloc] init];
    NSLog(@"%@", obj);
}

12,类的启动过程

@implementation Person
// 只要程序启动就会将所有类的代码加载到内存中, 放到代码区
// load方法会在当前类被加载到内存的时候调用, 有且仅会调用一次
// 如果存在继承关系, 会先调用父类的load方法, 再调用子类的load方法
+ (void)load
{
    NSLog(@"Person类被加载到内存了");
}

// 当当前类第一次被使用的时候就会调用(创建类对象的时候)
// initialize方法在整个程序的运行过程中只会被调用一次, 无论你使用多少次这个类都只会调用一次
// initialize用于对某一个类进行一次性的初始化
// initialize和load一样, 如果存在继承关系, 会先调用父类的initialize再调用子类的initialize
+ (void)initialize
{
    NSLog(@"Person initialize");
}

@end

13,SEL类型

// 1.SEL类型的第一个作用, 配合对象/类来检查对象/类中有没有实现某一个方法
    /*
    SEL sel = @selector(setAge:);
    Person *p = [Person new];
    // 判断p对象中有没有实现-号开头的setAge:方法
    // 如果P对象实现了setAge:方法那么就会返回YES
    // 如果P对象没有实现setAge:方法那么就会返回NO
    BOOL flag = [p respondsToSelector:sel];
    NSLog(@"flag = %i", flag);
    
    // respondsToSelector注意点: 如果是通过一个对象来调用该方法那么会判断该对象有没有实现-号开头的方法
    // 如果是通过类来调用该方法, 那么会判断该类有没有实现+号开头的方法
    SEL sel1 = @selector(test);
    flag = [p respondsToSelector:sel1];
    NSLog(@"flag = %i", flag);
    
    flag = [Person respondsToSelector:sel1];
    NSLog(@"flag = %i", flag);
     */
    
    
    // 2.SEL类型的第二个作用, 配合对象/类来调用某一个SEL方法
    /*
    SEL sel = @selector(demo);
    Person *p = [Person new];
    // 调用p对象中sel类型对应的方法
    [p performSelector:sel];
    
    SEL sel1 = @selector(signalWithNumber:);
    // withObject: 需要传递的参数
    // 注意: 如果通过performSelector调用有参数的方法, 那么参数必须是对象类型,
    // 也就是说方法的形参必须接受的是一个对象, 因为withObject只能传递一个对象
    [p performSelector:sel1 withObject:@"13838383438"];
    
    SEL sel2 = @selector(setAge:);
    [p performSelector:sel2 withObject:@(5)];
    NSLog(@"age = %i", p.age);
    
    // 注意:performSelector最多只能传递2个参数
    SEL sel3 = @selector(sendMessageWithNumber:andContent:);
    [p performSelector:sel3 withObject:@"138383438" withObject:@"abcdefg"];
     */
    
    // 3.配合对象将SEL类型作为方法的形参
    Car *c = [Car new];
    SEL sel = @selector(run);
    
    Person *p = [Person new];
    [p makeObject:c andSel:sel];
Day15.内存管理
/*
 
 ARC: Automatic(自动) Reference(引用) Counting(计数)
 什么是自动引用计数? 
 不需要程序员管理内容, 编译器会在适当的地方自动给我们添加release/retain等代码
 注意点: OC中的ARC和java中的垃圾回收机制不太一样, java中的垃圾回收是系统干得, 而OC中的ARC是编译器干得
 
 MRC: Manul(手动) Reference(引用) Counting(计数)
 什么是手动引用计数?
 所有对象的内容都需要我们手动管理, 需要程序员自己编写release/retain等代码
 
 内存管理的原则就是有加就有减
 也就是说, 一次alloc对应一次release, 一次retain对应一次relese
 
 */
int main(int argc, const char * argv[]) {

    @autoreleasepool {
        // 只要创建一个对象默认引用计数器的值就是1
        Person *p = [[Person alloc] init];
        
        NSLog(@"retainCount = %lu", [p retainCount]); // 1
        
        // 只要给对象发送一个retain消息, 对象的引用计数器就会+1
        [p retain];
        
        NSLog(@"retainCount = %lu", [p retainCount]); // 2
        
        // 通过指针变量p,给p指向的对象发送一条release消息
        // 只要对象接收到release消息, 引用计数器就会-1
        // 只要一个对象的引用计数器为0, 系统就会释放对象
        [p release];
        // 需要注意的是: release并不代表销毁\回收对象, 仅仅是计数器-1
        NSLog(@"retainCount = %lu", [p retainCount]); // 1
        
        [p release]; // 0
        NSLog(@"--------");
    }
//    [p setAge:20];
    
    return 0;

@implementation Person

- (void)dealloc
{
    NSLog(@"Person dealloc");
    // 注意:super dealloc一定要写到所有代码的最后
    // 一定要写在dealloc方法的最后面
    [super dealloc];
}
@end
 在xcode中的Build Setting 中搜索automatic r  更改后面的YES还是no

1,野指针

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init]; // 1
        
        // 只要一个对象被释放了, 我们就称这个对象为 "僵尸对象"
        // 当一个指针指向一个僵尸对象, 我们就称这个指针为野指针
        // 只要给一个野指针发送消息就会报错
        [p release]; // 1-1 = 0
        // *** -[Person release]: message sent to deallocated instance 0x1001146b0

        // 空指针  nil  0
        // 为了避免给野指针发送消息会报错, 一般情况下, 当一个对象被释放后我们会将这个对象的指针设置为空指针
        // 因为在OC中给空指针发送消息是不会报错的
        p = nil;
        
        [p release];
        [p release];
        [p release];
        [p release];
        [p release];
        [p release];
        [p release];
    }
    return 0;
}
Day16.ARC和分类Block使用

1,ARC基本概念

int main(int argc, const char * argv[]) {

    /*
//    Person *p = [[[Person alloc] init] autorelease];
    // 默认情况下所有的指针都是强指针
//    Person *p = [[Person alloc] init];
//    [p retain];
//    [p release];
     */
    /*
    {
        // ARC的判断准则: 只要没有强指针指向对象, 对象就会释放
        // 默认情况下所有的指针都是强指针
//        Person *p = [[Person alloc] init];
//        p = nil;
        
//        __strong Person *p = [[Person alloc] init];
//        // 弱指针
//        __weak Person *p2 = p;
//        p = nil;
        
        // 在开发中, 千万不要使用一个弱指针保存一个刚刚创建的对象
        // 立即释放
        __weak Person *p = [[Person alloc] init];
    }
     */
    Person *p = [[Person alloc] init];
    
    p = nil;
    
    return 0;
}

2,arc和mrc混编
加上这个:-fno-objc-arc

第四章、oc的语言初级阶段_第1张图片
混编添加.png

3,MRC转ARC

项目转ARC方法

3,分类Category

/*
 方法:
 方法的声明:
 方法的实现:
 
 所以: 通过分类给某一个类扩充方法, 也分为声明和实现两个部分
 
 // 分类的声明
 @interface ClassName (CategoryName)
 NewMethod; //在类别中添加方法
 //不允许在类别中添加变量
 @end
 
 ClassName: 需要给哪个类扩充方法
 CategoryName: 分类的名称
 NewMethod: 扩充的方法
 
 // 分类的实现
 @implementation ClassName(CategoryName)
 
 NewMethod
 ... ...
 @end
 
 ClassName: 需要给哪个类扩充方法
 CategoryName: 分类的名称
 NewMethod: 扩充的方法
 */
/*
 方法的调用顺序:
 1.分类
 2.本类
 3.父类
 */

4,Block基本使用

#import 

void printRose(int num)
{
    for (int i = 0; i < num; ++i) {
        
        printf("  {@} \n");
        printf("   |  \n");
        printf("  \\|/ \n");
        printf("   | \n");
    }
}

int sum(int value1, int value2)
{
    return value1 + value2;
}
int main(int argc, const char * argv[]) {

    /*
    printf("  {@} \n");
    printf("   |  \n");
    printf("  \\|/ \n");
    printf("   |");
     */
    /*
//    printRose();
//    printRose();
    
    // void代表指向的函数没有返回值
    // ()代表指向的函数没有形参
    // (*roseP)代表roseP是一个指向函数的指针
//    void (*roseP) ();
//    roseP = printRose;
//    roseP();
    
    // 定义一个block变量,
    // block和函数一样,可以没有返回值,也没有形参
    // 也可以没有返回值有形参
    // 也可以有返回值没有形参
    // 也可以有返回值有形参
    // 所以, 在定义一个block变量的时候, 也需要告诉该变量将来保存的代码有没有返回值和形参
    
    // void代表block将来保存的代码没有返回值
    // ()代表block将来保存的代码没有形参
    // (^roseBlock) 代表reseBlock是一个block变量, 可以用于保存一段block代码
    void (^roseBlock) ();
    // 如果block没有参数, 那么^后面的()可以省略
    roseBlock = ^(){
        printf("  {@} \n");
        printf("   |  \n");
        printf("  \\|/ \n");
        printf("   | \n");
    };
    // 要想执行block保存的代码, 必须调用block才会执行
    roseBlock();
    roseBlock();
     */
    /*
//    printRose(10);
    
//    void (*roseP)(int);
//    roseP = printRose;
//    roseP(3);
    
    void (^roseBlock) (int);
    roseBlock = ^(int num){
        for (int i = 0; i < num; ++i) {
            
            printf("  {@} \n");
            printf("   |  \n");
            printf("  \\|/ \n");
            printf("   | \n");
        }
    
    };
    
    roseBlock(2);
     */
    
    /*
//    int (*sumP)(int, int);
//    sumP = sum;
//    NSLog(@"sum = %i", sumP(10 , 20));
    
    int (^sumBlock) (int, int);
    sumBlock =^(int value1, int value2){
        return value1 + value2;
    };
    NSLog(@"sum = %i", sumBlock(10, 40));
     */
    
    // block是一种数据类型
    
    int (^printBlock)(int)= ^int (int num){
        for (int i=0; i

5,Block和typedef

#import 

int sum(int value1, int value2)
{
    return value1 + value2;
}

int minus(int value1, int value2)
{
    return value1 - value2;
}

typedef int (*calculte)(int, int);

// 注意: 利用typedef给block起别名, 和指向函数的指针一样, block变量的名称就是别名
typedef int (^calculteBlock)(int , int);


int main(int argc, const char * argv[]) {

    /*
//    int (*sumP)(int, int);
//    sumP = sum;
    calculte sumP = sum;
    NSLog(@"sum = %i", sumP(20, 10));
    
    
//    int (*minusP)(int, int);
//    minusP = minus;
    calculte minusP = minus;
    NSLog(@"minus = %i", minusP(20, 10));
       */
    
//    int (^sumBlock)(int , int );
    calculteBlock sumBlock = ^(int value1, int value2){
        return value1 + value2;
    };
    NSLog(@"sum = %i", sumBlock(20, 10));
    
//    int (^minusBlock)(int , int);
    calculteBlock minusBlock = ^(int value1, int value2){
        return value1 - value2;
    };
    NSLog(@"minus = %i", minusBlock(20, 10));
    return 0;
}

6,Block的应用场景

#import 
/*
 void goToWorkPrefix()
 {
 NSLog(@"起床");
 NSLog(@"穿衣服");
 NSLog(@"洗漱");
 NSLog(@"喝早茶");
 NSLog(@"驾车去上班");
 }
 
 void goToWorkSubfix()
 {
 NSLog(@"收拾东西");
 NSLog(@"驾车回家");
 NSLog(@"吃晚饭");
 NSLog(@"洗澡");
 NSLog(@"睡觉");
 }
 
 void goToWorkInday1()
 {
 goToWorkPrefix();
 
 NSLog(@"认识新同事");
 
 goToWorkSubfix();
 }
 
 void goToWorkInday2()
 {
 goToWorkPrefix();
 
 NSLog(@"熟悉公司代码");
 
 goToWorkSubfix();
 }
 
 
 void goToWorkInday3()
 {
 goToWorkPrefix();
 
 NSLog(@"开始编写代码");
 
 goToWorkSubfix();
 }
 
 void goToWorkInday4()
 {
 goToWorkPrefix();
 
 NSLog(@"应用程序上架");
 
 goToWorkSubfix();
 }
 */

// 当发现代码的前面和后面都是一样的时候, 这个时候就可以使用block
void goToWork(void (^workBlock)())
{
    NSLog(@"起床");
    NSLog(@"穿衣服");
    NSLog(@"洗漱");
    NSLog(@"喝早茶");
    NSLog(@"驾车去上班");
    
    // 不一样
    workBlock();
    
    NSLog(@"收拾东西");
    NSLog(@"驾车回家");
    NSLog(@"吃晚饭");
    NSLog(@"洗澡");
    NSLog(@"睡觉");
}

void goToWorkInDay1()
{
    goToWork(^{
        NSLog(@"认识新同事");
    });
}
void goToWorkInDay2()
{
    goToWork(^{
        NSLog(@"熟悉公司代码");
    });
}
void goToWorkInDay3()
{
    goToWork(^{
        NSLog(@"开始编写代码");
    });
}
void goToWorkInDay4()
{
    goToWork(^{
        NSLog(@"应用程序上架");
    });
}

/*
 找到需要读取的文件
 读取文件
 
 操作文件
 
 关闭文件
 */

int main(int argc, const char * argv[]) {

    goToWorkInDay2();
    return 0;
}

7,Block的注意点

#import 
#import "Person.h"

int main(int argc, const char * argv[]) {

    // 1.block中可以访问外面的变量
    /*
    int a = 10;
    void (^myBlock)() = ^{
        NSLog(@"a = %i", a);
    };
    myBlock();
     
     int a=10;
     void( ^myBlock)()=^{
       Nslog(@"a= %i",a);
     }
     myBlock();
    */
    
    // 2.block中可以定义和外界同名的变量, 并且如果在block中定义了和外界同名的变量, 在block中访问的是block中的变量
    /*
    int a = 10;
    void (^myBlock)() = ^{
        int a  = 20;
        NSLog(@"a = %i", a);
    };
    myBlock();
     */
    
    // 3.默认情况下, 不可以在block中修改外界变量的值
    // 因为block中的变量和外界的变量并不是同一个变量
    // 如果block中访问到了外界的变量, block会将外界的变量拷贝一份到堆内存中
    // 因为block中使用的外界变量是copy的, 所以在调用之前修改外界变量的值, 不会影响到block中copy的值
    /*
    int a = 10;
    NSLog(@"&a = %p", &a);
    void (^myBlock)() = ^{
//        a = 50;
        NSLog(@"&a = %p", &a);
        NSLog(@"a = %i", a);
    };
    a = 20;
    myBlock();
     */
    
    /*
    // 如果想在block中修改外界变量的值, 必须在外界变量前面加上__block
    // 如果在block中修改了外界变量的值, 会影响到外界变量的值
    __block int a = 10;
    NSLog(@"&a = %p", &a);
    void (^myBlock)() = ^{
        a = 50;
        NSLog(@"&a = %p", &a);
        NSLog(@"a = %i", a);
    };
    myBlock();
    NSLog(@"a = %i", a);
     */
    
    /*
    
//     int a = 10; // 如果没有添加__block是值传递
//     void (*myBlock)() = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, a);
//     (myBlock)->FuncPtr)(myBlock);
    
    // 为什么不加__block不能在block中修改外界变量的值
    int a = 10;
    void (^myBlock)() = ^{
//        a = 10;
        NSLog(@"a = %i", a);
    };
    myBlock();
     */
    
    /*
//    a =  10; // 如果加上__block之后就是地址传递, 所以可以在block中修改外界变量的值
//    void (*myBlock)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &a, 570425344);
    
    // 为什么加了__block就可以在block中修改外界变量的值
    __block int a = 10;
    void (^myBlock)() = ^{
        a = 10;
        NSLog(@"a = %i", a);
    };
    myBlock();
     */
    
    
    // block是存储在堆中还是栈中
    // 默认情况下block存储在栈中, 如果对block进行一个copy操作, block会转移到堆中
    // 如果block在栈中, block中访问了外界的对象, 那么不会对对象进行retain操作
    // 但是如果block在堆中, block中访问了外界的对象, 那么会对外界的对象进行一次retain
    
    // 如果在block中访问了外界的对象, 一定要给对象加上__block, 只要加上了__block, 哪怕block在堆中, 也不会对外界的对象进行retain
    // 如果是在ARC开发中就需要在前面加上__weak
    __block Person *p = [[Person alloc] init]; // 1
    
    // 如果在做iOS开发时, 在ARC中不这样写容易导致循环引用
//    Person *p = [[Person alloc] init];
//    __weak Person *weakP = p;
    
    NSLog(@"retainCount = %lu", [p retainCount]);
    void (^myBlock)() = ^{
        NSLog(@"p = %@", p); // 2
//        NSLog(@"p = %p", weakP);
        NSLog(@"block retainCount = %lu", [p retainCount]);
    };
    Block_copy(myBlock);
    myBlock();
    
    [p release]; // 1
    
    
    return 0;
}

你可能感兴趣的:(第四章、oc的语言初级阶段)