Objective-C 学习笔记

数据类型

基本数据类型

标准 C 数据类型也是可以用的

int someInt = 42;
float someFloat = 3.2f;

局部变量在栈上分配,而对象在堆上分配

BOOL a = YES;

非基本

// 根类型
NSObject

// 不可变类型
NSString : NSObject
NSNumber : NSObject

// 可变类型
NSMutableString : NSString
NSMutableArray : NSArray
NSMutableDictionary : NSDictionary

字符串和数字

NSString *str = @"fuckyou";
NSString *str2 = @"yes";

NSUInteger len = [str length]; // 7
NSUInteger len2 = str.length; // 7

BOOL eq = [str isEqualToString:str2];
BOOL endsWithU = [str hasSuffix:@"u"];

// split
NSArray *components = [pair componentsSeparatedByString:@"="];
NSNumber *num = [[NSNumber alloc] initWithInt:42];

// 使用工厂方法 更方便
NSNumber *num2 = [NSNumber numberWithInt:43];

// 使用 boxed expression
NSNumber *num3 = @(84 / 2);

oc 需要手动装箱/拆箱

NSNumber *num1 = 10; // 不行,不会自动装箱,需要加 @
NSNumber *num1 = @10; // OK
int num2 = num1; // 这样只会拿到 num1 对象的地址
int num2 = [num1 intValue]; // OK 手动拆箱
NSLog(@"%@, %d", num1, num2);

intValue 是 NSValue 类的方法

数组

可变 / 不可变

NSArray *arr = @[ @1, @2, @3 ];

NSMutableArray *mutArr = [[NSMutableArray alloc] initWithArray:arr];
[mutArr addObject:@4];

// enumerator 遍历
NSEnumerator *enumerator = [arr objectEnumerator];
id thing = nil;
while (thing = [enumerator nextObject]) {
    NSLog(@"thing: %@", thing);
}

// C 语言方式
for (int i = 0; i < arr.count; i++) {
    NSLog(@"for: %@", arr[i]);
}

// for-in 方式
for (NSNumber *num in arr) {
    NSLog(@"for in: %@", num);
}

字典

可变 / 不可变

NSDictionary *myDict = @{
    @"name": @"John",
    @"age": @123,
    @"male": @YES,
};

NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:myDict];
[mutDict addEntriesFromDictionary:@{
            @"dynamic": @YES,
        }];

打印

NSLog(@"I am a line of log");

NSLog(@"%@", greeting);

方法

  • 调用的语法和类 C 语法略有不同
  • 每个参数名都是方法名的一部分(这样一来跟重载也差不多了)

调用

UIViewController *vc = [[UIViewController alloc] init];

在类 C 语言中相当于

UIViewController *vc = UIViewController.alloc().init()

传参

UIView *view = [[UIView alloc] init];
view.backgroundColor = [UIColor greenColor];
view.frame = CGRectMake(150, 150, 100, 100);
[self.view addSubview:view];

类似于

UIView *view = UIView.alloc().init();
view.backgroundColor = UIColor.greenColor();
view.frame = {
    x: 150,
    y: 150,
    width: 100,
    height: 100
};
self.view.addSubView(view);

传单个参数的例子

#import "Foundation/NSObjCRuntime.h"
#import "XYZPerson.h"

@implementation XYZPerson


- (void) saySomething:(NSString *)word {
    NSLog(@"%@", word);
}

- (void) sayHello{
    [self saySomething:@"Hello"];
}

@end

OOP

声明/实现/头文件

类的声明一般放在 .h 文件

类的实现放在 .m 文件

声明

// MyViewController.h

@interface MyViewController : UIViewController

// 属性应该写到 interface 里面
@property NSString *name;
@property NSNumber *id;
@property (readonly) NSString *readOnlyName;

// 减号表示是实例的方法
- (void)someMethod;

@end

实现

#import "MyViewController.h"

@implementation MyViewController

- (instancetype)init{
    self = [super init];
    if (self) {
        
    }
    return self;
}

- (void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
}


@end

初始化

初始化的时候,alloc 和 init 必须连续调用,然后拿最后那个返回值

  • alloc 的作用是清除要分配的内存区域的脏数据
  • init 的作用是给各种属性分配合适的初始值
// 用法 1
MyViewController *myVc = [[MyViewController alloc] init];

// 用法 2, 它实际上与不带参数调用alloc和init相同
MyViewController *myVc = [MyViewController new];

似乎没有抽象类和虚函数?

属性

  • 在头文件中使用 @property 声明
  • 在实现文件中使用 @synthesize 为变量自动创建 getter/setter 函数(可以忽略,会自动创建)
  • 如果有自定义的 getter/setter 方法,会覆盖默认创建的方法
  • 外部可见的属性(公开属性),在头文件里写声明;私有属性在实现文件里写声明
特性公开属性(.h)私有属性(类扩展)
可见性外部可见,可直接访问仅类内部可见
子类继承子类可继承和访问子类不可见
内存管理需遵循属性修饰符规则(如 strong)同公开属性
适用场景需外部访问的属性(如 UI 配置)内部状态(如缓存、计数器)
// Person.h
@interface Person : NSObject
@property float age;
@property BOOL isMan;

-(void)somePublicMethod;
@end
// Person.m
@implementation Person

@end

私有属性在实现文件里写

// Person.h
@interface Person ()
@property float weight;
@end

一些发展历程:

  • 最开始的时候没有 @property 语法,每个属性的 getter/setter 方法要自己去写,访问属性/设置属性,都是通过方法来进行的,例如 [person age] 和 [person setAge:28]
  • 后来有了 @property 和 @synthesize 之后,属性只需要在头文件或者实现文件里声明,编译器会自动生成 getter/setter 方法(但走的不是单纯代码生成的逻辑,苹果隐藏了实现细节),此时访问属性和设置属性,仍然都要用方法
  • 再后来有了点语法,才能够通过点语法来访问和设置属性,例如 person.age 和 person.age = 28,但其实点语法也只是语法糖,出现在等号左边的时候其实是会隐式调用 setter 方法,出现在等号右边的时候会调用 getter 方法

类的写法

@interface + @implementation

@class 用来创建一个前向引用,加快编译速度

类别 category

用于给现有的类添加方法

创建一个带加号的头文件 NSString+SpecialFix.h

#import <Foundation/Foundation.h>

@interface NSString (SpecialFix)
-(NSNumber*)lengthAsNumber;
@end

同样地,创建一个带加号的实现文件 NSString+SpecialFix.m

#import "NSString+SpecialFix.h"

@implementation NSString (SpecialFix)
-(NSNumber*)lengthAsNumber {
    NSUInteger length = [self length];
    return [NSNumber numberWithUnsignedInt:length];
}
@end

此时在其他地方,只需要引入头文件 NSString+SpecialFix.h,就能够在 NSString 上使用新方法了

#import "NSString+SpecialFix.h"

NSNumber *len = [@"hello" lengthAsNumber];

协议 protocol

基本上就是 Java 的 interface

协议用头文件声明,例如 Encoder.h 写以下内容

@protocol Encoder <NSObject>

-(NSString*)encode;

@end

类 A 遵守协议 B,等同于 Java 里面 class A implements B {}

需要做两件事情:

  1. 需要在 A.h 里说明 A 遵守协议 B
  2. 需要在 A.m 里实现协议 B 的所有必须实现的方法(也有可选实现的方法,这种可以不实现)
@implementation A <B>

-(NSString*) encode {
    return @"encoded result";
}

@end

几何

  • CGFloat 浮点数
  • CGSize 宽高
  • CGPoint 二维平面点
  • CGRect 二维矩形区域表示

快速创建的方法:

  • CGSizeMake
  • CGPointMake
  • CGRectMake
CGFloat w = 100;
CGFloat h = 200;
CGSize size = CGSizeMake(w, h);

CGFloat x = 10;
CGFloat y = 20;
CGPoint point = CGPointMake(x, y);

CGRect rect = CGRectMake(point.x, point.y, size.width, size.height);
NSLog(@"%f, %f, %f, %f", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
// 10 20 100 200

头文件

C 语言用 include,oc 两种都可以,主要用 import

尖括号表示系统头文件,引号表示是项目内的头文件

#import <Foundation/Foundation.h>
#import "WRAIChatViewController.h"

KVC

该特性不是 Objective-C 的特性,其实是 Cocoa 提供的特性。

// Shape.h
@interface Shape : NSObject <RYCopying, RYSerializable>

@property float age;

@end

// test.m
Shape *sh = [[Shape alloc] init];
[sh setAge:23.2];
float aggg = [[sh valueForKey:@"age"] floatValue];
NSLog(@"valueForKey: %f", aggg); // 23.20001
NSLog(@"valueForKey at: %@", [sh valueForKey:@"age"]); // 23.2
[sh setValue:@54.3 forKey:@"age"];
NSLog(@"valueForKey at: %@", [sh valueForKey:@"age"]); // 54.3

优点:

  • 不需要 getter/setter 方法也能访问和设置属性
  • valueForKey 拿出来的值会自动装箱
  • setValue:forKey 需要自己用 @ 包装

缺点:不适合用键路径方式来取深嵌套的属性值,因为键路径用字符串来表达,编译器无法检查它是否正确

block

定义

C 语言函数指针写法

int (*addFunc)(int num1, int num2);

int add(int a, int b) {
    return a + b;
}

addFunc = &add;

JS 写法

const add = (a, b) => a + b;

OC 写法

int (^addFunc)(int num1, int num2) = ^(int a, int b){
    return num1 + num2;
};

int ress = addFunc(1,9);
NSLog(@"ress: %d", ress);

// 没返回值的写法
void (^printFunc)(void) = ^{
    NSLog(@" I am printFunc result");
};

总而言之,OC 的语法:

return_type (^block_name)(args) = ^(args) {
    logic
}

IIFC

其实也可以像 JS 一样搞一个 IIFC,声明完马上执行

JS 这样写

const result = (() => {
    const a = "prefix";
    const b = "suffix";
    return a + b;
})(); // prefixsuffix

OC 这样写

NSString* result = (^{
    NSString *a = @"prefix";
    NSString *b = @"suffix";
    NSArray *components = @[a, b];
    return [components componentsJoinedByString:@""];
})();
NSLog(@"result = %d", result); // prefixsuffix

再加个参数,JS

const result = ((a, b) => a + b)("prefix", "suffix");

OC

NSString* result = (^(NSString *a, NSString *b) {
    NSArray *components = @[a, b];
    return [components componentsJoinedByString:@""];
})(@"prefix", @"suffix"); // prefixsuffix

变量捕获

OC block 自动捕获这些类型的变量:

  • 全局静态变量
  • 本地变量:和 block 声明处在同一作用域内的变量
  • 参数变量

捕获本地变量的时候,我猜是拷贝方式的捕获,所以在 block 中无法对引用的外部本地变量赋值

需要赋值的话,引用的外部变量需要手动在声明前加一个 __block

参考资料备忘

官方文档