阅读视图

发现新文章,点击刷新页面。

Objective-c中的nil,NULL,Nil,NSNULL

nil

OC中nil 是一个宏,定义如下:

#define nil ((id)0)
NSString *str = nil; //实际会被转为
NSString *str = ((id)0); // 即 str = 0

它表示 将 0 强制转换为 id 类型(Objective-C 对象的通用指针类型)。

Objective-C 中,对象指针的本质是 C 指针,而 0(或 NULL)是 C 语言中表示空指针的标准方式。这里提供两个验证方式:

1、nil 和 0 的等价性:

图片

2、指针的底层值:

图片

nilNULL 和 0 在指针层面是相同的,以下几行代码都不会报错

int *a = nil;
int *b = NULL;
NSString *c = NULL;
NSString *c0;
NSString *e = Nil;

为什么 Objective-C 要用 nil 而不是直接写 0

语义清晰,nil 明确表示“空对象指针”,而 0 可能被误解为整数值,与 C 语言的 NULL 区分,NULL 用于 C 指针(如 int*),nil 用于 Objective-C 对象。但是从上述例子中也可以看到NULL和nil可以混用

Nil

Nil 表示指向 Objective-C 类(Class)的空指针。它是一个宏,定义为 (Class)0。可以说跟nil是一模两样了

用于 类的空指针,通常较少使用:

Class someClass = Nil;
if (someClass == Nil) {    
    NSLog(@"Class is Nil");
}

与 nil 类似,但用于 类对象(Class)  而不是实例对象。

NULL

NULL 是 C 语言标准的空指针,表示指向任何类型的空指针。通常定义为 (void *)0

int *ptr = NULL;
if (ptr == NULL) {    
    NSLog(@"ptr is NULL");
}

适用于 C 语言层面的指针(如 int*char*)。

在 Objective-C 中,优先使用 nil 而不是 NULL

NSNull

在 NSArray 或 NSDictionary 中,nil 不能直接存储(因为 nil 表示列表结束),可以用 NSNull 占位

NSNull 是一个单例对象, 它只有一个单例方法:+[NSNull null],用于表示集合(如 NSArrayNSDictionary)中的空值

它不是指针,而是一个 真正的 Objective-C 对象

swift的get和set,newValue和oldValue

计算属性和存储属性都长什么样子,一定要记忆深刻

存储属性

var name: String
var name = "a"
var property: Int = {
return 1
}()

计算属性

class sample {
    var no1 = 0.0, no2 = 0.0
    var length = 300.0, breadth = 150.0

    var middle: (Double, Double) {
        get{
            return (length / 2, breadth / 2)
        }
        set(axis){ //注意这里的axis只是给newValue显示指定了参数名
            no1 = axis.0 - (length / 2)
            no2 = axis.1 - (breadth / 2)
        }
    }
}
或者
var computedValue: Int {
    get { _backingValue }
    set {
        // 使用隐式 newValue 在计算属性的setter中,如果不显式指定参数名,则默认使用`newValue`作为参数名。但是,计算属性没有内置的旧值(oldValue)访问,因为计算属性本身不存储值
        print("新值: \(newValue)")
        _backingValue = newValue
    }
}

在计算属性的setter中,如果不显式指定参数名,则默认使用`newValue`作为参数名。但是,计算属性没有内置的旧值(oldValue)访问,因为计算属性本身不存储值那么如果我们需要一个旧的值呢?需要手动存储旧值

private var _storage = 0
private var _oldValue = 0 // 额外存储旧值
var computedWithOldValue: Int {
    get { _storage }
    set {
        _oldValue = _storage  // 保存当前值为旧值
        _storage = newValue   // 更新为新值

        print("旧值: \(_oldValue), 新值: \(newValue)")
    }
}

只读的计算属性

var metaInfo: [String:String] {
        return [
            "head": self.head,
            "duration":"\(self.duration)"
        ]
    }
或者
var name: String {
       get {
           return ""
       }
   }

注意只读的计算属性并不是我们之前认识的readonly:因为只读计算属性在本类/结构体中也不能赋值图片那么如何实现readonly呢?

private(setvar name: String

这个也很好理解,是有set方法是private的,所以在类/结构体外还是可以get的

struct test1{
    private(set) var members :[String] = []
}

注意一个private(set)的集合,是不能添加和删除元素的

var t = test1()
t.members.append("a") //Cannot use mutating member on immutable value: 'members' setter is inaccessible

也可以结合计算属性使用

private var rawValue: Double = 0

private(set) var calibratedValue: Double {
    get { rawValue * 1.25 }
    set { rawValue = newValue / 1.25 }
}

关于计算属性的几个点

  • 存储属性我们可以定义常量或者是变量,但是对于计算属性,必须定义为变量,并且计算属性在定义时必须包含类型
  • 对于计算属性来说,set方法是可选的,而get方法必不可少
  • let的存储属性没有set方法,只读的计算属性area也没有set方法;所以我们不能简单的通过有没有set方法来区分属性是计算属性还是存储属性

接下来继续看一下协议

  • 协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。
  • 协议不指定属性是存储属性还是计算属性,它只指定属性的名称和类型
  • 在协议中,实例属性总是使用var声明为变量属性
  • 在协议中,始终使用static关键字作为类属性声明的前缀, 在类中实现时,可以使用classstatic关键字作类属性声明前缀(Class properties are only allowed within classes)
  • 协议还指定属性是可读的还是可读可写的

可读可写的属性在类型声明后通过写入{get set}表示可读属性通过写入{get}表示

protocol SomeProtocol {
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}

如果协议要求属性是只读的,这意味着遵循协议的类型必须提供该属性,并且至少允许外部读取(get)。但是,在实现时,该属性可以是存储属性或计算属性,甚至可以是可读可写的(即可写的),因为协议只要求至少可读,不禁止可写。如下图,mustBeSettable至少要满足SomeProtocol中的“可读可写”图片属性观察者属性观察器(didSet 和 willSet)是在属性的值被修改时触发的。这种修改必须通过显式的赋值语法完成

self.property = newValue

这种是观察不到的:

@State private var blurAmount = 0.0 {
    didSet {
        print("New value is \(blurAmount)")
    }
}
Slider(value: $blurAmount, in: 0...20)
    .onChange(of: blurAmount) { newValue in
      print("New value is \(newValue)")
    }

除了在声明语句中对属性赋值,其他对属性做赋值操作,必会触发观察器属性观察器(Property Observers)提供了两个特殊的关键字 newValue 和oldValue,用于在属性值变化时访问新值和旧值。它们分别用于 willSet 和 didSet 观察器中

var property: DataType = initialValue {
    willSet {
        // 使用 newValue 访问即将设置的值
        // 当前属性值仍是旧值
    }
    didSet {
        // 使用 oldValue 访问被覆盖的值
        // 当前属性值已是新值
    }
}

willSet中自带一个newValue的属性,oldValue用property自身即可访问,相同的didSet自带一个oldValue的属性,newValue用property自身即可访问

❌