{"id":20948469,"url":"https://github.com/ajiew/headfirstobjectivec","last_synced_at":"2025-10-10T23:39:12.623Z","repository":{"id":107895079,"uuid":"566110531","full_name":"aJIEw/HeadFirstObjectiveC","owner":"aJIEw","description":"一个安卓开发者的 Objective-C 学习笔记。","archived":false,"fork":false,"pushed_at":"2022-12-01T06:02:53.000Z","size":33,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-13T04:44:01.771Z","etag":null,"topics":["headfirst","ios","objective-c"],"latest_commit_sha":null,"homepage":"","language":"Objective-C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aJIEw.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-15T01:32:43.000Z","updated_at":"2023-03-04T09:07:03.000Z","dependencies_parsed_at":null,"dependency_job_id":"cdd43663-154b-4cf6-960f-6dbad39fcb1c","html_url":"https://github.com/aJIEw/HeadFirstObjectiveC","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/aJIEw/HeadFirstObjectiveC","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aJIEw%2FHeadFirstObjectiveC","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aJIEw%2FHeadFirstObjectiveC/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aJIEw%2FHeadFirstObjectiveC/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aJIEw%2FHeadFirstObjectiveC/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aJIEw","download_url":"https://codeload.github.com/aJIEw/HeadFirstObjectiveC/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aJIEw%2FHeadFirstObjectiveC/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279005577,"owners_count":26083920,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["headfirst","ios","objective-c"],"created_at":"2024-11-19T00:19:25.145Z","updated_at":"2025-10-10T23:39:12.573Z","avatar_url":"https://github.com/aJIEw.png","language":"Objective-C","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003e 一个安卓开发者的 Objective-C 学习笔记。[^1]\n\n## HeadFirstObjectiveC\n\nObjective-C 主要用于开发 macOS/iOS/iPadOS 等平台上的应用，看它的名字就可以猜到，这是一门基于 C 的面向对象的编程语言。\n\n### 程序入口\n\n与大多数编程语言类似，OC 的程序运行入口也是 main 方法：\n\n```objective-c\n// 导入依赖，因为使用了 NSLog\n#import \u003cFoundation/Foundation.h\u003e\n\n// main 方法的返回值通常是 int\n// main 方法参数分别是参数个数和参数数组，如果不需要也可以不写\n// int main() {\nint main(int argc, const char * argv[]) {\n  // 为了和 C 的字符串区分，声明 NSString 时，需要在字符串前加 @\n  // 另外，在创建一些字面量 literals 时，也需要使用 @\n  NSLog(@\"Hello World!\");\n}\n```\n\n我们可以使用 [clang](https://opensource.apple.com/source/clang/clang-23/clang/tools/clang/docs/UsersManual.html) 或 [gcc](https://opensource.apple.com/source/clang/clang-23/clang/tools/clang/docs/UsersManual.html) 编译 OC 源文件：\n\n```shell\nclang main.m file1.m file2.m -w -framework Foundation -o main\n```\n\n运行编译后生成的二进制文件（-t 表示命令行参数）：\n\n```sh\n./main -t arg1 arg2\n```\n\n### 数据类型\n\n#### Primitive types\n\nObjective-C 是一门基于 C 的语言，所以为了兼容 C，它做了很多改进。我们可以像 C 中一样声明原始数据类型，也可以声明 Objective-C 中独有的支持作为对象使用的数据类型。\n\n```objective-c\nint primitiveInt  = 1;\nlong primitiveLong = 1;\nfloat primitiveFloat = 1.0f;\ndouble primitiveDouble = 1.0;\nchar charA = 'A';\n```\n\nObjective 中布尔类型字面量为数字 0 和 1：\n\n```objective-c\nBOOL noBool  = NO;\nBOOL yesBool = YES;\nNSLog(@\"%d %d\", noBool, yesBool);\n```\n\n另外，与 C 中一样，所有的整形数字分为  `signed` 与 `unsigned` 类型，`unsigned` 类型数据只能代表非负数。以 `unsigned int` 为例，同样 16 位的情况下，它的取值范围是 0 ~ 65535，而普通 `int` 的取值范围则是 -32767 ~ 32767。\n\n#### Objects\n\n除了基本数据类型之外，其它所有的数据类型都是作为对象被创建的。\n\n```objective-c\nMyClass *myObject1 = nil;  // Strong typing\nid       myObject2 = nil;  // Weak typing\n```\n\n上面的例子中，`MyClass` 是我们定义的一个类，创建它时在对象名称前加 `*` 表示这是一个强类型对象。我们也可以使用 `id` 声明一个对象，它表示这是一个弱类型的对象，它可以代表任何类的对象。\n\n```objective-c\nid var = nil;\nif (var) {\n  NSLog(@\"This is %p\", var);\n} else {\n  NSLog(@\"var is empty\");\n}\n```\n\n上面的例子中，我们声明一个 `var` 变量，并且初始化值为 `nil`，表示空值，此时使用 `if` 判断将会为 `false`，我们可以尝试将它修改成 `1` 或者 `YES` 再运行看看。\n\n##### NSString\n\nObjective-C 中的字符串用 `NSString` 表示，由于它是对象，所以创建时也需要使用指针。\n\n```objective-c\nNSString *str = @\"Objective-C\";\nNSLog(str);\n\n// 可变字符串\nNSMutableString *mutableString = [NSMutableString stringWithString:@\"Hello\"];\n[mutableString appendString:@\" World!\"];\nNSLog(mutableString);\n```\n\n##### NSNumber\n\n我们可以使用 `NSNumber` 创建数字类型的对象，与创建字符串类似，同样需要使用 `@` 符号。\n\n```objective-c\n// 创建一个长整型数字类型的对象\nNSNumber *fortyTwoLongNumber = @42L;\n// 转换成 long\nlong fortyTwoLong = [fortyTwoLongNumber longValue];\n// 打印\nNSLog(@\"%li\", fortyTwoLong);\n```\n\n##### NSArray\n\n数组可以包含不同类型的数据，但是必须是对象。\n\n```objective-c\nNSArray *anArray = @[@1, @2L, @3.0F, @4U, @\"5\"];\n\nNSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:2];\n[mutableArray addObject:@\"Hello\"];\n[mutableArray addObject:@\"World\"];\n[mutableArray removeObjectAtIndex:0];\nNSLog(@\"%@\", [mutableArray objectAtIndex:0]);\n```\n\n##### NSDictionary\n\n字典，类似于其它编程语言中的 `Map` 的数据类型。\n\n```objective-c\nNSDictionary\u003cNSString *, NSObject *\u003e *dictionary = \n    @{ @\"name\" : @\"Objective-C\", @\"birth\" : @1992 };\nNSObject *dName = dictionary[@\"name\"];\nNSLog(@\"dictionary = %@\", [dictionary description]);\n```\n\n##### NSSet\n\n集合对象。\n\n```objective-c\n// 创建一个可变集合对象\nNSMutableSet\u003cNSString *\u003e *set = [NSMutableSet setWithObjects:@\"Hi\", nil];\n[set addObject:@\"there\"];\n// 空值不会被加入\nNSLog(@\"set = %@\", set);\n```\n\n### 类\n\nObjective-C 虽然也是面向对象的语言，但是如果你像我一样之前没有接触过 C，那么第一点感到不适应的地方可能会是：为什么所有的类都要分别创建一个 `.h` 和一个 `.m` 文件呢？为什么要在 `.h` 文件中定义所有的属性和方法，然后再在 `.m` 文件中去实现呢？\n\n其实这主要是因为过去的编程范式遗留下来的习惯，为了最大程度地复用代码，同时也保证了代码的清晰度，而且编译器也能根据 `.h` 文件来决定哪些属性和方法会被编译到最终生成的可执行文件中。[^2]\n\n如下所示，我们创建了一个 `person.h` 和 `person.m` 文件，其中定义和实现了 Person 类：\n\n```objective-c\n// 所有类都继承自 NSObject\n@interface Person : NSObject\n  // 属性\n  @property (assign) NSString *name;\n  // 方法，+ 表示类方法（类似静态方法），- 表示实例方法\n  - (void)sayHi:(NSString *)greeting;\n@end\n```\n\n定义完 `.h` 文件之后再在 `person.m` 中实现：\n\n```objective-c\n// 首先需要导入头文件\n#import \"person.h\"\n\n@implementation Person {\n  // 实例变量\n  NSString *_nickName;\n  // 私有实例变量\n  @private int _age;\n}\n\n- (void)sayHi:(NSString *)greeting {\n  // 方法体\n}\n\n@end\n```\n\n#### Properties\n\n类的属性，也就是可以被外界直接访问类中的变量，和实例变量最大的区别是，实例变量只能在当前类中的构造器或实例方法中才能被访问。\n\n```objective-c\n// 编译器会为属性生成一个 setter 方法，这里的话就是 setName 方法\n@property NSString *name;\n\n// 我们也可以自定义 getter 和 setter\n@property (getter = ageGet, setter = ageSet: ) int age;\n```\n\n##### Attributes\n\n我们还可以为类的属性设置其它修饰符 (*attribute*)，上面例子中的自定义 setter 和 getter 其实也是属性的修饰符，其它这样的修饰符还有：\n\n- `readonly`: 表示只读，不会生成 setter 方法，默认为 `readwrite`。\n- `copy`: 复制属性的值，也就是使用强引用，即使引用值发生了修改也不会影响属性本身的值。\n- `nonatomic`: 非原子性（线程不安全）。默认所有的属性都是原子性的，也就是支持跨进程赋值。\n- `unsafe_unretained`: 等同于 `weak`，也就是弱引用，当没有强引用的时候即释放它。主要用于防止多个对象之间的循环引用。\n\n除了 `readonly` 之外，默认的 attribute 还有：\n\n- `assign`: 赋值，告诉编译期生成 setter 方法。\n- `retain`: 等同于 `strong`，也就是强引用，保持引用直到所有对象都释放了它，旧值被释放新值被赋值。\n- `atomic`: 保持原子性，只有单个线程能访问它，线程安全但是效率更低。\n\n##### `synthesize`\n\n属性背后其实也依赖于实例属性，它会自动生成为我们生产一个 `_perpertyName` 的幕后属性 (*backing field*)，假如我们想要在实现类中使用不一样的名字，可以使用 `@synthesize` 自定义：\n\n```objective-c\n@implementation Person\n\n@synthesize name = instanceVariableName;\n\n@end\n```\n\n定义完属性之后，在实现类中有下面几种方式去访问它：\n\n```objective-c\n- (void)changeName {\n  NSString *var = @\"Objective-C\";\n  // 通过幕后属性访问\n  // _name = var;\n  \n  // 使用 @synthesize 替代幕后属性\n  // instanceVariableName = var;\n  \n  // 通过 setter\n  [self setName:var];\n  \n  // 还可以使用 self. 语法\n  self.name = var;\n}\n```\n\n##### 实例变量\n\n我们可以把实例变量看作其他语言中类的私有属性，而且通常我们只会在实现类中定义实例变量。\n\n```objective-c\n@implementation MyClass {\n  NSString *_nickName;\n}\n```\n\n之后，我们可以在构造器方法中去初始化它。\n\n#### Methods\n\n前面提到过，Objective-C 中的方法有两种，一种是类方法，一种是实例方法，分别使用方法前面的 `+` 和 `-` 表示。除此之外，实现类中的方法通常由这几个部分组成：方法类型+返回值+方法名+可选的方法参数+方法体。\n\n```objective-c\n- (void)myMethod:(int)arg1, name:(NSString *)arg2 {\n  // do something\n}\n```\n\n可以看到，和其它编程语言不同的地方在于，方法的参数名和参数值是分开表示的，而且参数名也是方法的一部分。因此，整个方法的方法名是：\n\n```objective-c\nmyMethod:name:\n```\n\n当我们调用一个方法的时候，需要用中括号将对象引用和方法括起来，如果方法有多个参数，还需要将参数名称一一列出，中间使用空格分割，并在冒号后表示实际参数：\n\n```objective-c\n[myClass myMethod:42 name:@\"arg2\"];\n```\n\n需要注意的是，Objective-C 中还有一个 [Message](https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Message.html#//apple_ref/doc/uid/TP40008195-CH59-SW1) 的概念，当我们用以上的形式调用一个方法时，本质上是发送了一个消息给对象实例，编译器会将所有的方法都编译成下面这个形式：\n\n```objective-c\nid objc_msgSend(id self, SEL op, ...);\n```\n\n上面的 [`objc_msgSend`](https://developer.apple.com/documentation/objectivec/1456712-objc_msgsend) 是用于发送消息的方法。也就是说，当我们调用 `myMethod` 的时候，本质上是调用的是：\n\n```objective-c\nobjc_msgSend(myClass, @selector(myMethod:name:), 42, @\"arg2\");\n```\n\n关于 [selector](#selectors) 的用法下面会再具体介绍。\n\n##### Constructors\n\n构造器是一类特殊的方法，返回值通常是 `id`，我们可以在其中做一些类的初始化工作。\n\n默认的构造器是 `init`：\n\n```objective-c\n- (id)init {\n  self = [super init];\n  // 判断父类是否初始化成功，如果初始化失败将会返回 nil\n  if (self) {\n    _nickName = @\"default\";\n  }\n  return self;\n}\n```\n\n自定义构造器：\n\n```objective-C\n- (id)initWithNickname:(NSString *)nickName {\n  self = [super init];\n  if (self) {\n    _nickName = nickName;\n  }\n  return self;\n}\n```\n\n##### Selectors\n\nSelector 是在对象上执行的方法的名称，编译器会将所有的方法都编译成 selector 来执行。当我们使用 selector 的时候，通常是为了实现动态分配执行方法。\n\nSelector 用 [`SEL`](https://developer.apple.com/documentation/objectivec/sel/) 表示，一般有两种方式创建：\n\n```objective-c\nSEL sel = @selector(methodName);\n```\n\n当我们不知道方法名时，可以通过运行时方法 `NSSelectorFromString` 创建 selector：\n\n```objective-c\nSEL sel = NSSelectorFromString(dynamicMethodsString);\n```\n\n在使用 selector 之前，通常需要先进行判断，确保方法可以执行：\n\n```objective-c\nif ([myClass respondsToSelector:sel]) {\n  // 调用方法\n  [myClass performSelector:selectorVar];\n}\n```\n\n除了以上这种调用方法之外，我们还可以使用 [block](#blocks) 动态调用方法：\n\n```objective-c\nif ([myClass respondsToSelector:sel]) {\n  // 获取实现的方法\n  IMP imp = [myClass methodForSelector:sel];\n  // 将 IMP 对象转换为 block 对象\n  void (*func)(id, SEL, NSString*) = (void *)imp;\n  // 调用方法\n  func(myClass, sel, @\"newArg\");\n}\n```\n\n上面的例子中，我们使用了 [`methodForSelector`](https://developer.apple.com/documentation/objectivec/nsobject/1418863-methodforselector/) 定位到方法，然后将其转换为 block 并执行。不过，由于 [`performSelector`](https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418867-performselector) 方法的限制，方法的参数最多只能有两个。\n\n#### Lifecycles\n\nObjective-C 中的类也有生命周期，而且还需要我们去管理内存的释放，这点会在[内存管理](#内存管理)部分详细介绍。\n\n```objective-c\n// 任何类在实例化之前都会先调用该方法\n+ (void)initialize {  \n}\n\n// 对应于 initialize 方法，用于清空对象，当引用计数为 0 时被调用\n- (void)dealloc {\n  // 如果未启用 ARC，则需要手动释放引用计数\n  [nickName release];\n  // 调用父类方法\n  [super dealloc];\n}\n```\n\n#### Extensions\n\nExtensions 用于扩展类的属性和方法。\n\n```objective-c\n// Extension 通常和实现类放在一起\n@interface Person () // 声明扩展类的语法是：在类名后 + ()\n\n// 添加一个扩展属性\n@property NSString *firstName;\n  \n// 添加一个扩展方法\n+ (instancetype)createWithName:(NSString *)name;\n\n@end\n```\n\n#### Generic\n\n从 Xcode 7 开始支持声明泛型类，使用关键字 `__covariant`：\n\n```objective-c\n@interface Result\u003c__covariant A\u003e : NSObject\n\n// 使用 block 作为方法参数\n- (void)handleSuccess:(void (^)(A))success\n              failure:(void (^)(NSError *))failure;\n\n@end\n```\n\n由于编译期不支持在实现类中使用泛型，所以实现类中需要使用 `id`：\n\n```objective-c\n@implementation Result\n\n- (void)handleSuccess:(void (^)(id))success\n              failure:(void (^)(NSError *))failure {\n  // 假设调用成功，返回值是 42\n  success(@42);\n}\n```\n\n使用：\n\n```objective-c\nResult\u003cNSNumber *\u003e *r = [[Result alloc] init];\n  [r handleSuccess:^void (NSNumber * result) { NSLog(@\"result: %i\", [result intValue]); } \n           failure:^void (NSError *) { NSLog(@\"error\"); } ];\n```\n\n当我们将返回值修改为字符串时：\n\n```objective-c\nsuccess(@\"ok\");\n```\n\n对应的处理结果是：\n\n```objective-c\nResult\u003cNSString *\u003e *r = [[Result alloc] init];\n  [r handleSuccess:^void (NSString * result) { NSLog(@\"result: %@\", result); } \n           failure:^void (NSError *) { NSLog(@\"error\"); } ];\n```\n\n### Others\n\n#### Protocols\n\n协议类似于接口 (*Interface*) 的概念，我们可以在 `@protocol` 中定义方法和属性，然后交由其它类去实现。\n\n```objective-c\n@protocol Worker \u003cNSObject\u003e\n\n  @property BOOL retired;\n\n  - (void)performWork;\n@end\n```\n\n通常，我们在声明类时将协议作为泛型类型使用：\n\n```objective-c\n@interface Person : NSObject\u003cWorker\u003e\n```\n\n在实现类中实现协议中的方法：\n\n```objective-c\n// 下面这行注释会在文件和导航栏中添加一条分割线\n#pragma mark -\n// pragma mark 是一种特殊的管理代码的注释，方便我们在 XCode 导航栏中跳转到页面的各个区域\n#pragma mark Career protocol\n// 如果需要在实现类中使用 protocol 中的属性，必须使用 synthesize 暴露出该属性\n@synthesize retired = _retired;\n\n// 实现 protocol 中的方法\n- (void)performWork {\n  NSLog(@\"do some work.\");\n}\n```\n\n#### Categories\n\nCategories 同样用于扩展一个类，它和 extensions 的最大的区别是，extensions 中扩展的方法一般只能用于某一个特定的实现类，但是 categories 为某个类添加的扩展方法可以用于所有的类，包括子类。\n\n定义 category 和定义类的方式相同，需要先声明头文件，然后再在实现类中实现方法。通常情况下，文件名是实现的基类名+category 名，比如下面的这个例子中，为 `Person` 类添加了阅读相关的方法，那么，文件名就是 `person+read.h`。\n\n```objective-c\n#import \"person.h\"\n\n// 类后括号内就是这个 category 的名字\n@interface Person (Read)\n\n// 添加的方法\n- (void)read:(NSString *)material;\n\n@end\n```\n\n实现类：\n\n```objective-c\n#import \"person+read.h\"\n\n@implementation Person (Read)\n\n- (void)read:(NSString *)material {\n  NSLog(@\"I'm reading %@\", material);\n}\n\n@end\n```\n\n当需要调用 category 中的方法时，只需要导入 category 即可：\n\n```objective-c\n#import \"person+read.h\"\n#import \"person.h\"\n\n...\n- (void)someMethod {\n  Person *p = [[Person alloc] init];\n  [p read:@\"a book\"];\n}\n```\n\n#### Blocks\n\nBlocks 是 Objective-C 中一种特殊的对象，它可以直接执行一段代码，类似其它语言中的 lambda 表达式。\n\n##### Block 字面量\n\n我们可以使用 `^` 符创建一个 block 字面量：\n\n```objective-c\n^{\n  NSLog(@\"This is a block\");\n}\n```\n\n类似与 C 中的方法指针，我们可以使用下面的语法去引用一个 block：\n\n```objective-c\nvoid (^simpleBlock)(void);\n```\n\n以上可以理解为我们创建了一个 `simpleBlock` 变量来表示一个参数为 void，返回值也为 void 的方法。\n\n之后，我们可以创建方法体：\n\n```objective-c\nsimpleBlock = ^ {\n  NSLog(@\"This is a block\");\n}\n```\n\n当然，我们也可以将上面两个步骤合二为一：\n\n```objective-c\nvoid (^simpleBlock)(void) = ^ {\n  NSLog(@\"This is a block\");\n}\n```\n\n另外，block 也可以包含参数和返回值：\n\n```objective-c\ndouble (^addBlock)(double, double) = \n  ^(double firstValue, double secondValue) {\n    return firstValue * secondValue;\n  };\n```\n\n更多 block 的语法可以参考[这个回答](https://stackoverflow.com/questions/7936570/objective-c-pass-block-as-parameter/32225544#32225544)。\n\n#### 内存管理\n\nObjective-C 中并没有 Java 中的垃圾回收机制，我们必须自行管理内存。Objective-C 中使用引用计数的方式管理内存，当我们创建一个对象时，对应的内存也会被创建并分配给该对象，我们即拥有了 (*own*) 该对象，此时引用计数 +1。只要我们还持有该引用，该对象就会持续存在，不会被释放 (*deallocate*)。而当我们释放 (*release*) 了该对象，引用计数 -1，若引用计数减到 0 时，该对象才会从内存中被移出。\n\n```objective-c\n// 创建对象\nMyClass *classVar = [[MyClass alloc] init];\n\n// 使用对象\n[classVar doSomething];\n\n// 释放对象\n[classVar release];\n```\n\n由于手动管理内存麻烦且容易出错，从 Xcode 4.2 和 iOS 4 开始，引入了 Automatic Reference Counting 来帮助我们管理内存。\n\n```objective-c\n// 默认值，变量会被保存在内存中直到离开作用域\n__strong NSString *strongString;\n\n// 弱引用，假如引用的对象被释放，那么弱引用会被设为 nil\n__weak NSSet *weakSet;\n\n// 与弱引用类似，但是如果引用的对象被释放，引用也不会被设为 nil\n__unsafe_unretained NSArray *unsafeArray;\n```\n\n对于类的属性而言，默认会是 `strong` 的，也就是会在内存中被保存直到对象被释放；而如果属性是 `weak` 的，则当引用计数减到零后，属性接收到的值会变成 nil。\n\n```objective-c\n// 默认值\n@property (strong) MyClass *strongVar;\n// 改为弱引用\n@property (weak) MyClass *weakVar;\n```\n\n#### Q \u0026 A\n\n##### 如何理解指针？\n\n当我们使用指针时，我们其实是在引用一个对象的地址，而不是直接使用堆 (*heap*) 中创建的对象，这样，当我们传递对象并且对象被改变时，由于使用的是引用，我们能够得到改变后的对象。由于 Objecgive-C 是一门面向对象的语言，当我们创建一个对象时，大多数时候都应该使用指针。\n\n##### `id` 和 `void *` 的区别？\n\n`id` 表示一个指向 Objective-C 对象的指针，而 `void *` 可以表示为任何指针。\n\n另外，使用 `id` 声明对象时编辑器不会报错，只有在运行时才会提示错误，所以，推荐使用 `NSObject *` 而不是直接使用 `id` 创建一个代表任何类的对象。\n\n\n\n[^1]: https://learnxinyminutes.com/docs/objective-c/\n[^2]: 参考这个回答：https://stackoverflow.com/a/2620632/4837812\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fajiew%2Fheadfirstobjectivec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fajiew%2Fheadfirstobjectivec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fajiew%2Fheadfirstobjectivec/lists"}