6

开始学习 Objective-C

 3 years ago
source link: http://blog.danthought.com/programming/2020/11/29/get-started-with-objective-c/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

随着 Swift 日益成熟,Objective-C 地位在日益下滑,但是并不会完全消失,一是很多已有的大应用都有很多 Objective-C 存量代码,如果要使用 C/C++ 的一些跨平台的库,使用 Objective-C 编写 Objective-C++ 做桥接是唯一的选择。多说一点 Swift 可以直接使用 C 的库。本文并不是一篇完整的教程,更像一篇快速笔记,既会回顾 C 语言,也会讲解 Objective-C 中的扩展部分:声明 declar 和定义 define,基本数据类型、对象封装的基本数据类型、集合类、面向对象和其他杂项。

Objective C

回顾 C 语言

Objective-C 是对 C 语言的扩展,主要增加了面向对象的功能,这里罗列一下 C 语言的内容,要详细学习 C 语言,推荐 C 语言程序设计:现代方法

  • 语法
    • 声明和定义
    • 算术运算符、赋值运算符、位运算符
    • 选择语句、循环语句
    • 基本类型:整数、浮点数、字符,类型转换
    • 动态存储分配
  • 程序的组成
    • 头文件和源文件
    • 编译和链接

声明 declar 和定义 define

声明说明符 声明符;

声明为编译器提供有关于标识符含义的信息,声明说明符分为 3 大类:

  1. 存储类型。
  2. 类型限定符。
  3. 类型说明符,比如 char, short, int, long 等。

存储类型可以用于变量以及较小范围的函数和形式参数的说明,C 和 Objective-C 程序中的每一个变量都具有以下 3 个性质:

  1. 存储期限 Storage Duration:变量的存储期限决定了为变量预留和内存被释放的时间。
  2. 作用域 Scope:变量的作用域是指可以引用变量的那部分程序文本。
  3. 链接 Linkage:变量的链接确定了程序的不同部分可以共享此变量的范围。

auto 存储类型

auto 存储类型的变量具有自动存储期限,块作用域,并且无链接。

int i; // 静态存储期限,文件作用域,外部链接

void f(void) {
  int j; // 自动存储期限,块作用域,无链接
}

static 存储类型

static 存储类型用在块外部时,说明变量具有内部链接,本质上使变量只在声明它的文件内可见;当用在块内部时,static 把变量的存储期限从自动的变成了静态的。

static int i; // 静态存储期限,文件作用域,内部链接

void f(void) {
  static int j; // 静态存储期限,块作用域,无链接
}

extern 存储类型

extern 存储类型使几个源文件可以共享同一个变量,下面的语句不会导致编译器为变量 i 分配存储单元,它只是提示编译器需要访问定义在别处的变量,可能稍后在同一个文件中,更常见的是在另一个文件中。

extern int i; // 静态存储期限,文件作用域,什么链接?

register 存储类型

register 存储类型就是要求编译器把变量存储在寄存器中。

类型限定符

const

const 用来声明一些变量是只读的

通过 staticconst 定义在此文件内部使用的常量:

static NSString * const PWHeaderCRLF = @"\r\n";

通过 externconst 定义在此文件外部可使用的常量:

// .h
extern NSString * const HVBaseURL;

// .m
NSString * const HVBaseURL = @"https://example.com/api/";

基本数据类型

C 定义的基本数据类型

int someInteger = 42;
float someFloatingPointNumber = 3.1415;
double someDoublePrecisionFloatingPointNumber = 6.02214199e23;
char ch = 'a';

Objective-C 定义的基本数据类型

注意 NSInteger、NSUInteger 和 CGFloat 在不同 CPU 架构上精度会有所不同。

BOOL isGood = YES;
NSInteger negative = -123;
NSUInteger positive = 123;
CGFloat width = 100.0;

对象封装的基本数据类型

可以使用 C 语言中用 指针指向字符数组 的方法来使用字符串:

char *name = "John Smith";
printf("My name is %s", name);

Objective-C 中用 NSString 封装了字符串类型为对象的方式:

NSString *firstString = [[NSString alloc] initWithCString:"Hello World!"
                                                 encoding:NSUTF8StringEncoding];
NSString *secondString = [NSString stringWithCString:"Hello World!"
                                            encoding:NSUTF8StringEncoding];
NSString *thirdString = @"Hello World!";

NSNumber

NSNumber *magicNumber = @42;
NSNumber *unsignedNumber = @42u;
NSNumber *longNumber = @42l;

NSNumber *boolNumber = @YES;

NSNumber *simpleFloat = @3.14f;
NSNumber *betterDouble = @3.1415926535;

NSNumber *someChar = @'T';
int scalarMagic = [magicNumber intValue];
unsigned int scalarUnsigned = [unsignedNumber unsignedIntValue];
long scalarLong = [longNumber longValue];

BOOL scalarBool = [boolNumber boolValue];

float scalarSimpleFloat = [simpleFloat floatValue];
double scalarBetterDouble = [betterDouble doubleValue];

char scalarChar = [someChar charValue];

NSNumber 是 NSValue 的子类,NSValue 可以封装 C 基本数据类型,还可以封装指针和结构体。

NSString *mainString = @"This is a long string";
NSRange substringRange = [mainString rangeOfString:@"long"];
NSValue *rangeValue = [NSValue valueWithRange:substringRange];
typedef struct {
    int i;
    float f;
} MyIntegerFloatStruct;

struct MyIntegerFloatStruct aStruct;
aStruct.i = 42;
aStruct.f = 3.14;

NSValue *structValue = [NSValue value:&aStruct
                         withObjCType:@encode(MyIntegerFloatStruct)];

可以使用 C 语言的数组:

int numbers[3];

numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;

for (int i = 0; i < 3; i++) {
  printf("%d\n", numbers[i]);
}

Objective-C 中使用 NSArray 实现数组,NSArray 中只能装对象,所以对于基本数字类型要用 NSNumber 封装为对象,所以要采用下面的形式:

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

for (NSNumber *number in numbers) {
  NSLog(@"%d", number.intValue);
}

id firstObject = @"someString";
id secondObject = nil;
id thirdObject = @"anotherString";

NSArray *someArray = @[firstObject, secondObject, thirdObject];

NSSet

NSSet *simpleSet = [NSSet setWithObjects:@"Hello, World!", @42, aValue, anObject, nil];

NSDictionary

NSDictionary *dictionary = @{
    @"anObject" : someObject,
    @"helloString" : @"Hello, World!",
    @"magicNumber" : @42,
    @"aValue" : someValue
};

上面的 NSArray、NSSet 和 NSDictionary 都是创建的时候给定其内容,之后就不能再修改了,其可变类是 NSMutableArray、NSMutableSet 和 NSMutableDictionary。

NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setObject:@"another string" forKey:@"secondString"];
[dictionary removeObjectForKey:@"anObject"];

定义了一个 DTShape 类,有一个属性是 numberOfSides,还有一个实例方法 simpleDescription

开头为 + 是类方法

开头为 - 是实例方法

#import <Foundation/Foundation.h>

@interface DTShape : NSObject

@property (nonatomic) int numberOfSides;

- (NSString *)simpleDescription;

@end
#import "DTShape.h"

@implementation DTShape

- (NSString *)simpleDescription {
  return [NSString stringWithFormat:@"A shape with %d sides", self.numberOfSides];
}

@end

使用 new 创建一个对象,设置属性 numberOfSides4,再调用 simpleDescription 方法:

DTShape *shape = [DTShape new];
shape.numberOfSides = 4;
NSString *description = [shape simpleDescription];

属性是对实例变量访问的抽象,类似于 Java 中 getter 和 setter,如下的代码会自动添加两个实例变量 _threshold 和 _quantizationLevels 来作为属性的实现:

@interface VideoToonFilter : VideoFilter

@property (nonatomic, assign) float threshold;
@property (nonatomic, assign) float quantizationLevels;

- (instancetype)init;

@end

@dynamic

@dynamic 关键字告诉编译器不要自动添加实例变量作为属性的实现,实现会在运行时找到。

默认情况下,属性的实现都会加锁来保证原子性,也就自带 atomic,声明成 nonatomic 就不保证原子性。

默认情况下,属性的实现都是可读可写的,也就是自带 readwrite,声明成 readonly 就是只读的。

  • assign:只是赋值。
  • strong:retain 新值,release 旧值。
  • weak:像 assign 只是赋值,但是指向的对象被销毁时,会赋值为 nil。
  • unsafe_unretained:像 assign 只是赋值,但是指向的对象被销毁时,不会赋值为 nil。
  • copy:拷贝一次。
  • getter=<name> 指定 getter 的名称。
  • setter=<name> 指定 setter 的名称。

通过继承 DTShape 来声明了类 DTNamedShape,其中新增了属性 name,新增了初始化方法 initWithName,覆盖了实例方法 simpleDescription

#import "DTShape.h"

@interface DTNamedShape : DTShape

@property (nonatomic, copy) NSString *name;

- (instancetype)initWithName:(NSString *)name;

@end
#import "DTNamedShape.h"

@implementation DTNamedShape

- (instancetype)initWithName:(NSString *)name {
  self = [super init];
  if (self) {
    self.name = name;
  }
  return self;
}

- (NSString *)simpleDescription {
  return [NSString stringWithFormat:@"A %@ with %d sides", self.name, self.numberOfSides];
}

@end

id 数据类型可存储任何类型的对象,它是一般对象类型。

id s = [DTShape new];

@class

@class 指令提高了效率,因为编译器不需要引入和处理整个 DTShape.h 文件,只需要知道 DTShape 是一个类名:

@class DTShape;

typedef

使用 typedef 定义一个新类型名,可按照下面的步骤:

  1. 像声明所需类型的变量那样写一条语句;
  2. 在通常应该出现声明的变量名的地方,将其替换为新的类型名;
  3. 在语句的前面加上关键字 typedef
typedef NSNumber *NumberObject;

NumberObject myValue1, myValue2, myResult;

Objective-C 中通过 NS_ENUM 定义枚举,不过只能是整数类型:

typedef NS_ENUM(NSInteger, HVRegisterType) {
  HVRegisterTypeRegister = 0,
  HVRegisterTypeForgetPassword = 1,
  HVRegisterTypeResetPassword = 2
};

C 语言中定义枚举:

typedef enum {CLUBS, DIAMONDS, HEARTS, SPADES} Suit;
Suit s1, s2;

Objective-C 声明和使用的联合就是 C 的语法:

union {
  int i;
  double d;
} u;
struct {
  int i;
  double d;
} s;

内存中的分布:

C Union Struct

Objective-C 声明和使用的结构体就是 C 的语法:

struct MyPoint {
  int x;
  int y;
};
typedef struct MyPoint MyPoint;

MyPoint p = { .x = 10, .y = 20 };

printf("(%d, %d)\n", p.x, p.y);

块是 Objective-C 对 C 语言的一种扩展,块定义在函数或者方法内部,并能够访问在函数或者方法范围内块之外的任何变量,一般不能够改变这些变量的值,只有添加 __block 在变量前面才可以:

NSString* (^calculateTriangularNumber) (int) = ^NSString* (int n) {
  int i, triangularNumber = 0;
  
  for (i = 1; i <= n; i++) {
    triangularNumber += i;
  }
  
  return [NSString stringWithFormat:@"Triangular number %i is %i", n, triangularNumber];
};

NSString *result = calculateTriangularNumber(5);

通过 typedef 定义类型:

@interface DTAlertView : UIView

typedef void (^alertViewCompletion) (BOOL finished);

- (void)showInView:(UIView *)view completion:(alertViewCompletion)completion;

@end

Objective-C 声明和使用的函数就是 C 的语法:

int foo(int bar) {
  return bar * 2;
}

foo(1);

Objective-C 声明和使用的指针就是 C 的语法,指针就是一个整数变量,里面装着指向某个值的内存地址,& 是地址运算符,* 是间接寻址运算符:

int count = 10;

int *intPtr = &count;

int x = *intPtr;

动态存储分配

C 语言中的动态存储分配:

  • malloc 函数 —— 分配内存块,但是不对内存块进行初始化。
  • calloc 函数 —— 分配内存块,并且对内存块进行清零。
  • realloc 函数 —— 调整先前分配的内存块大小。

预处理程序语句使用 # 标记,这个符号必须是一行中的第一个非空字符,顾名思义,预处理程序实际上是在分析 Objective-C 或者 C 程序之前处理这些语句。

使用 #define 就是给符号名称指定程序常量:

#define PI 3.141592654
#define TWO_PI 2 * 3.141592654

#ifdef#ifndef#endif 是条件编译:

#ifdef __OBJC__
  #import <Foundation/Foundation.h>
  #import <UIKit/UIKit.h>
#endif

#ifndef __OBJC__
  #if USE_IL2CPP_PCH
    #include "il2cpp_precompiled_header.h"
  #endif
#endif

在 Xcode -> Target -> Build Settings 中设置:

Objective C Preprocessor Macros

Objective-C 中使用 #import 导入头文件,"" 在包含源文件的目录中查找,<> 在系统头文件目录中寻找包含文件:

#import "PWAPIController.h"
#import <AFNetworking/AFNetworking.h>

C 中使用 #include 导入头文件,"" 在包含源文件的目录中查找,<> 在系统头文件目录中寻找包含文件,通常把系统头文件保存在目录 /usr/include 中:

#include "decode_audio.h"
#include <stdio.h>

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK