OC系列之构造方法

一 OC中的私有方法

  • OC中的私有变量

    • 实例变量(成员变量)既可以在@interface中定义,也可以在@implementation中定义;
    • 写在@implementation中的成员变量,默认就是私有成员变量,在其他类中无法查看,也不能访问(只能在本类中访问)。
  • OC中的私有方法

    • 私有方法:只有实现没有声明的方法
    • 原则上:私有方法只能在本类中的方法的中才能调用

注意:在oc中没有真正的私有方法,因为oc是消息机制

二 @property基本概念

  • 什么是@property

    • @property是编译器的指令
  • @property会让编译器做什么呢?

    • @property 用在声明文件中告诉编译器声明成员变量的的访问器(getter/setter)方法
    • 这样的好处是免去我们手工书写getterr和setter方法繁琐的代码
  • @property基本使用

    • 在@inteface中,用来自动生成setter和getter的声明
      @property int age;就可以代替下面的两行
      - (int)age;   // getter
      - (void)setAge:(int)age;  // setter
  • @property编写步骤:

    • 1.在@inteface和@end之间写上@property
    • 2.在@property后面写上需要生成getter/setter方法声明的属性名称, 注意因为getter/setter方法名称中得属性不需要, 所以@property后的属性也不需要.并且@property和属性名称之间要用空格隔开
    • 3.在@property和属性名字之间告诉需要生成的属性的数据类型, 注意两边都需要加上空格隔开
  • @property增强

    • 默认情况下,setter和getter方法中的实现,会去访问下划线 _ 开头的成员变量。
    • 如果没有会自动生成一个_开头的成员变量,自动生成的成员变量是私有变量, 声明在.m中,在其它文件中无法查看,但当可以在本类中查看
    • @property只会生成最简单的getter/setter方法,而不会进行数据判断
    • 如果需要对数据进行判断需要我们之间重写getter/setter方法
      • 若手动实现了setter方法,编译器就只会自动生成getter方法
      • 若手动实现了getter方法,编译器就只会自动生成setter方法
      • 若同时手动实现了setter和getter方法,编译器就不会自动生成不存在的成员变量

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

  • @property修饰符

    • 修饰是否生成getter方法的
      • readonly 只生成setter方法,不生成getter方法
      • readwrite 既生成getter 又生成setter方法(默认)
    • 指定所生成的方法的方法名称
      • getter=你定制的getter方法名称
      • setter=你定义的setter方法名称(注意setter方法必须要有 :)
        @property (getter=isMarried)  BOOL  married;
         //  通常BOOL类型的属性的getter方法要以is开头
        

三 @synthesize基本概念

  • 什么是@synthesize

    • @synthesize是编译器的指令
  • @synthesize基本使用

    • 在@implementation中, 用来自动生成setter和getter的实现
      @synthesize age = _age;就可以代替
      - (int)age{
      return _age;
      }
      - (void)setAge:(int)age{
      _age = age;
      }
  • @synthesize编写步骤

    • 1.在@implementation和@end之间写上@synthesize
    • 2.在@synthesize后面写上和@property中一样的属性名称, 这样@synthesize就会将@property生成的属性名称拷贝到@implementation中
    • 3.由于getter/setter方法实现是要将传入的形参给属性和获取属性的值,所以在@synthesize的属性后面写上要将传入的值赋值给谁和要返回哪个属性的值, 并用等号连接
  • @synthesize注意点

    • @synthesize age = _age;

      • setter和getter实现中会访问成员变量_age
      • 如果成员变量_age不存在,就会自动生成一个@private的成员变量_age
    • @synthesize age;

      • setter和getter实现中会访问@synthesize后同名成员变量age
      • 如果成员变量age不存在,就会自动生成一个@private的成员变量age
    • 多个属性可以通过一行@synthesize搞定,多个属性之间用逗号连接

      @synthesize age = _age, number = _number, name  = _name;

四 id类型

  • id是一个数据类型,是一个动态数据类型

  • 静态数据类型的特点:

    • 在编译时就知道变量的类型,
    • 知道变量中有哪些属性和方法
    • 在编译的时候就可以访问这些属性和方法,
    • 如果是通过静态数据类型定义变量, 如果访问了不属于静态数据类型的属性和方法, 那么编译器就会报错
  • 动态数据类型的特点:

    • 在编译的时候编译器并不知道变量的真实类型, 只有在运行的时候才知道它的真实类型
    • 如果通过动态数据类型定义变量, 如果访问了不属于动态数据类型的属性和方法, 编译器不会报错
 id == NSObject * 万能指针
 idNSObject *的区别: NSObject *是一个静态数据类型,id是一个动态数据类型;

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

弊端: 由于动态数据类型可以调用任意方法, 所以有可能调用到不属于自己的方法, 而编译时又不会报错, 所以可能导致运行时的错误

应用场景: 多态, 可以减少代码量, 避免调用子类特有的方法需要强制类型转换

  • 为了避免动态数据类型引发的运行时错误,一般情况使用动态数据类型定义一个变量,在调用这个变量的方法之前在进行一次判断,判断当前变量是否能够调用这个方法

    • - (BOOL)isKindOfClass:classObj 判断实例对象是否是这个类或者这个类的子类的实例
    • - (BOOL) isMemberOfClass: classObj 判断是否是这个类的实例
    • + (BOOL) isSubclassOfClass:classObj 判断类是否是指定类的子类)

五 new方法实现原理

  • new方法实现原理

    • 完整的创建一个可用的对象:Person *p=[Person new];new方法的内部会分别调用两个方法来完成3件事情:
      • (1)使用alloc方法来分配存储空间(返回分配的对象);
      • (2)使用init方法来对对象进行初始化。
      • (3)返回对象的首地址
  • 可以把new方法拆开如下:

    • (1).调用类方法+alloc分配存储空间,返回未经初始化的对象Person *p1=[person alloc];
    • (2).调用对象方法-init进行初始化,返回对象本身
      Person *p2=[p1 init];
    • (3).以上两个过程整合为一句:Person *p=[[Person alloc] init];

说明:
alloc 与 init合起来称为构造方法,表示构造一个对象
alloc 方法为对象分配存储空间,并将所分配这一块区域全部清0
init方法是初始化方法(构造方法),用来对象成员变量进行初始化,默认实现是一个空方法。

  • 所以下面两句的作用是等价的
Person *p1 = [Person new];
Person *p = [[Person alloc] init];

iOS 程序通常使用[[类名 alloc] init] 的方式创建对象,因为这个可以与其他initWithXX:…的初始化方法,统一来。

六 构造方法

  • 构造方法的用途

    • 用于初始化一个对象,让某个对象一创建出来就拥有某些属性和值
  • 重写init方法

      1. 先初始化父类,在初始化子类;self = [super init];
      1. 判断父类是否初始化成功,只有父类初始化成功才能继续初始化子类;

        ifself != nil) {                 
        //  (“ != nil”可以省略)--常写格式:ifself = [super init])
        初始化子类,设置属性值
        }
      2. 返回当前对象的地址;return self;

- (id)init {
    self = [super init];
    if (self) {
        // Initialize self.
    }
    return self;
}
  • 在重写构造方法的时候应该首先对从父类继承而来的成员变量先进行初始化。

    • 原则:先初始化父类的,再初始化子类的。
      • 先调用父类的构造方法[super init];
      • 再进行子类内部成员变量的初始化。
    • 重写构造方法的目的:为了让对象方法一创建出来,成员变量就会有一些固定的值。
  • instancetype的作用

    • instancetype与id相似,不过instancetype只能作为方法返回值,它会进行类型检查,如果创建出来的对象,赋值了不相干的对象就会有一个警告信息,防止出错。

注意: 以后但凡自定义构造方法, 返回值尽量使用instancetype, 不要使用id

七 自定义构造方法

  • 自定义构造方法的规范

    • (1)一定是对象方法,以减号开头
    • (2)返回值一般是instancetype类型
    • (3)方法名必须以initWith开头
  • 代码示例

@interface Person : NSObject

@property int age;
@property NSString *name;

// 当想让对象一创建就拥有一些指定的值,就可以使用自定义构造方法
- (id)initWithAge:(int)age;
- (id)initWithName:(NSString *)name;
- (id)initWithAge:(int)age andName:(NSString *)name; // 多个参数

@end
  • 继承中的自定义构造方法

    • 不能在子类访问父类私有变量

      @interface Person : NSObject
      
      @property int age;
      
      - (id)initWithAge:(int)age;
      @end
      
      
      @interface Student : Person
      
      @property NSString *name;
      
      - (id)initWithAge:(int)age andName:(NSString *)name;
      @end
      
      @implementation Student
      
      - (id)initWithAge:(int)age andName:(NSString *)name
      {
      if (self = [super init]) {
      //     这个_Age是父类中通过property自动在.m中生成的无法继承,不能直接访问
      //      _age = age;
          [self setAge:age];
          _name = name;
      }
      return self;
      }
      @end
    • 父类的属性交给父类的方法来处理

      @interface Student : Person
      
      @property NSString *name;
      
      - (id)initWithAge:(int)age andName:(NSString *)name;
      @end
      
      @implementation Student
      
      - (id)initWithAge:(int)age andName:(NSString *)name
      {
      if (self = [super initWithAge:age]) {
          _name = name;
      }
      return self;
      }
      @end

八 自定义类工厂方法

  • 什么是工厂方法(快速创建方法)

    • 类工厂方法是一种用于分配、初始化实例并返回一个它自己的实例的类方法。
  • 自定义类工厂方法的规范

    • (1)一定是+号开头
    • (2)返回值一般是instancetype类型
    • (3)方法名称以类名开头,首字母小写
  • 子父类中的类工厂方法

    • 由于子类默认会继承父类所有的方法和属性, 所以类工厂方法也会被继承
    • 由于父类的类工厂方法创建实例对象时是使用父类的类创建的, 所以如果子类调用父类的类工厂方法创建实例对象,创建出来的还是父类的实例对象
      • 为了解决这个问题, 以后在自定义类工厂时候不要利用父类创建实例对象, 改为使用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:@"xxx"];
}

九 类的本质

  • 类其实也是一个对象,这个对象会在这个类第一次被使用的时候创建;
  • 只要有了类对象,将来就可以通过类对象来创建实例对象;
  • 实例对象中有一个isa指针, 指向创建自己的类对象;

  • 如何获取类对象

    • 通过实例对象
    格式:[实例对象   class ];
    如:   [dog class];
    • 通过类名获取(类名其实就是类对象)
    格式:[类名 class];
    如:[Dog class]
  • OC实例对象类对象元数据之间关系
    这里写图片描述

十 类的启动过程

  • +load方法

    • 只要程序启动就会将所有类的代码加载到内存中, 放到代码区
    • load方法会在当前类被加载到内存的时候调用, 有且仅会调用一次
    • 如果存在继承关系, 会先调用父类的load方法, 再调用子类的load方法
  • +initialize方法

    • 当前类第一次被使用的时候就会调用(创建类对象的时候)
    • initialize方法在整个程序的运行过程中只会被调用一次, 无论你使用多少次这个类都只会调用一次
    • initialize用于对某一个类进行一次性的初始化

注意:只要类一启动,就会调用load方法,如果用到类,就会调用initialize方法,2个方法都仅会调用一次,initialize和load一样,如果存在继承关系,会先调用父类的initialize再调用子类的initialize。

十一 SEL类型

  • 什么是SEL类型
    • SEL类型代表着方法的签名,在类对象的方法列表中存储着该签名与方法代码的对应关系
  • SEL类型的定义:typedef struct objc_selector *SEL;

  • 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"];
      
      // 注意:performSelector最多只能传递2个参数
      SEL sel2 = @selector(sendMessageWithNumber:andContetn:);
      [p performSelector:sel2 withObject:@"138383438" withObject:@"abcdefg"];
      
    • 3.配合对象将SEL类型作为方法的形参

      @implementation Person
      
      - (void)makeObject:(id) obj performSelector:(SEL) selector
      {
      [obj performSelector:selector];
      }
      @end
      
      int main(int argc, const char * argv[]) {
      
      Person *p = [Person new];
      SEL s1 = @selector(eat);
      Dog *d = [Dog new];
      [p makeObject:d performSelector:s1];
      
      return 0;
      }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OCObjective-C)是一种面向对象的编程语言,它支持在中使用方法和block。方法是在中定义的方法,可以直接通过名来调用,而不需要实例化对象。而block是一种闭包,可以在代码中定义并传递给方法或函数,用于延迟执行特定的逻辑。 在OC中,可以使用方法来创建和操作的实例,例如通过一个工厂方法创建对象,或者在方法中实现一些与相关的逻辑。方法通常使用“+”符号进行声明和实现。 而block可以在方法中作为参数传递,也可以在方法中定义和使用。block可以捕获其所在作用域的变量,可以在方法内部延迟执行一段代码,也可以用于实现回调等功能。block的定义和使用使用“^(){}”语法。 方法和block可以结合使用,例如可以在方法中接受一个block作为参数,并在合适的时机调用该block,以实现一些灵活的逻辑。通过方法和block的组合,可以在OC中实现更加灵活和强大的功能,例如在异步操作中使用block来回调结果,或者在工厂方法中使用block来定制对象的初始化逻辑等。 总而言之,方法和block是OC中的两个重要特性,它们可以分别用于的操作和延迟执行逻辑,也可以结合使用以实现更加灵活的功能。在实际的OC开发中,方法和block通常会被广泛使用,可以帮助开发者更加简洁和灵活地实现代码逻辑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值