美文网首页
OC语言基础

OC语言基础

作者: 恒筠 | 来源:发表于2018-03-04 21:20 被阅读0次

    第一课

    1.对象方法和类方法区别

    • 对象方法
      • 对象方法是属于对象的
      • 以减号-开头
      • 只能让对象调用,没有对象,这个方法根本不可能被执行
      • 对象方法能访问实例变量(成员变量)
      • 对象方法中可以调用当前对象的对象方法
      • 对象方法中可以调用其他对象的对象方法
      • 对象方法中不可以调用类方法
    • 类方法

      • 类方法是属于类的
      • 以加号+开头
      • 只能用类名调用,对象不能调用
      • 类方法中不能直接访问实例变量(成员变量)
      • 类方法中不能直接调用对象方法,要想调用对象方法,必须创建或传入对象。
    • 使用场合:

      • 当不需要访问成员变量的时候,尽量用类方法
      • 类方法和对象方法可以同名

    2.函数与方法对比

    函数属于整个文件,可以写在文件中的任何位置,包括@implementation...@end中,但写在 @interface...@end会无法识别,函数的声明可以在main函数内部也可以在main函数外部。

    3.通过类创建对象

    通过类创建对象

    • 1.开辟存储空间, 通过new方法创建对象会在堆 内存中开辟一块存储空间

    • 2.初始化所有属性

    • 3.返回指针地址

    • 创建对象的时候返回的地址其实就是类的第0个属性的地址

    • 但是需要注意的是: 类的第0个属性并不是我们编写的_age, 而是一个叫做isa的属性

    • isa是一个指针, 占8个字节

    • 其实类也是一个对象, 也就意味着Person也是一个对象

    • 平时我们所说的创建对象其实就是通过一个 类对象 来创建一个 新的对象

    • 类对象是系统自动帮我们创建的, 里面保存了当前对象的所有方法

    • 而实例对象是程序自己手动通过new来创建的, 而实例对象中有一个isa指针就指向了创建它的那个类对象



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


    第二课

    1.对象和方法之间的关系

    对象作为方法参数传递是地址传递,因为对象是一个指针变量
    在方法内部,可以通过对象形参,访问该对象的成员变量(如果该对象的该成员变量的访问权限是public的)
    在方法内部,可以通过对象形参,调用该对象上的方法(给这个对象发送消息)

    2.xcode项目模版的修改

    • 1.应用程序中,找到Xcode, 右键"显示包内容"
      • 打开"/Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/Project\ Templates/Mac/Application" 文件夹

        • 在/Application文件夹中能够找到所有和OS X Application界面对应的文件夹
      • 修改Command Line Tool模板

        • 打开"Command Line Tool.xctemplate"文件夹, 发现和"改Command Line Tool模板"一一对应
        • 打开"TemplateInfo.plist文件"发现和"改Command Line Tool模板"中内容对应

    3.修改类的头部信息

    • 找到对应类对应的类文件模板. (因为类是创建项目之后手动创建的, 而不是随着项目的创建自动创建的, 所以修改类文件模板和项目模板并不是修改同一个文件)
      • 打开"/Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates File Templates/Source/Cocoa Class.xctemplate"文件夹

    4.NSString的基本使用

    // C语言中的字符串不是对象
        char *name1 = "lnj";
        char name2[] = "lmj";
        
        // OC中的字符串是一个对象
        // 正是因为OC中的字符串是一个对象, 所以它就具备了很多功能
        NSString *str = @"lk";
        
        Iphone *p = [Iphone new];
        // 注意: 输出C语言的字符串使用%s
        //      输出OC的字符串使用%@,  %@就专门用于输出对象类型的
    //    NSLog(@"content = %s", [p loadMessage]);
        NSLog(@"content = %@", [p loadMessage]);
    
    

    第三课

    1.self关键字

    • 类方法中可以直接调用类方法
    • 类方法中不可以直接调用对象方法
    • 类方法中不能访问成员变量
    + (void)carameWithFlahlightStatus:(FlahlightStatus)status
    {
        if (status == kFlahlightStatusOpen) {
    //        [Iphone openFlahlight];
            ** // 其实在类方法中调用类方法除了可以使用类名调用以外, 还可以使用self来调用**
            [self openFlahlight];
        }else
        {
    //        [Iphone closeFlahlight];
            // self == Iphone
            [self closeFlahlight];
        }
        NSLog(@"拍照");
        
    }
    
    
    • 如果self在对象方法中, 那么self就代表调用当前对象方法的那个对象
    • 如果self在类方法中, 那么self就代表调用当前类方法的那个类
    • 总结:
      • 我们只用关注self在哪一个方法中 , 如果在类方法那么就代表当前类, 如果在对象方法那么就代表"当前调用该方法的对象"
    • 注意:

      • self会自动区分类方法和对象方法, 如果在类方法中使用self调用对象方法, 那么会直接报错
      • 不能在对象方法或者类方法中利用self调用当前self所在的方法
    • 使用场景:

      • 可以用于在对象方法之间进行相互调用
      • 可以用于在类方法之间进行相互调用
      • 可以用于区分成员变量和局部变量同名的情况

    2.多态

    • 什么是多态:

    • 事物的多种表现形态

    • 在程序中如何表现:

    • 父类指针指向子类对象

    • 优点:

    • 提高了代码的扩展性

    • 注意点:

    • 如果父类指针指向子类对象, 如果需要调用子类特有的方法, 必须先强制类型转换为子类才能调用

    多态: 事物的多种表现形态
    动态类型: 在编译的时候编译器只会检查当前类型对应的类中有没有需要调用的方法
    在运行时,系统会自动判断a1的真实类型

      Animal *a1 = [Dog new];
        [a1 eat];
    

    注意点: 在多态中, 如果想调用子类特有的方法必须强制类型转换为子类才能调用

    3.description关键字

    • %@是用来打印对象的, 其实%@的本质是用于打印字符串
    • 只要利用%@打印某个对象, 系统内部默认就会调用父类的description方法
    • 调用该方法, 该方法会返回一个字符串, 字符串的默认格式 <类的名称: 对象的地址>

    第四课

    1.OC中的私有方法

    OC中的私有变量

    • 在类的实现即.m文件中也可以声明成员变量,但是因为在其他文件中通常都只是包含头文件而不会包含实现文件,所以在.m文件中声明的成员变量是@private的。在.m中定义的成员变量不能和它的头文件.h中的成员变量同名,在这期间使用@public等关键字也是徒劳的。
    @implementation Dog
    {
        @public
        int _age;
    }
    @end
    

    2.OC中的私有方法

    • 私有方法:只有实现没有声明的方法

    • 原则上:私有方法只能在本类的方法中才能调用。

      • 注意: OC中没有真正的私有方法

    3.id类型

    • 我们知道NSObject是OC中的基类

    • 那么任何对象的NSObject类型的指针可以指向任意对象,都没有问题

    • 但是NSObject是静态类型,如果通过它直接调用NSObject上面不存在的方法,编译器会报错。

    • 你如果想通过NSObject的指针调用特定对象的方法,就必须把NSObject * 这种类型强转成特定类型。然后调用。如下

      //定义NSObject * 类型
       NSObject* obj = [Cat new];
       Cat *c = (Cat*)obj;
       [c eat];
      
    • id 是一种通用的对象类型,它可以指向属于任何类的对象,也可以理解为万能指针 ,相当于C语言的 void *

    • 因为id是动态类型,所以可以通过id类型直接调用指向对象中的方法, 编译器不会报错

    ```
    /// Represents an instance of a class.
    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    
    /// A pointer to an instance of a class.
    typedef struct objc_object *id;
    ```
    
    
    ```
     id obj = [C at new];
     [obj eat]; // 不用强制类型转换
    
     [obj test]; //可以调用私有方法
    ```
    
    • 注意:
      • 在id的定义中,已经包好了*号。id指针只能指向OC中的对象
      • 为了尽可能的减少编程中出错,Xcode做了一个检查,当使用id 类型的调用本项目中所有类中都没有的方法,编译器会报错
      • id类型不能使用.语法, 因为.语法是编译器特性, 而id是运行时特性
    • id == NSObject * 万能指针
    • id和NSObject *的区别:
    • NSObject *是一个静态数据类型
    • id 是一个动态数据类型
    • 通过静态数据类型定义变量, 不能调用子类特有的方法(需要强制转成子类类型才能调用方法)

    • 通过动态数据类型定义变量, 可以调用子类特有的方法

    • 通过动态数据类型定义的变量, 可以调用私有方法

      • 弊端: 由于动态数据类型可以调用任意方法, 所以有可能调用到不属于自己的方法, 而编译时又不会报错, 所以可能导致运行时的错误
      • 应用场景: 多态, 可以减少代码量, 避免调用子类特有的方法需要强制类型转换
    • 为了避免动态数据类型引发的运行时的错误, 一般情况下如果使用动态数据类型定义一个变量, 在调用这个对象的方法之前会进行一次判断, 判断当前对象是否能够调用这个方法

    id obj = [Student new];
        /*
        if ([obj isKindOfClass:[Student class]]) {
            // isKindOfClass , 判断指定的对象是否是某一个类, 或者是某一个类的子类
            [obj eat];
        }
         */
       
        if ([obj isMemberOfClass:[Student class]]) {
            // isMemberOfClass : 判断指定的对象是否是当前指定的类的实例
            [obj eat];
        }
    
    

    4.构造方法

    重写init方法

    - (id)init {
        self = [super init];
        if (self) {
            // Initialize self.
        }
        return self;
    }
    
    • 重写init方法其它格式
    - (id)init {
        if (self = [super init]) {
            // Initialize self.
        }
        return self;
    }
    

    构造方法使用注意

    • 子类拥有的成员变量包括自己的成员变量以及从父类继承而来的成员变量,在重写构造方法的时候应该首先对从父类继承而来的成员变量先进行初始化。

    • 原则:先初始化父类的,再初始化子类的。

    • 先调用父类的构造方法[super init];

    • 再进行子类内部成员变量的初始化。

    • 千万不要把self = [super init]写成self == [super init]

    • 重写构造方法的目的:为了让对象方法一创建出来,成员变量就会有一些固定的值。

    • (1)自己做自己的事情

    • (2)父类的属性交给父类的方法来处理,子类的方法处理子类自己独有的属性

    • 自定义构造方法必须以initWith开头,并且’W’必须大写

    5.自定义类工厂方法

    • 由于父类的类工厂方法创建实例对象时是使用父类的类创建的, 所以如果子类调用父类的类工厂方法创建实例对象,创建出来的还是父类的实例对象
    • 为了解决这个问题, 以后在自定义类工厂时候不要利用父类创建实例对象, 改为使用self创建, 因为self谁调用当前方法self就是谁
    
    @interface Person : NSObject
    + (id)person;
    @end
    
    @implementation Person
    + (id)person
    {
    //   return  [[Person alloc]init];
    //     谁调用这个方法,self就代表谁
    //    注意:以后写类方法创建初始化对象,写self不要直接写类名
        return  [[self alloc]init];
    }
    @end
    
    @interface Student : Person
    @property NSString *name;
    @end
    
    @implementation Student
    @end
    
    int main(int argc, const char * argv[])
    {
        Student *stu = [Student person];// [[Student alloc] init]
        [stu setName:@"lnj"];
    }
    

    6.类的本质

    • 类的本质其实也是一个对象(类对象)
    • 程序中第一次使用该类的时候被创建,在整个程序中只有一份。
    • 此后每次使用都是这个类对象,它在程序运行时一直存在。
    • 类对象是一种数据结构,存储类的基本信息:类大小,类名称,类的版本,继承层次,以及消息与函数的映射表等
    • 类对象代表类,Class类型,对象方法属于类对象
    • 如果消息的接收者是类名,则类名代表类对象
    • 所有类的实例都由类对象生成,类对象会把实例的isa的值修改成自己的地址,每个实例的isa都指向该实例的类对象

    7.类的启动过程

    +load方法

    • 在程序启动的时候会加载所有的类和分类,并调用所有类和分类的+load方法(只会调用一次)
    • 先加载父类,再加载子类;也就是先调用父类的+load,再调用子类的+load
    • 先加载元原始类,再加载分类
    • 不管程序运行过程有没有用到这个类,都会调用+load加载
    ```
    @implementation Person
    
    + (void)load
    {
        NSLog(@"%s", __func__);
    }
    @end
    
    @implementation Student : Person
    
    + (void)load
    {
        NSLog(@"%s", __func__);
    }
    @end
    
    输出结果:
    +[Person load]
    +[Student load]
    ```
    

    +initialize

    • 在第一次使用某个类时(比如创建对象等),只会调用一次+initialize方法
    • 一个类只会调用一次+initialize方法,先调用父类的,再调用子类的

    8.property的增强

    • 如果利用@property来生成getter/setter方法, 那么我们可以不写成员变量, 系统会自动给我们生成一个_开头的成员变量

    • 注意: @property自动帮我们生成的成员变量是一个私有的成员变量, 也就是说是在.m文件中生成的, 而不是在.h文件中生成的

    • 如果重写了setter方法, 那么property就只会生成getter方法

    • 如果重写了getter方法, 那么property就只会生成setter方法

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

    9.SEL类型

    • a.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);
    ```
    
    • b.SEL类型的第二个作用, 配合对象/类来调用某一个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个参数
    ```
    
    • c.配合对象将SEL类型作为方法的形参
    ```
    /    Car *c = [Car new];
    //    SEL sel = @selector(run);
    //    
    //    Person *p = [Person new];
    //    [p makeObject:c andSel:sel];
    ```
    

    第五课

    1.内存管理

    • 只有OC对象才需要进行内存管理的本质原因

      • OC对象存放于堆里面

      • 非OC对象一般放在栈里面(栈内存会被系统自动回收)

      • OC中的ARC和java中的垃圾回收机制不太一样, java中的垃圾回收是系统干得, 而OC中的ARC是编译器干得

    2.堆和栈

    • 栈(操作系统):由操作系统自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈(先进后出);

    • 堆(操作系统):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方式类似于链表。

    3.dealloc方法

    • dealloc方法的重写

      • 一般会重写dealloc方法,在这里释放相关资源,dealloc就是对象的遗言
      • 一旦重写了dealloc方法, 就必须调用[super dealloc],并且放在最后面调用
    • 使用注意

      • 不能直接调用dealloc方法
      • 一旦对象被回收了, 它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)

    4.野指针/空指针

    • 1.僵尸对象

      • 已经被销毁的对象(不能再使用的对象)
    • 2.野指针

      • 指向僵尸对象(不可用内存)的指针
      • 给野指针发消息会报EXC_BAD_ACCESS错误
    • 3.空指针

      • 没有指向存储空间的指针(里面存的是nil, 也就是0)
      • 给空指针发消息是没有任何反应的
    • 为了避免野指针错误的常见办法

      • 在对象被销毁之后, 将指向对象的指针变为空指针
    • 只要一个对象被释放了, 我们就称这个对象为 "僵尸对象"

    • 当一个指针指向一个僵尸对象, 我们就称这个指针为野指针

    • 只要给一个野指针发送消息就会报错

    5.多对象内存管理原则

    • 管理规律:

      只要还有人在用某个对象,那么这个对象就不会被回收
      只要你想用这个对象,就让对象的计数器+1
      当你不再使用这个对象时,就让对象的计数器-1

    在mrc环境下

    // 当A对象想使用B对象一定要对B对象进行一次retain, 这样才能保证A对象存在B对象就存在, 也就是说这样才能保证无论在什么时候在A对象中都可以使用B对象
    // 当A对象释放的时候, 一定要对B对应进行一次release, 这样才能保证A对象释放了, B对应也会随之释放, 避免内存泄露
    // 总结一句话: 有增就有减
    
    
    换房了, 如果set方法中没有release旧值, 就会内存泄露
    - (void)setRoom:(Room *)room // room = r
    {
        // 只有房间不同才需用release和retain
        if (_room != room) {// 0ffe1 != 0ffe1
            
            // 将以前的房间释放掉 -1
            [_room release];
            
            /*
            // 对房间的引用计数器+1
            [room retain];
            
            _room = room;
             */
            // retain不仅仅会对引用计数器+1, 而且还会返回当前对象
            _room = [room retain];
        }
    }
    
    - (void)dealloc
    {
        // 人释放了, 那么房间也需要释放
        [_room release];
        NSLog(@"%s", __func__);
        [super dealloc];
    }
    

    6.class和#import

    作用上的区别

    • import会包含引用类的所有信息(内容),包括引用类的变量和方法
    • @class仅仅是告诉编译器有这么一个类, 具体这个类里有什么信息, 完全不知

    • 效率上的区别

    • 如果有上百个头文件都#import了同一个文件,或者这些文件依次被#import,那么一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍 , 编译效率非常低

    • 相对来讲,使用@class方式就不会出现这种问题了

    • 总结:
      • 如果两个类相互拷贝, 例如A拷贝B, B拷贝A, 这样会报错
      • 如何解决: 在.h中用@class, 在.m中用import
      • 因为如果.h中都用import, 那么A拷贝B, B又拷贝A, 会形成死循环
      • 如果在.h中用@class, 那么不会做任何拷贝操作, 而在.m中用import只会拷贝对应的文件, 并不会形成死循环

    7.property修饰符

    • 1.相同类型的property修饰符不能同时使用
    • 2.不同类型的property修饰符可以多个结合在一起使用, 多个之间用,号隔开
    • 3.iOS开发中只要写上property, 那么就立刻写上nonatomic
    /*
    retain: 就会自动帮我们生成getter/setter方法内存管理的代码
    assign: 不会帮我们生成set方法内存管理的代码, 仅仅只会生成普通的getter/setter方法, 默认什么都不写就是assign
    */
    @property(nonatomic, retain) Room *room;
    

    第六课

    1.autorelease

    • 并不是放到自动释放池代码中,都会自动加入到自动释放池

       @autoreleasepool {
          // 因为没有调用 autorelease 方法,所以对象没有加入到自动释放池
          Person *p = [[Person alloc] init];
          [p run];
      }
      
    • 在自动释放池的外部发送autorelease 不会被加入到自动释放池中

    • autorelease是一个方法,只有在自动释 放池中调用才有效。

       @autoreleasepool {
       }
       // 没有与之对应的自动释放池, 只有在自动释放池中调用autorelease才会放到释放池
       Person *p = [[[Person alloc] init] autorelease];
       [p run];
      
       // 正确写法
        @autoreleasepool {
          Person *p = [[[Person alloc] init] autorelease];
       }
      
       // 正确写法
       Person *p = [[Person alloc] init];
        @autoreleasepool {
          [p autorelease];
       }
      
    • 自动释放池是以栈的形式存在

      • 由于栈只有一个入口, 所以调用autorelease会将对象放到栈顶的自动释放池
      • 栈顶就是离调用autorelease方法最近的自动释放池
        ```
        @autoreleasepool { // 栈底自动释放池
          @autoreleasepool {
              @autoreleasepool { // 栈顶自动释放池
                  Person *p = [[[Person alloc] init] autorelease];
              }
              Person *p = [[[Person alloc] init] autorelease];
          }
        }
        ```
    

    2.ARC(Automatic Reference Counting)

    ARC的判断原则

    • ARC的判断原则

      • 只要还有一个强指针变量指向对象,对象就会保持在内存中
    • 强指针

      • 默认所有指针变量都是强指针
      • 被__strong修饰的指针
    • 弱指针

      • 被__weak修饰的指针

    注意:当使用ARC的时候,暂时忘记“引用计数器”,因为判断标准变了。

    • ARC机制下有几个明显的标志:
      • 不允许调用对象的 release方法
      • 不允许调用 autorelease方法
      • 再重写父类的dealloc方法时,不能再调用 [super dealloc];

    ARC下多对象内存管理

    • ARC和MRC一样, 想拥有某个对象必须用强指针保存对象, 但是不需要在dealloc方法中release
    ```
    @interface Person : NSObject
    
    // MRC写法
    //@property (nonatomic, retain) Dog *dog;
    
    // ARC写法
    @property (nonatomic, strong) Dog *dog;
    
    @end
    ```
    
    • ARC和MRC一样, 如果A拥有B, B也拥有A, 那么必须一方使用弱指针
    @interface Person : NSObject
    
    //@property (nonatomic, retain) Dog *dog;
    @property (nonatomic, strong) Dog *dog;
    
    @end
    
    @interface Dog : NSObject
    
    // 错误写法, 循环引用会导致内存泄露
    //@property (nonatomic, strong) Person *owner;
    
    // 正确写法, 当如果保存对象建议使用weak
    //@property (nonatomic, assign) Person *owner;
    @property (nonatomic, weak) Person *owner;
    @end
    
    • ARC下@property参数

      • strong : 用于OC对象, 相当于MRC中的retain

      • weak : 用于OC对象, 相当于MRC中的assign

      • assign : 用于基本数据类型, 跟MRC中的assign一样

      • 在ARC中如果保存对象不要用assign, 用weak

      • assign是专门用于保存基本数据类型的, 如果保存对象用weak

    3.ARC模式下如何兼容非ARC的类

    • 转变为非ARC -fno-objc-arc
    • 转变为ARC的, -f-objc-arc (不常用)

    4.Category

    • 分类只能增加方法, 不能增加成员变量
    • 分类中写property只会生成方法声明,不会生成实现和私有成员变量
    • 分类可以访问原来类中的成员变量
    • 如果分类和原来类出现同名的方法, 优先调用分类中的方法, 原来类中的方法会被忽略
    • 方法调用的优先级(从高到低)
      • 分类(最后参与编译的分类优先)
      • 原来类
      • 父类

    5.类扩展(Class Extension)

    • 延展类别又称为扩展(Extendsion),Extension是Category的一个特例

    • 可以为某个类扩充一些私有的成员变量和方法

      • 写在.m文件中
      • 英文名是Class Extension
      • 对比分类, 就少了一个分类名称,因此也有人称它为”匿名分类”

    6.Block

    Block的定义格式

    //定义的格式类似c语言中指向函数的指针
    
    返回值类型 (^block变量名)(形参列表) = ^(形参列表) {
    
    };
    

    7.Block的注意事项

    • 1.block中可以访问外面的变量

    • 2.block中可以定义和外界同名的变量, 并且如果在block中定义了和外界同名的变量, 在block中访问的是block中的变量

    • 3.默认情况下, 不可以在block中修改外界变量的值

      • 因为block中的变量和外界的变量并不是同一个变量
      • 如果block中访问到了外界的变量, block会将外界的变量拷贝一份到堆内存中
      • 因为block中使用的外界变量是copy的, 所以在调用之前修改外界变量的值, 不会影响到block中copy的值
    • 如果想在block中修改外界变量的值, 必须在外界变量前面加上__block,如果没有添加__block是值传递

    • 如果在block中修改了外界变量的值, 会影响到外界变量的值,如果加上__block之后就是地址传递, 所以可以在block中修改外界变量的值

    • block是存储在堆中还是栈中
    • 默认情况下block存储在栈中, 如果对block进行一个copy操作, block会转移到堆中
    • 如果block在栈中, block中访问了外界的对象, 那么不会对对象进行retain操作
    • 但是如果block在堆中, block中访问了外界的对象, 那么会对外界的对象进行一次retain
    • 如果在block中访问了外界的对象, 一定要给对象加上__block, 只要加上了__block, 哪怕block在堆中, 也不会对外界的对象进行retain
    • 如果是在ARC开发中就需要在前面加上__weak

    第七课

    1.protocol

    • 继承之后默认就有实现, 而protocol只有声明没有实现

    • 相同类型的类可以使用继承, 但是不同类型的类只能使用protocol

    • protocol可以用于存储方法的声明, 可以将多个类中共同的方法抽取出来, 以后让这些类遵守协议即可

    • protocol 的使用注意

      • Protocol:就一个用途,用来声明一大堆的方法(不能声明成员变量),不能写实现。
      • 只要父类遵守了某个协议,那么子类也遵守。
      • OC不能继承多个类(单继承)但是能够遵守多个协议。继承(:),遵守协议(< >)
      • 协议可以遵守协议,一个协议遵守了另一个协议,就可以拥有另一份协议中的方法声明
    • NSObject是一个基类,最根本最基本的类,任何其他类最终都要继承它

    • 还有名字也叫NSObject的协议,它是一个基协议,最根本最基本的协议

    • NSObject协议中声明很多最基本的方法

    • 建议每个新的协议都要遵守NSObject协议

    @protocol SportProtocol <NSObject> // 基协议
    
    - (void)playFootball;
    - (void)playBasketball;
    @end
    
    • 协议中有2个关键字可以控制方法是否要实现(默认是@required,在大多数情况下,用途在于程序员之间的交流)
    * @required:这个方法必须要实现(若不实现,编译器会发出警告)
    * @optional:这个方法不一定要实现
    
    • 协议的编写规范:
    • 1.一般情况下, 当前协议属于谁, 我们就将协议定义到谁的头文件中
    • 2.协议的名称一般以它属于的那个类的类名开头, 后面跟上protocol或者delegate
    • 3.协议中的方法名称一般以协议的名称protocol之前的作为开头
    • 4.一般情况下协议中的方法会将触发该协议的对象传递出去
    • 5.一般情况下一个类中的代理属于的名称叫做 delegate
    • 6.当某一个类要成为另外一个类的代理的时候, 一般情况下在.h中用@protocol 协议名称;告诉当前类 这是一个协议.在.m中用#import真正的导入一个协议的声明

    2.代理设计模式

    • 代理设计模式的场合:

      • 当对象A发生了一些行为,想告知对象B(让对象B成为对象A的代理对象)
      • 对象B想监听对象A的一些行为(让对象B成为对象A的代理对象)
      • 当对象A无法处理某些行为的时候,想让对象B帮忙处理(让对象B成为对象A的代理对象)

    3.NSString

    • 通过不同的方式创建字符串,字符串对象储存的位置也不一样

    如果是通过字符串常量创建,那么字符串对象存储在常量区中
    如果是通过alloc initWithFormat/stringWithFormat创建,那么字符串对象存储在堆区中
    而且需要注意:
    不同的平台存储的方式也不一样,如果是Mac平台系统会自动对字符串对象进行优化,但是如果是iOS平台就是两个对象
    不同的编译器存储的方式也不一样,如果是Xcode6以下并且是在iOS平台,那么每次alloc都会创建一个新的对象,如果是在Xcode6以上那么alloc多次指向同一块存储空间

    • 注意:一般情况下,只要是通过alloc或者类工厂方法创建的对象,每次都会在堆内存中开辟一块新的存储空间
    • 但是如果是通过alloc的initWithString方法除外,因为这个方法是通过copy返回一个字符串对象给我们
    • 而copy又分为深拷贝和浅拷贝,如果是深拷贝会创建一个新的对象,如果是浅拷贝不会创建一个新的对象,而是直接返回被拷贝的对象的地址给我们
    • 首字母变大写,其他字母都变小写

      • (NSString *)capitalizedString
    • 字符串搜索

      • (BOOL)hasPrefix:(NSString *)aString;

      • 是否以aString开头

      • (BOOL)hasSuffix:(NSString *)aString;

      • 是否以aString结尾

      • (NSRange)rangeOfString:(NSString *)aString;

      • 用来检查字符串内容中是否包含了aString

      • 如果包含, 就返回aString的范围

      • 如果不包含, NSRange的location为NSNotFound, length为0

    • 补充:NSRange的创建

    • NSRange是Foundation框架中比较常用的结构体, 它的定义如下:

    ```
    typedef struct _NSRange {
        NSUInteger location;
        NSUInteger length;
    } NSRange;
    // NSUInteger的定义
    typedef unsigned int NSUInteger;
    ```
    
    ```
    NSRange range = NSMakeRange(7, 3);
    ```
    
    • (BOOL)isAbsolutePath;

      • 是否为绝对路径
    • (NSString *)lastPathComponent;

      • 获得最后一个目录
    • (NSString *)stringByDeletingLastPathComponent;

      • 删除最后一个目录
    • (NSString *)pathExtension;

      • 获得拓展名
    • 转为C语言中的字符串

    • (char *)UTF8String;

      NSString *str = @"abc";
      const char *cStr = [str UTF8String];
      NSLog(@"cStr = %s", cStr);
      
      
      char *cStr = "abc";
      NSString *str = [NSString stringWithUTF8String:cStr];
      NSLog(@"str = %@", str);
      
      

    4.NSMutableString

    • 不可变:指的是字符串在内存中占用的存储空间固定,并且存储的内容不能发生变化

    • 可变:指的是字符串在内存中占用的存储空间可以不固定,并且存储的内容可以被修改

    5.NSArray

    • NSArray的使用注意

      • 只能存放任意OC对象, 并且是有顺序的
      • 不能存储非OC对象, 比如int\float\double\char\enum\struct等
      • 它是不可变的,一旦初始化完毕后,它里面的内容就永远是固定的, 不能删除里面的元素, 也不能再往里面添加元素
    • NSArray直接使用NSLog()作为字符串输出时是小括号括起来的形式。

      • NSArray中不能存储nil,因为NSArray认为nil是数组的结束(nil是数组元素结束的标记)。nil就是0。0也是基本数据类型,不能存放到NSArray中。
    ```
        NSArray *arr = [NSArray arrayWithObjects:@"lnj", nil ,@"lmj",@"jjj", nil];
        NSLog(@"%@", arr);
    输出结果:
    (
        lnj
    )
    ```
    
    // 创建一个空的数组
        NSMutableArray *arrM = [NSMutableArray array];
        NSLog(@"%@", arrM);
        // 如何添加
        [arrM addObject:@"lnj"];
        // 将指定数组中的元素都取出来, 放到arrM中
        // 并不是将整个数组作为一个元素添加到arrM中
        [arrM addObjectsFromArray:@[@"lmj", @"jjj"]];
        // 注意: 以下是将整个数组作为一个元素添加
    //    [arrM addObject:@[@"lmj", @"jjj"]];
        NSLog(@"%@", arrM);
    

    第八课

    1.NSDictionary

    • 键值对集合的特点

      • 字典存储的时候,必须是"键值对"的方式来存储(同时键不要重复)
      • 键值对中存储的数据是"无序的".
      • 键值对集合可以根据键, 快速获取数据.
    • NSArray和NSDictionary的区别

      • NSArray是有序的,NSDictionary是无序的
      • NSArray是通过下标访问元素,NSDictionary是通过key访问元素
    • NSArray的用法

      • 创建
        ```
        @[@"Jack", @"Rose"] (返回是不可变数组)
        
        ```
        
    * NSDictionary的用法 +创建
    
         ```
               
            NSDictionary *dict = @{key:value};
            NSDictionary *dict = @{@"name": @"lnj"};
            NSLog(@"%@", dict[@"name"]);
                
            NSDictionary *dict = @{@"name":@"lnj", @"age":@"30", @"height":@"1.75"};
    
            @{ @"name" : @"Jack", @"phone" : @"10086" } (返回是不可变字典)
       
       
    * 字典的遍历
    
            
        ```
        for (int i = 0; i < dict.count; ++i) {
        // 获取字典中所有的key
        NSArray *keys = [dict allKeys];
        // 取出当前位置对应的key
    

    // NSLog(@"%@", keys[i]);
    NSString *key = keys[i];
    NSString *value = dict[key];
    NSLog(@"key = %@, value = %@", key, value);
    }

        ```
        
        * **字典的遍历(2)(常用)**
    
        
        ```
        //如何通过forin遍历字典, 会将所有的key赋值给前面的obj
    

    for (NSString *key in dict) {
    NSLog(@"%@", key);
    NSString *value = dict[key];
    NSLog(@"key = %@, value = %@", key, value);
    }
    ```

        * 字典的遍历(3)
    
       [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
            NSLog(@"key = %@, value = %@", key, obj);
        }];
    

    注意:

    • 如果是不可变数组, 那么key不能相同
    • 如果是不可变字典出现了同名的key, 那么后面的key对应的值不会被保存
    • 如果是在可变数组中, 后面的会覆盖前面的

    2.NSNumber

    • NSNumber的创建

      • 以前
        ```
        + (NSNumber *)numberWithInt:(int)value;
        + (NSNumber *)numberWithDouble:(double)value;
        + (NSNumber *)numberWithBool:(BOOL)value;
        
        ```   
    * 现在
    
    
        ```
           @10;
           @10.5;
           @YES;
           @(num);
        ```
        
    * 基本数据类型转换对象类型简写
        * 注意: 如果传入的是变量那么必须在@后面写上(), 如果传入的常量, 那么@后面的()可以省略
    
            ```    
                NSNumber *temp = @(number);
                NSNumber *temp  =@10.10;
                NSLog(@"%@", temp);
            ```
    

    3.NSValue

    • NSNumber是NSValue的子类, 但NSNumber只能包装数字类型

    • NSValue可以包装任意值

      • 因此, 可以用NSValue将结构体包装后,加入NSArray\NSDictionary中
    • 为了方便 结构体 和NSValue的转换,Foundation提供了以下方法

    • 将结构体包装成NSValue对象

      + (NSValue *)valueWithPoint:(NSPoint)point;
      + (NSValue *)valueWithSize:(NSSize)size;
      + (NSValue *)valueWithRect:(NSRect)rect;
      
    • 从NSValue对象取出之前包装的结构体

      - (NSPoint)pointValue;
      - (NSSize)sizeValue;
      - (NSRect)rectValue;
      
    ```
    
    // 1.利用NSValue包装常用的结构体
        /*
        CGPoint point = NSMakePoint(10, 20);
        NSValue *value = [NSValue valueWithPoint:point];
        NSArray *arr = @[value];
        NSLog(@"%@", arr);
         */
        
        // 2.利用NSValue包装自定义的结构体
        typedef struct{
            int age;
            char *name;
            double height;
        }Person;
        
        Person p = {30, "lnj", 1.75};
        // valueWithBytes: 接收一个指针, 需要传递需要包装的结构体的变量的地址
        // objCType: 需要传递需要包装的数据类型
        NSValue *pValue = [NSValue valueWithBytes:&p objCType:@encode(Person)];
        NSArray *arr = @[pValue];
        NSLog(@"%@", arr);
        // 从NSValue中取出自定义的结构体变量
        Person res;
        [pValue getValue:&res];
        NSLog(@"age = %i, name = %s, height = %f", res.age, res.name, res.height);
    
    ```
    

    4.NSDate

    • 结合NSCalendar和NSDate能做更多的日期\时间处理

    • 获得NSCalendar对象

    ```
    NSCalendar *calendar = [NSCalendar currentCalendar];
    ```
    
    • 获得年月日
    // 创建一个时间格式化对象
        NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
        // 告诉时间格式化对象, 按照什么样的格式来格式化时间
        // yyyy 年
        // MM 月
        // dd 日
        // HH 24小时  hh 12小时
        // mm 分钟
        // ss 秒钟
        // Z 时区
    
    - (NSDateComponents *)components:(NSCalendarUnit)unitFlags fromDate:(NSDate *)date;
    

    5.NSFileManager

    • NSFileManager使用了单例模式
      • 使用defaultManager方法可以获得那个单例对象

        [NSFileManager defaultManager]
        
    • NSFileManager的文件操作
      • (BOOL)copyItemAtPath:(NSString )srcPath toPath:(NSString )dstPath error:(NSError **)error;

        • 拷贝
      • (BOOL)moveItemAtPath:(NSString )srcPath toPath:(NSString )dstPath error:(NSError **)error;

        • 移动(剪切)
      • (BOOL)removeItemAtPath:(NSString )path error:(NSError *)error;

        • 删除
      • (BOOL)createDirectoryAtPath:(NSString )path withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary )attributes error:(NSError **)error;

        • 创建文件夹(createIntermediates为YES代表自动创建中间的文件夹)
    • 创建文件夹
    // createDirectoryAtPath: 告诉系统文件夹需要创建到什么位置
        // withIntermediateDirectories: 如果指定的文件中有一些文件夹不存在, 是否自动创建不存在的文件夹
        // attributes: 指定创建出来的文件夹的属性
        // error: 是否创建成功, 如果失败会给传入的参数赋值
        // 注意: 该方法只能用于创建文件夹, 不能用于创建文件
         BOOL flag = [manager createDirectoryAtPath:@"/Users/chenhengjun/Desktop/Beginlnj" withIntermediateDirectories:YES attributes:nil error:nil];
        NSLog(@"%i", flag);
    

    判断一个文件或者文件夹是否存在

     BOOL flag = [manager fileExistsAtPath:@"/Users/chenhengjun/Desktop/Begin"];
    NSLog(@"flag = %i", flag);
    

    判断一个文件是否存在, 并且判断它是否是一个文件夹

    BOOL dir = NO;
    //    BOOL flag = [manager fileExistsAtPath:@"/Users/chenhengjun/Desktop/Begin" isDirectory:&dir];
    //    NSLog(@"flag = %i, dir = %i", flag, dir);
    
    

    获取文件或文件夹的属性

    注意:contentsOfDirectoryAtPath方法有一个弊端, 只能获取当前文件夹下所有的文件, 不能获取子文件夹下面的文件
    //    NSArray *res = [manager contentsOfDirectoryAtPath:@"/Users/chenhengjun/Desktop/Begin" error:nil];
    //    NSLog(@"res = %@", res);
     NSArray *res = [manager subpathsAtPath:@"/Users/chenhengjun/Desktop/Begin"];
    ////    NSArray *res = [manager subpathsOfDirectoryAtPath:@"/Users/chenhengjun/Desktop/Begin" error:nil];
    //    NSLog(@"res = %@", res);
    
    

    创建文件

    createFileAtPath: 指定文件创建出来的位置
        // contents : 文件中的内容
        // attributes: 创建出来的文件的属性
        
        // NSData : 二进制数据
        // 注意: 该方法只能用于创建文件, 不能用于创建文件夹
    //    NSString *str = @"江哥真帅";
    //    NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
    //    [manager createFileAtPath:@"/Users/xiaomage/Desktop/abc.txt" contents:data attributes:nil];
    

    6.Copy

    / 如果是通过不可变对象调用了copy方法, 那么不会生成一个新的对象
    // 原因: 因为原来的对象是不能修改的, 拷贝出来的对象也是不能修改的
    // 既然两个都不能修改, 所以永远不能影响到另外一个对象, 那么已经符合需求
    // 所以: OC为了对内存进行优化, 就不会生成一个新的对象
    NSString *srcStr = @"lnj";
    NSString *copyStr = [srcStr copy];
    NSLog(@"srcStr = %p, copyStr = %p", srcStr, copyStr);
        
    /*
    正是因为调用copy方法有时候会生成一个新的对象, 有时候不会生成一个新的对象
    所以: 如果没有生成新的对象, 我们称之为浅拷贝, 本质就是指针拷贝
         如果生成了新的对象, 我们称之为深拷贝, 本质就是会创建一个新的对象
    */
    
    • copy的三个用途
      * 1.copy的第一个用途, 防止外界修改内部的数据

         * 记住: 以后字符串(NSString)属性都用copy
      
      • 2.可以使用copy保存block, 这样可以保住block中使用的外界对象的命

      • 3.注意点: copy block之后引发循环引用

        • 如果对象中的block又用到了对象自己, 那么为了避免内存泄露, 应该将对象修饰为__block
    • 自定义类实现copy

      • 1.以后想让自定义的对象能够被copy只需要遵守NSCopying协议
      • 2.实现协议中的- (id)copyWithZone:(NSZone *)zone
      • 3.在- (id)copyWithZone:(NSZone *)zone方法中创建一个副本对象, 然后将当前对象的值赋值给副本对象即可
    • 注意点:

      • 如果想让子类在copy的时候保留子类的属性, 那么必须重写copyWithZone方法, 在该方法中先调用父类创建副本设置值, 然后再设置子类特有的值

    7.单例

    + (instancetype)shareTools
    {
        Tools *instance = [[self alloc] init];
        return instance;
    }
    
    static Tools *_instance = nil;
    + (instancetype)allocWithZone:(struct _NSZone *)zone
    {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _instance = [[super allocWithZone:zone] init];
        });
        return _instance;
    }
    
    
    - (id)copyWithZone:(NSZone *)zone{
    
        return _instance;
    }
    
    - (id)mutableCopyWithZone:(NSZone *)zone
    {
        return _instance;
    }
    
    // MRC
    - (oneway void)release
    {
    
    }
    
    - (instancetype)retain
    {
        return _instance;
    }
    
    - (NSUInteger)retainCount
    {
        return  MAXFLOAT;
    }
    
    

    宏定义重构单例

    #define interfaceSingleton(name)  +(instancetype)share##name
    
    
    #if __has_feature(objc_arc)
    // ARC
    #define implementationSingleton(name)  \
    + (instancetype)share##name \
    { \
    name *instance = [[self alloc] init]; \
    return instance; \
    } \
    static name *_instance = nil; \
    + (instancetype)allocWithZone:(struct _NSZone *)zone \
    { \
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{ \
    _instance = [[super allocWithZone:zone] init]; \
    }); \
    return _instance; \
    } \
    - (id)copyWithZone:(NSZone *)zone{ \
    return _instance; \
    } \
    - (id)mutableCopyWithZone:(NSZone *)zone \
    { \
    return _instance; \
    }
    #else
    // MRC
    
    #define implementationSingleton(name)  \
    + (instancetype)share##name \
    { \
    name *instance = [[self alloc] init]; \
    return instance; \
    } \
    static name *_instance = nil; \
    + (instancetype)allocWithZone:(struct _NSZone *)zone \
    { \
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{ \
    _instance = [[super allocWithZone:zone] init]; \
    }); \
    return _instance; \
    } \
    - (id)copyWithZone:(NSZone *)zone{ \
    return _instance; \
    } \
    - (id)mutableCopyWithZone:(NSZone *)zone \
    { \
    return _instance; \
    } \
    - (oneway void)release \
    { \
    } \
    - (instancetype)retain \
    { \
    return _instance; \
    } \
    - (NSUInteger)retainCount \
    { \
    return  MAXFLOAT; \
    }
    #endif
    
    

    相关文章

      网友评论

          本文标题:OC语言基础

          本文链接:https://www.haomeiwen.com/subject/slhxfftx.html