nonocast / me

记录和分享技术的博客
http://nonocast.cn
MIT License
20 stars 0 forks source link

学习 MacOS 开发 (Part 7: Swift, ObjC和C) #244

Open nonocast opened 2 years ago

nonocast commented 2 years ago

学习 MacOS App Part 1-6

前导

对于有C/C++背景的程序员,Objective-C(简称ObjC)是可以速成的,因为说到底,ObjC只是在传统的C语言上加上一层面向对象的语义。ObjC是C语言的超集,一个C语言程序就是一个ObjC程序。

ObjC

hello.m

#import <Foundation/Foundation.h>

int main(int argc, char *argv[]) {
  NSLog(@"hello world");
}

命令行编译:

clang -framework Foundation main.m -o app

CMake编译:

find_library(Foundation Foundation)
add_executable(app main.m)
target_link_libraries(app ${Foundation})

注:

  1. import有点类似nodejs的import,编译器会帮你维护全局加载一起,在header中不需要#ifndef

我理解ObjC有3个层:

ARC

#import <Foundation/Foundation.h>

int main(int argc, char *argv[]) {
  @autoreleasepool {
    NSDate *time = [NSDate date];
    NSLog(@"%@", time);
    time = nil;
  }
  return 0;
}

Foundation

取代标准C的char*,对等std::string.

int main(int argc, char *argv[]) {
  @autoreleasepool {
    NSString *hi = @"你好, 中文";
    NSLog(@"%@", hi);
    NSLog(@"length: %lu", [hi length]);  // Output: 6

    NSDate *now = [NSDate date];
    NSString *dateString = [NSString stringWithFormat:@"The date is %@", now];
    NSLog(@"%@", dateString);
  }
  return 0;
}

一个典型的class如下:

HCPerson.h

#import <Foundation/Foundation.h>

@interface HCPerson : NSObject {
  // instance member (不推荐)
  int age;
}

// instance property
@property(nonatomic) float heightInMeters;

// class method
+ (bool)canFly;

// instance method
- (void)walk:(int)distance andDirection:(int)dir;
@end

HCPerson.m

#import "HCPerson.h"

@implementation HCPerson

- (id)init {
  self = [super init];
  if (self) {
    self->age = 10;
    self.heightInMeters = 1.7f;
  }
  return self;
}

+ (bool)canFly {
  return false;
}

- (void)walk:(int)distance andDirection:(int)dir {
  NSLog(@"walk");
}
@end

编译:

➜ clang -c HCPerson.m 
➜ 
➜ nm HCPerson.o
0000000000000070 t +[HCPerson canFly]
00000000000000c0 t -[HCPerson heightInMeters]
0000000000000000 t -[HCPerson init]
00000000000000e0 t -[HCPerson setHeightInMeters:]
0000000000000090 t -[HCPerson walk:andDirection:]
                 U _NSLog
0000000000000108 T _OBJC_CLASS_$_HCPerson
                 U _OBJC_CLASS_$_NSObject
00000000000003d8 T _OBJC_IVAR_$_HCPerson._heightInMeters
00000000000003d0 T _OBJC_IVAR_$_HCPerson.age
0000000000000130 T _OBJC_METACLASS_$_HCPerson
                 U _OBJC_METACLASS_$_NSObject
00000000000001e0 t _OBJC_SELECTOR_REFERENCES_
00000000000001e8 t _OBJC_SELECTOR_REFERENCES_.2
0000000000000258 t __OBJC_$_CLASS_METHODS_HCPerson
00000000000002c0 t __OBJC_$_INSTANCE_METHODS_HCPerson
0000000000000328 t __OBJC_$_INSTANCE_VARIABLES_HCPerson
0000000000000370 t __OBJC_$_PROP_LIST_HCPerson
0000000000000388 t __OBJC_CLASS_RO_$_HCPerson
0000000000000278 t __OBJC_METACLASS_RO_$_HCPerson
                 U ___CFConstantStringClassReference
                 U __objc_empty_cache
                 U _objc_msgSend
                 U _objc_msgSendSuper2
0000000000000158 t l_OBJC_CLASSLIST_SUP_REFS_$_
00000000000003e0 t l_OBJC_LABEL_CLASS_$

注:

RunLoop

#import <Foundation/Foundation.h>

int main(int argc, char *argv[]) {
  @autoreleasepool {
    [[NSRunLoop currentRunLoop] run];
  }
  return 0;
}

然后添加一个NSTimer

#import <Foundation/Foundation.h>

@interface HCLogger : NSObject
- (void)updateLastTime;
@end

@implementation HCLogger
- (void)updateLastTime {
  NSDate *now = [NSDate date];
  NSLog(@"%@", now);
}
@end

int main(int argc, char *argv[]) {
  @autoreleasepool {
    HCLogger *logger = [[HCLogger alloc] init];
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:logger selector:@selector(updateLastTime:) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] run];
  }
  return 0;
}

关于OC的语法强烈推荐Objective-C编程, 网上随便找个pdf看个1个小时就差不多了,非常好的书,不仅学习了OC,同时也完整复习标准C。有句说句,OC/Foundation要比C++/std更友好。

语法细节

include

为什么需要include, 为什么需要header? 根据#20的表述,每个.c会通过预编译一系列过程最终生成object file,所以include只是在编译过程中定义符号。换句话说,你不可以使用一个没有定义的变量或者方法,但是不需要实现,这个是链接过程所需要的。

比如main.c

#include <stdio.h>

void foo();

int main() {
  foo();
  return 0;
}

gcc -c main.c -o main.o是成功的,只是在后续的ld会报错 (symbol not found)

所以header的出现只是为了提取公应式而已,你完全可以在每个c file中复制定义。

foo.c

#include <stdio.h>

void foo() {
  printf("i'm foo\n");
}

main.c

#include <stdio.h>

void foo();

int main() {
  foo();
  return 0;
}

比较合理的当然是将foo的定义提取到foo.h,但道理就是这么个道理。

本质上来说,对于linux来说后缀根本不重要,但推荐c使用.h和.c,c++使用.hpp和.cpp,用来区分c和c++的header file。

另外一个话题就是需要明确区分definition和declaration,在header中只应该保留declaration,不能放definition, 否则会导致多个object file链接的时候存在多个相同definition。

什么是声明 (declaration)?

你可以将一个.c/.cpp对应的.o(object file),看成是一个module (模块),header中应该就是存放模块需要到导出的symbol,用来做自动link。

如果你在bar这个模块中定义变量x, extern int x;, 那么这个x在link的时候就是平铺唯一,其他模块如果也导出x,链接就会报错。

foo.h

#include <stdio.h>

#ifndef FOO
#define FOO

extern int bar;
void foo();

#endif

foo.c

#include "foo.h"

int bar = 1;

void foo() {
  printf("bar: %d\n", bar);
}

as

➜  gcc -c -o foo.o foo.c 
➜  file foo.o
foo.o: Mach-O 64-bit object x86_64
➜  
➜  nm foo.o
000000000000001c D _bar
0000000000000000 T _foo
                 U _printf

DTU是symbol类型:

link: gcc -o app foo.o main.o

如果我们不在foo.h中extern int bar;则在main.c中需要手动声明extern int bar

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

extern int bar;

bar = 7;

int main() {

  bar = 9;
  foo();
  return 0;
}

但这里bar = 7是编译不通过的,因为bar = 7是一个expression, 而不是definition。但是如果你写int bar = 7 则表示你在main模块中定义了一个全局变量bar,这样也冲突,所以两种都不行,只能在函数中去做expression。

static local variables

A static variable inside a function keeps its value between invocations.

#include <stdio.h>

void run() {
  static int count = 0;
  printf("count: %d\n", ++count);
}

int main() {
  run();
  run();
  run();

  return 0;
}

运行后输出:

➜  ./app
count: 1
count: 2
count: 3

和全局变量的最大区别就是控制住了count的scope限定在run方法中。

static global variables (Multi-file variable scope example)

简单来说,static的全局变量的scope仅限于此模块,不允许外部链接。

static function

仅限于此模块进行调用,不允许外部模块使用。

所以默认应该用static,需要开放到才需要去掉static。

Structs

struct Person {
  int age;
  float height;
};

int main(int argc, char *argv[]) {
  struct Person mikey;
  mikey.age = 18;
  mikey.height = 1.7f;
  return 0;
}

通过typedef 省略使用的时候需要struct。

typedef struct {
  int age;
  float height;
} Person;

int main(int argc, char *argv[]) {
  Person mikey;
  mikey.age = 18;
  mikey.height = 1.7f;
  return 0;
}

参考阅读