Object-C简介 | 青训营笔记

207 阅读8分钟

这是我参与「第四届青训营 」笔记创作活动的的第2天。

今天尝试学习了老师上课讲的Object-C部分,虽然只是入门,但是比起之前学习的面向对象等还是有一些难度,期待明天的课!

Object-C简介

iOS开发语言

OBJ-C Swift C# HTML+JavaScript React Native Flutter

Objective-C(OC)通用 面向对象程式语言 扩充C语言 C语言在其中合法只多了面向对象

Class的编写:介面与实现

一个OC class🟰类的介面interface➕类的实现Implementation‘

  • 类的介面(interface)

    • @interface开头➕@end结尾
    • interface只是一个声明 可以声明属性、变量和函数方法,不对声明的任何方法进行实现,一个类可以有多个interface,将不同功能的函数分散在这些intreface中,但是都要有对应的实现
  • 类的实现(implementation)

    • @inplementation开头➕@end结尾
    • 在interface声明的方法都需要在implementation实现
    • 可以添加interface中没有声明的函数或变量,作为私有,只有这个implementation内可以访问
// 类的介面(interface)
@interface Mytest : NSObject
// 属性、变量、函数方法声明 不对声明方法实现
@property (nonatomic, copy) NSString* name;
- (void)helloworld;
@end//@interface开头➕@end结尾
// 一个类可以有多个interface
// 类的实现(implementation)
@implementation Mytest
- (void)helloworld {
    // 方法实现
    NSLog(@"My name is %@", self.name);
}
- (void)private_method {
    // 私有,只有当前的implementation能使用本方法
}
@end
// @inplementation开头➕@end结尾

.h文件和.m文件

声明文件.h与实现文件.m

  • interface可以写在 .h文件和 .m文件

    1. .h 文件 可被其他文件看到

      这个interface里的声明,可以被其他的 .h .m文件 import调用

    2. .m 文件 只有该.m文件可用

      只能在这个文件的其它类调用

  • implementation 只能写在 .m文件里

    1. 在 .m文件的顶部引入 .h 文件,才能在实现代码里调用其他类的函数
    2. .m文件不能被引入,只能引入 .h文件

对象与构造函数

  • 创建对象[ [class alloc] init]

    //main.m
    #import "Mytest.h"
    Mytest  *mytest1 = [[Mytest alloc] init];//mytest1是对象指针
    [mytest1 helloworld];
    int staticReturn = [Mytest staticFunction];
    
  • override父类默认声明的构造函数init

    //Mytest.m
    @implementation Mytest
    // Override 父类 NSObject 的 init构造函数
    - (instancetype)init {
        self = [super init];//传入父类init
        if (self) {
            self.name = @"Yancy";
        }
        NSLog(@"Welcome to iosworld!");
        return self;
    }
    @end
    
  • interface声明一个需要参数的构造函数

    // Mytest.h
    @interface Mytest: NSObject
    // MARK: 构造函数 - (instancetype)initWithName:(NSString *)name;
    @end
      
    // Mytest.m
    @implementation Mytest
    - (instancetype)initWithName:(NSString *)name {
        self = [super init];
        if (self) {
            self.name = name;
        }
        NSLog(@" %@ Welcome to iosworld!", name);
        return self;
    }
    @end
    

函数方法

声明➕实现

🌰 +(int)methodName With:(int)paralparaLabel:  (double)para2 

方法类型回传类型方法名标签:参数名&类型

  • 以 +/- 开头,表示函数类型

    • +代表类别方法(class method)不需要实例就可以呼叫,类似C++static函数
    • -代表一般的实例方法(instance method)
// Mytest.h
@interface Mytest: NSObject
// MARK: 函数声明 - (void)helloworld;
// 方法类型(回传值)方法名:(参数1类型)参数1 标签:(参数2类型)参数2
- (void)groupWith:(NSString *)name;
// 返回对象
- (Mytest *)groupWith:(NSString *)name andWith:(NSString *)name2;
​
// 类方法(静态函数)
+ (int)staticFunction;
@end
// Mytest.m
@implementation Mytest
  
- (void)helloworld {
    NSLog(@"helloworld");
}
- (void)groupWith:(NSString *)name {
    NSLog(@"Hi, I'm group with %@", name);
}
- (Mytest *)groupWith:(NSString *)name and:(NSString *):name2 {
    Mytest *group = [[Mytest alloc] initWithName:
    [NSString stringWithFormat:@"%@,%@,%@", self.name, name, name2]];
    return group;
}
+ (int)staticFunction {
    return 0;
}
@end

方法调用

函数的调用写法是通过两个方括号包起来

如果方法是类方法,使用方框 - 类名 - 方法签名 - 方框

//main.m
#import "Mytest.h"
​
Mytest *mytest2 = [[Mytest alloc] initWithName:@"Yancy"];
// 实例方法
[mytest2 sayHello];
// 实例方法 - 多参数, 返回对象
[[mytest2 groupWith:@"Alice" andWith:@"Bob"] sayHello]; 
//第一个函数返回对象后再调用
​
// 类方法 (静态函数)
int staticReturn = [Mytest staticFunction];

成员变量

如果变量要供外部使用,需要在interface里让别人引入

// Mytest.h
@interface Mytest: NSObject{
    //公开变量 @public int public_int_variable; @protected double protected_double_variable; }// Mytest.m
@implementation Mytest {
   // 私有
    int _private_int_variable;
    double _private_double_variable
}
@end
//main.m 外部访问公开成员变量
Mytest3->_public_double_variable = 0.1;
Mytest3->_public_int_variable = 0;
       
// Mytest.m 内部可以访问公开和私有的成员变量
self->_public_double_variable = 0.1;
self->_private_int_variable = 0;
总结
  • @interface和implementation

    • interface 声明代码 包括变量和方法的声明
    • implementation 实现代码 主要对应interface中已经声明的方法(也包括似有方法,只能被同个implementation中的代码调用,同个文件的其他类也不能使用)
  • .h .m文件的区别

    一个类的interface写在.h文件都可以访问,但写在.m只能被当前访问

语言特性

@property属性

  1. 通过 get set 方法访问可以保证入口的唯一性,通过 set get 方法访问,而不是直接访问变量,防止同时改变出现错乱(因为多个地方都能直接存取变量)由此产生属性(不用在interface声明变量)。

    • @property声明属性实际上是

      .h声明了 set get 方法,和实例变量

      .m实现了 set get 方法

// Mytest.h
@interface Mytest: NSObject
// 属性 
@property (nonatomic, copy) NSString* name;
//属性(属性的特性)类型 属性名称
@end
//起到作用等同于下一段代码,不用写下面这么多的代码
// Mytest.h
@interface Mytest: NSObject
// 自动生成 get set 方法
- (void)setName:(NSString *)name;//自动生成的set方法=set+名称首字母大写
- (NSString *)name;//自动生成的get方法=属性名
@end// Mytest.m
@implementation ByteDancer {
    // 特性自动声明变量是带下划线的(_name)
    NSString *_name;
}
// MARK: 自动实现
- (void)setName:(NSString *)name {
    self->_name = name;
}
- (NSString *)name {
    return _name;
}
@end
  1. 在外部通过 . 访问属性,相当于访问对应set get 方法。(自动声明的变量带底线)
//main.m
​
// MARK : 对象外部用.访问
// 属性设置 == [Mytest setName:@"Yancy"]
mytest3.name = @"Yancy";
// 访问属性 == [mytest3 name];
 NSString *yourName = mytest3.name;
​
 //MARK : 内部
 self.name = @"James";
 self->_name =  @"James";
  1. 在类的实现里可以override get / set 方法,但要注意区分变量和属性,否则会死循环。

    🌰比如在属性生成的 set 方法设置属性(set方法),会导致死循环,正确用法是直接个带底线的变量。

❌
- (void)setName:(NSString *)name {
    self.name = [NSString stringWithFormat:@"%@ %@", name, @"!"];
}
✅
- (void)setName:(NSString *)name {
    _name = [NSString stringWithFormat:@"%@ %@", name, @"!"];
}
属性(property)特性(attribute)
  • 访问原子性 默认:atomic

    • atmic(原子性)如果有多个线程同时调用setter,不会出现某一个线程执行完setter的全部语句之前,另一个线程开始执行setter的情况,相当于函数头尾加锁,保证多线程情况下线程安全,但是浪费嗲量系统资源
    • nonatomic(非原子性)不加同步,多线程并发访问会提高性能,但有坑不安全,如果没有使用多线程间的通讯编程,使用nonatomic很好。
  • 存取特性 默认:readwrite

    • readwrite 生成 getter setter 方法(读写)
    • readonly 只生成 getter 方法(只读)
  • 内存管理 默认:strong(weak copy assign)

    • strong 只能用于对象类型 需要引用这个对象 负责保持这个对象的生命周期
    • weak 给一个指向对象的引用 但不主张所有权 对象被销毁 weak引用=null
    • copy 复制一个对象 指向新对象 防止属性被意外修改
    • assign 类似weak 但不设置为null 变野指针 再访问程序crash
  • 重命名 get 方法 getter

  • 重命名 set 方法 setter

  • 是否可为null 默认 null_unspecified(nullable,nonnull)

    • nullable:对象可为空
    • nonnull:对象不可为空
    • null_unspecified:对象未指定
属性总结
  • 几乎所有情况,都写上nonatomic
  • 对外「只读」的,写上readonly
  • 一般的对象属性,写上strong(用retain也可以,比较少用)
  • 需要解决strong reference cycles问题的对象属性,strong改为weak
  • 有mutable(可变)版本的对象属性,strong改为copy
  • 基本数据类型(int, float, BOOL)(非对象属性),用assign

协议@protocol

协议类似父类,不同的类有共通的方法/变量/介面,不需要每个类各自interface声明方法,一个类可以遵循多个协议,但一个类只能继承一个父类

  • 协议声明
//Mytest.h
@protocol SeniorLevel <NSObject>//协议名+继承协议
@required
-(void)doCodeReview;
@optional
-(void)writeDailyReportAt:(NSDate *)date;
@end
  • 遵守协议
#import "Mytest.h"
// Mytest.h
@interface SenioriOSDeveloper: Mytest <SeniorLevel, ClientDeveloper>//SenioriOSDeveloper继承Mytest(父类只有一个)遵守<>中协议(协议可以多个)
@end
@interface JunioriOSDeveloper: Mytest <JuniorLevel, ClientDeveloper>
// myMentor 类型是任何遵守 id<SeniorLevel> 的类
@property (strong, nonatomic) id<SeniorLevel> myMentor;
@end
// ByteDancerProtocol.h@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
  • 类实现协议方法&创建遵守协议的对象与调用
// ByteDancer.m
@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
//main.m
// 任何遵守 SeniorLevel 的对象都可以被指派
id <SeniorLevel> seniorEmployee
    = [[SenioriOSDeveloper alloc] initWithName:@"Bob"];
JunioriOSDeveloper *junioriOSDeveloper
    = [[JunioriOSDeveloper alloc] initWithName:@"James"];
// 也可以使用原本的方式创建对象
[junioriOSDeveloper assignMentor:seniorEmployee];
[junioriOSDeveloper doCoding];
[seniorEmployee doCodeReview];
  • 协议的设计

    • 委托方(声明协议,并持有委托对象属性)
    • 代理方(遵守协议,并成为代理方的委托对象)
    • 代理方到委托方 数据源模式(dataSource)
    • 委托方到代理方 委托模式(delegate)
  • 协议声明&系统类和自定义类

//CommonProtocolDefineExample.h 
@protocol CommonDataSource <NSObject> //协议CommonDataSource
// MARK: DataSource 一般用来要求协议的遵守者,提供数据或是其他信息 
- (NSString *)dataRequestFrom:(NSString *)source; 
- (int)numerOfDatasFrom:(NSString *)source; 
@end  
@protocol CommonDelegate <NSObject> //协议CommonDelegate
// MARK: Delegate 一般用来要求协议的遵守者,提供数据或是其他信息 
- (void)onSessionStateChange:(int)oldState newState:(int)newState; - (void)onError:(NSError *)error; @end
//MARK: 假设 系统网路请求类 
@interface SystemNetworkService : NSObject 
// MARK: 底层的持有 DataSource和Delegate 
@property (weak, nonatomic) id<CommonDelegate> delegate;
//遵守delegate的对象
@property (weak, nonatomic) id<CommonDataSource> datasource; 
//遵守datasource的对象
@end  
//由自定义的类遵守两个协议 
@interface MyClass : NSObject <CommonDataSource, CommonDelegate>  @end

方法=讯息传递

OC里,调用对象的方法,可以看做是给对象发送一个消息,但是如何处理这条消息在运行时程序才去决定要如何去回应这个消息(可以使用runtime框架 运行时动态替换方法的实现指针)。

调用可以使用 respondsToSelector 这个方法,并传入方法签名(也称为选择器)去确认这个对象有没有响应这个方法,如果有那就调用方法,如果没有那就使用默认数据。

MyClass *myObject = [[MyClass alloc] init];
if ([myObject respondsToSelector: @selector(numerOfDatasFrom:)]) {
    int dataCount = [myObject numerOfDatasFrom:@"main"];
    NSLog(@"%d", dataCount);
} else {
    // MyObject 不回应我的 numerOfDatasFrom 消息(没实现)
    int dataCount = -1;
    NSLog(@"%d", dataCount);
}

引用文章

第二节:Xcode & Objective-C 简介

Objective-C属性特性