Xcode & Objective-C 简介|青训营笔记

477 阅读6分钟

这是我参与「第四届青训营」笔记创作活动的第1天,记录了7月23日iOS端青训营第二课的课堂内容。

课堂重点

  本次课程介绍了Xcode工具的项目结构和基础功能,并且讲解了Objective-C语言的基本语法特征,用OC实现了简单的命令行程序。

Xcode

  集合了编译、测试、Git,可以将app直接提交到AppStore

工程体系

  工厂(Workspace&Project)里有多条生产线(Target),它们可以共用同样的原料和机器(Project中包含的源代码、资源文件、第三方库等)

  • Workspace:一个工作空间,可以包含多个Project,组合成一个庞大复杂的工程
  • Project:工程的核心,可以包含多个Target,管理源代码、资源文件、第三方库等
    • 后缀为.pbxproj的文件,Old-Style ASCII Property Lists的格式,记录了:
      • 对文件的引用
        • 源代码
        • 资源文件
        • Framework, Library
      • 虚拟文件夹,不影响真实项目目录结构
      • 应包括的Target
      • 构建配置
  • Target:特定的构建目标(App主体、命令行工具、代码二进制库……),可以利用所在Project中的(部分或者全部)源代码和资源文件,定义了系统的所有输入和文件的处理配置(Build Phases, Build Setting),其对应的构建产物是Product,即运行最终结果

image.png

课堂例图

Objective-C

  面向对象的程序语言,是标准C语言的扩充,所以C语言代码在这里可以合法通过编译。

OC的类由Interface和Implementation两部分组成

  • interface可以声明属性、变量和函数方法,但是不实现,声明内容包含在@interface和@end中间。
    • 一个类可以有多个interface
    // 类的Interface开始
    @interface ByteDancer : NSObject
    @property (nonatomic, copy) NSString* name;
    - (void)printHello;
    @end
    // 类的Interface结束
    
  • implementation主要实现在interface中声明的方法,实现内容包含在@implementation和@end之间。
    • 每一个声明的方法都需要实现,否则编译不通过
    • 如果实现了interface中没有声明的方法或变量,那么将被视作private,只能在这个implementation中被访问
    // 类implementation 开始
    @implementation ByteDancer
    - (void)printHello {
        // 实现已经声明的方法
        NSLog(@"My name is %@", self.name);
    }
    - (void)private_method {
        // 实现没有声明的方法
        NSLog(@"hello");
    }
    @end
    // 类implementation 结束
    

.h文件和.m文件

  .h文件可以被其他.h和.m文件import,而.m文件不可以被import

  • OC类的@interface部分可以写在.h或者.m文件中
    • 在.h文件中,该声明可以被其他的.h或者.m引入和调用
    • 在.m文件中,只能被当前文件的其他类调用
  • OC类的@implementation部分只能写在.m文件中
    • .m引入了对应的.h文件,才能调用外部方法的实现 <-> 必须声明在.h中,才能被其他.m调用

对象与构造函数

  • 创建对象:创建的是对象指针,必须带*
    #import "ByteDancer.h"
    ByteDancer *byteDancer1 = [[ByteDancer alloc] init];
    
    [byteDancer1 printHello];
    int staticReturn = [ByteDancer staticFunction];
    
  • 构造函数:
    • 重写父类默认声明的构造函数init
      @implementation ByteDancer
      // 重写父类NSObject的init
      - (instancetype)init {
          // 需要先调用父类的init
          self = [super init];
          if(self) {
              self.name = @"James";
          }
          NSLog(@"Joined in!");
          return self;
      }
      
    • 通过interface声明一个需要参数的构造函数
      //在ByteDancer.h里声明
      @interface ByteDancer: NSObject
      // 构造函数 
      - (instancetype)initWithName:(NSString *)name;
      @end
      
      // 在ByteDancer.m里实现
      @implementation ByteDancer
      - (instancetype)initWithName:(NSString *)name {
          // 需要先调用父类的init
          self = [super init];
          if (self) {
              self.name = name;
          }
          NSLog(@"A ByteDancer, %@ Joined!", name);
          return self;
      }
      @end
      

函数方法

  • 无论是在interface还是complementation,方法均以+/-符号开头
    • 加号表示类方法,无实例化,相当于cpp和Java中的static
    • 减号表示实例方法
    // 在ByteDancer.h中声明
    @interface ByteDancer: NSObject
    // (回传值)方法名:(参数1类型)参数1 标签:(参数2类型)参数2
    - (void)sayHello;
    - (void)groupWith:(NSString *)name;
    - (ByteDancer *)groupWith:(NSString *)name andWith:(NSString *)name2;
    // 类方法(静态函数)
    + (int)staticFunction;
    @end
    
    
    // 在ByteDancer.m中实现
    @implementation ByteDancer
    // 与 Interface一一对应,但完成函数主体实现
    - (void)sayHello {
        NSLog(@"Hi");
    }
    - (void)groupWith:(NSString *)name {
        NSLog(@"Hi, I'm group with %@", name);
    }
    - (ByteDancer *)groupWith:(NSString *)name andWith:(NSString *):name2 {
        ByteDancer *group = [[ByteDancer alloc] initWithName:
        [NSString stringWithFormat:@"%@,%@,%@", self.name, name, name2]];
        return group;
    }
    + (int)staticFunction {
        return 0;
    }
    @end
    
  • 函数调用:用方括号包起来。如果是类方法,用类名+方法名;如果是实例方法,一般用对象名+方法名
#import "ByteDancer.h"
// 创建对象
ByteDancer *byteDancer2 = [[ByteDancer alloc] initWithName:@"James"];
// 实例方法,没有参数
[byteDancer2 sayHello];
// 实例方法,多参数、返回对象
//第一个函数groupWith返回对象group后,再调用它的sayHello
[[byteDancer2 groupWith:@"Andy" andWith:@"Amy"] sayHello]; 

// 类方法 (静态函数)
int staticReturn = [ByteDancer staticFunction];

成员变量

  • 只有放在.h文件的interface里才可以是公开变量,可以被外部调用访问;其余都是私有变量,只允许在相同.m文件里的方法访问。
    • public:类内类外均可访问
    • protected:类及其子类可以访问
    • private:仅类内可访问
      (类内:@implementation和@end之间)
// ByteDancer.h
@interface ByteDancer: NSObject{
    //公开变量 
    @public int public_int_variable;
    @protected double protected_double_variable; 
}

// ByteDancer.m
@implementation ByteDancer {
   // 私有
    int _private_int_variable;
    double _private_double_variable;
}
@end

语言特性

  • @property属性
    • 用该关键字代替在@interface中声明变量
    // ByteDancer.h
    @interface ByteDancer: NSObject
    @property (nonatomic, copy) NSString* name;
    @end
    
    • 在类外也可以通过.(点符号)来访问
    // 外部
    // 属性设置(name为同名变量):相当于[byteDancer3 setName:@"James"]
    byteDancer3.name = @"James";
    // 访问属性(name为同名变量):相当于[byteDancer3 name]
    NSString *yourName = byteDancer3.name;
    
    //内部
    self.name = @"James";
    self->_name =  @"James";
    
    • 声明属性(name) = 声明私有变量(_name,自动生成的下划线开头) + 声明并实现对应的get/set方法(getName/setName)

      注意⚠️
      变量和属性的区别!为属性重写get/set方法时,更改的是私有变量,而不是属性
    // 正确案例
    @implementation ByteDancer
    /* 覆盖 Set 方法
     * 更改私有属性_name */
    - (void)setName:(NSString *)name {
        _name = [NSString stringWithFormat:@"%@ %@", name, @"!"];
    }
    @end
    
    // 错误案例
    @implementation ByteDancer
    /* 覆盖 Set 方法
     * .name的调用相当于使用getName/setName,
     * 而我们重写的本身就是get/set方法,却又在使用旧的方法,
     * 这样程序就陷入了一种悖论般的死循环 */
    - (void)setName:(NSString *)name {
        self.name = [NSString stringWithFormat:@"%@ %@", name, @"!"];
    }
    @end
    

协议

  • 与父类在目的上有共同点:多个类有共用的方法(即共用的interface),无需各自重新声明方法
  • 协议声明
@protocol SeniorLevel <NSObject>
- (void)doCodeReview;
@end

@protocol JuniorLevel <NSObject>
// id<SeniorLevel> 代表任何遵守SeniorLevel的类
- (void)assignMentor:(id<SeniorLevel>)mentor;
@end

// 协议可以继承多个协议
@protocol Developer <NSObject>
- (bool)doCoding;
@end
@protocol ClientDeveloper <Developer>
@end
  • 类遵守协议
# import "ByteDancerProtocol.h"
@interface SenioriOSDeveloper: ByteDancer <SeniorLevel, ClientDeveloper>
@end

@interface JunioriOSDeveloper: ByteDancer <JuniorLevel, ClientDeveloper>
// myMentor 类型是任何遵守 SeniorLevel 的类
@property (strong, nonatomic) id<SeniorLevel> myMentor;
@end
  • 类实现协议方法
@implementation SenioriOSDeveloper
- (bool)doCoding{
    return YES;
}
- (void)doCodeReview {
    NSLog(@"Code Review Done");
}
@end

@implementation JunioriOSDeveloper
- (bool)doCoding {
    return YES;
}
- (void)assignMentor:(id<SeniorLevel>)mentor {
    self.myMentor = mentor;
}
@end
  • 创建和调用对象
// 任何遵守 SeniorLevel 的对象
id <SeniorLevel> seniorEmployee = [[SenioriOSDeveloper alloc] initWithName:@"Bob"];
// 指定的对象
JunioriOSDeveloper *junioriOSDeveloper = [[JunioriOSDeveloper alloc] initWithName:@"James"];
// 调用
[junioriOSDeveloper assignMentor:seniorEmployee];
[junioriOSDeveloper doCoding];
[seniorEmployee doCodeReview];

方法调用 = 发送信息

  C++函数在编译时确定a调用b的行为(呈现在汇编代码阶段),OC将调用作为信息传递的行为,在运行时才确定回应方式。出于这样的特性:

  • 存在Runtime框架:在程序运行时,动态替换方法的实现指针、新增或删除方法。
  • 使用respondsToSelector进行方法调用(传入方法名):如果对象响应了,表明方法存在可以直接调用;如果没有响应,说明不存在该方法。
MyClass *myObject = [[MyClass alloc] init];
if ([myObject respondsToSelector: @selector(numerOfDatasFrom:)]) {
    int dataCount = [myObject numerOfDatasFrom:@"main"];
    NSLog(@"%d", dataCount);
} else {
    int dataCount = -1;
    NSLog(@"%d", dataCount);
}

总结

  快速学习了Xcode和Objective-C的基本知识,是学校和自学速度的100000倍😢消化了好久好久诶。