Objective-C 学习笔记 | 基础

参考书:《Objective-C 编程(第2版)》

第1部分 入门

Objective-C语言是以C语言为基础的,但增加了对面向对象编程的支持。Objective-C语言是用来开发在苹果iOS以及OS X操作系统上运行的应用的编程语言。

第2部分 如何编程

该部分讲解了C语言编程的必要知识,这里只记录Objective-C新增内容。

NSInteger和NSUInteger

NSUInteger是无符号的整型,NSInteger是有符号的整型。

NSInteger是一个封装,它会识别当前操作系统的位数,自动返回最大的类型。
定义的代码类似于下:

#if __LP64__ || TARGET_OS_EMBEDDED || TARGET_OS_IPHONE || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif

这样做让NSInteger和NSUInteger变得通用,不用考虑设备是32位还是64位。

如果使用printf()来输出这两种类型的变量,需要先将NSInteger转换成long,NSUInteger转换成unsigned long。

第3部分 Objective-C与Foundation

该部分开始介绍Objective-C。编写Objective-C程序时,要使用Foundation框架。框架(framework)是由很多类(Class)组成的库。这里记录一些新的知识点。

#import和#include的区别

#include指令告诉编译器做呆板的复制粘贴,将包含的内容粘贴到目标文件中来。而#import指令则让编辑器先检查之前是否导入过这个文件,或是已经被包含到目标文件中了。因此,#import指令导入更快、更有效率。

类与实例、方法与消息

Objective-C也有类和对象的概念,Objective-C的方法和消息与函数类似。如需执行方法中的代码,首先需要发送一条消息给包含这个方法的对象或类。消息发送(指令)必须写在一对方括号中,并且必须包含接收方(receiver)和选择器(selector),如下图所示:

请添加图片描述

date是一个类方法。date方法执行后,NSDate类会在堆上new一个NSDate实例:now,并初始化为当前的日期/时间,然后返回新对象(now)的地址。

请添加图片描述

有了NSDate实例:now之后,我们可以给这个对象发一个实例方法,比如:timeIntervalSince1970。通常来说,实例方法会提供实例中实例变量的信息,或是对实例的实例变量进行操作。

测试程序:

//
//  main.m
//  TimeAfterTime
//
//  Created by 刘文晨 on 2024/6/5.
//

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 向 NSDate 类发送 date 消息,让它执行 date 方法
        NSDate *now = [NSDate date];
        // 打印实例的地址
        NSLog(@"This NSDate object lives at %p", now);
        // %@ 会输出相应对象的“描述信息”
        NSLog(@"The date is %@", now);
        // 实例方法
        double seconds = [now timeIntervalSince1970];
        NSLog(@"It has benen %f seconds since the start of 1970.", seconds);
    }
    return 0;
}

注意:

  1. 类方法和类对应,实例方法和实例对应。receiver和selector不匹配就会出错。
  2. Objective-C是区分大小写的,方法名也是区分大小写的。
  3. Objective-C语言命名习惯为“驼峰式”或“前缀大写的驼峰式”。

alloc和init

消息可以以嵌套的形式连续发送,而唯一必须要以嵌套的形式发送的消息是alloc和init。

每个类都有一个alloc类方法,它创建一个新的对象并返回指向该对象的指针。通过alloc类方法创建出来的对象,必须要经过初始化才能使用。每个类也都有一个init实例方法,它用来初始化实例。

NSDate *now = [[NSDate alloc] init]; // 消息嵌套,和下面的效果一样
NSDate *now = [NSDate date]; // date 方法代码最少,称之为便利方法

nil

nil就是空指针,不指向任何对象。

在Objective-C中,可以向nil发送消息,是合法的,但得到的返回值没有意义。

注意,如果程序向某个对象发送了消息,但不符合预期,应该检查receiver是否为nil。

id

id 类型的含义是:可以指向任意类型的Objective-C对象的指针。‘

id delegate; // id 已经隐含了 * 的作用

类似于 auto(?)

ARC

ARC(automatic reference counting,自动引用计数)为Objective-C提供了一种自动销毁不被引用的对象的机制。当项目开启了ARC,编译器会自动给项目添加代码来计算每个对象的引用数,即每个对象都会对指向自己的指针计数。当引用数为0时, 程序会自动销毁该对象,释放内存。

类似于 shared_ptr。

在Objective-C加入ARC之前,程序员必须手动维护引用计数。

[anObject release]; // abOject 会失去一个拥有方
[anObject retain]; // abOject 会得到一个拥有方
[anObject autorelease]; // abOject 会在 autorelease 池(对象)被排干(drain)的时候收到 release 消息

虽然 ARC 会自动使用 autorelease 池,但是必须由程序创建并排空相应的 autoreleasepool 池,语法如下:

// 创建 autorelease 池(对象)
@autoreleasepool {
  ...
} // autorelease 池被排空

字面量语法

格式:

NSString *lament = @"Hello World!";
NSArray *dataList = @[now, tomorrow, yesterday]; // 这三个都是 NSDate 对象

字面量语法是Objective-C语言的一种缩写,可以以不明确发送消息的方式创建实例。

还没有字面量语法的时候,只能使用 arrayWithObjects: 类方法来创建NSArray实例:

NSArray *dataList = [NSDate arrayWithObjects:now, tomorrow, yesterday, nil]; // nil 是结束标记,让方法停止运行

self

Objective-C的方法都包含一个隐含的局部变量 self。self 是指针,指向运行当前方法的对象。当某个对象要向自己发送消息时,就需要使用 self。它有 2 个简单的用法:

  1. 调用自身的存取方法,避免直接存取实例变量。
  2. 将 self 作为实参传给其他方法,以便其访问“当前的”对象。

NSObject 协议中的 self:

#include <objc/objc.h>
#include <objc/NSObjCRuntime.h>

@class NSString, NSMethodSignature, NSInvocation;
@protocol NSObject

- (BOOL)isEqual:(id)object;
@property (readonly) NSUInteger hash;
@property (readonly) Class superclass;
......
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'anObject.dynamicType' instead");
- (instancetype)self;
......
@end

self最终返回的结果就是instancetype类型的代理方法,它是动态类型,最终运行时才会确定,实例方法返回实例类型、静态方法返回的是Class。

super

super 的作用:直接调用父类中的某个方法,或者说从父类开始查找与之匹配的实现。

使用场合:子类重写父类的方法时想保留父类的一些行为。

结构体 objc_super 的官方解释:The compiler generates an objc_super data structure when it encounters the super keyword as the receiver of a message. It specifies the class definition of the particular superclass that should be messaged.

#include <objc/objc.h>
#include <objc/runtime.h>

#pragma GCC system_header

#ifndef OBJC_SUPER
#define OBJC_SUPER

/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained id receiver;
    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    __unsafe_unretained Class class;
#else
    __unsafe_unretained Class super_class;
#endif
};

当遇到super关键字时,编译器会生成一个objc_super结构体,作为消息的接收者,objc_super结构体使得接收消息的父类的定义被明确化。

isa指针

任何一个对象的isa指针都会指向创建该对象的类。

给对象发送消息的时候,对象就会通过isa指针找到该对象的类并查询是否有该消息名的方法。如果没有,就会继续查询它的父类,直到找到名为消息名的方法,或者到达继承链的顶端(NSObject)为止。

description方法和%@转换说明

格式说明符%@让对象描述自己,实际上在处理%@时,程序会向相应的指针变量所指的对象发送 description 消息。

description 方法会返回一个描述类实例的字符串。description 是一个 NSObject 方法,所以所有的对象都有这个方法。默认的 NSObject 实现会以字符串的形式返回该对象在内存的地址。不同的类可以重写(override)description 方法,来最有效地描述实例。

强引用循环和弱引用

两个对象互相拥有的关系将导致相关对象都无法释放,这种情况叫强引用循环,这是导致内存泄露的常见原因。

请添加图片描述

Xcode的Instruments中的Leaks组件可以找出程序中的强引用循环:

请添加图片描述

通过弱引用,可以解决该问题。弱引用是不说明所有权的指针,把 BNRAsset 的 holder 属性改成 weak,就能让 BNRAsset 对象不拥有它的 holder(BNREmployee 对象)。代码如下:

#import <Foundation/Foundation.h>
@class BNREmployee;

NS_ASSUME_NONNULL_BEGIN

@interface BNRAsset : NSObject

@property (nonatomic, copy) NSString *label;
@property (nonatomic, weak) BNREmployee *holder;
@property (nonatomic) unsigned int resaleValue;

@end

NS_ASSUME_NONNULL_END

完整程序见于:UestcXiye/Objective-C-Practice

弱引用的自动置零特性:强引用会保留对象的拥有方,使其不被释放。而弱引用则不会保留,因此标为弱引用的实例变量与属性指向的对象可能会消失,如果发生了这种情况,那么这个实例变量或属性会被设为 nil。

Collection 类

Collection 类的实例用于保存指向其他对象的指针。主要分为三种:

  1. NSArray 及其子类 NSMutableArray
  2. NSSet/NSMutableSet
  3. NSDictionary/NSMutableDictionary

注意以下 4 点:

  1. Collection 对象只能保存对象的指针,不能保存基本类型变量或指向结构的指针,需要先将这些 C 语言基本类型封装成对象,再存入 Collection 对象。

    例如:float变量、int变量等要先转换成NSNumber再存入Collection对象,或者直接用NSNumber字面量实例。结构可以用NSValue(它是NSNumber的父类)实例来封装。

  2. 其中 NSArray、NSSet、NSDictionary 具有不可修改性。使用它们可以节约内存提高性能,因为它们的 copy 方法仅仅返回指向自己的指针,不会做拷贝之类的事情。

  3. 向可改变的 Collection 对象中加入某个对象时,Collection 对象会成为该对象的拥有方;同理,移除对象时,Collection 对象就不再是该对象的拥有方了。对于不可改变的 Collection 对象而言,创建时就拥有其中所有对象的所有权,而 Collection 对象被释放时,它就放弃其中所有对象的所有权。

  4. Collection 对象不能保存 nil,需要将“空”包装成一个对象,可以使用 NSNull 类。

Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐