me
me copied to clipboard
学习 MacOS 开发 (Part 7: Swift, ObjC和C)
前导
- 从obs来看,如果是跨平台则应该考虑Qt和C/C++,native部分辅以objc
- 但如果只需要考虑macOS和iOS体系,应该考虑SwiftUI/Swift, native部分通过obj/c实现
- linux的服务端程序应该考虑nodejs/javascript和C++两个体系,在需要的情况下混合nodejs和C++
对于有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})
注:
- #import有点类似nodejs的import,编译器会帮你维护全局加载一起,在header中不需要#ifndef
我理解ObjC有3个层:
- 标准C (基础语法完全符合C)
- 面向对象的新语法
- Cocoa Foundation Framework
ARC
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
@autoreleasepool {
NSDate *time = [NSDate date];
NSLog(@"%@", time);
time = nil;
}
return 0;
}
- 仅针对objc的object, 自动跟踪pointer的引用计数,当没有引用时会自动释放内存。
Foundation
- NSString
取代标准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_$
注:
- OC没有namespace, 所以建议采用2-3个大写字母来避免冲突,这也是为什么苹果自己也是CF, NS, CI等等的原因。
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)?
- 对于变量的声明,必须使用extern,且不能赋值
- 对于函数的声明,不能有函数体。
你可以将一个.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类型:
- D: The symbol is in the initialized data section. 指代变量
- T: The symbol is in the text(code) section. 方法都是T
- U: The symbol is undefined. 未定义或在外部定义的符号. 这里指_printf
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;
}