Подтвердить что ты не робот

Расширение не может содержать хранимое свойство, но почему статическое разрешено

Расширение не может содержать сохраненное свойство, но почему тогда статическое хранимое свойство должно быть определено внутри расширения?

Я также не нашел никакой документации, в которой упоминалось, что статическое свойство разрешено в расширении.

extension String {
  static let test = "Test"
  static var test2 = "Test2"
}
4b9b3361

Ответ 1

Расширения не могут содержать сохраненные свойства экземпляра. Зачем? Поскольку добавление свойства экземпляра изменит размер экземпляров этого типа. Что произойдет, если один модуль добавит расширение, чтобы Int теперь осталось 2 слова? Что тогда должно произойти, когда оно, например, получает Int из другого модуля, где они все еще имеют 1 слово?

Причина, по которой статические хранимые свойства разрешены в расширениях, - это просто потому, что они имеют статическое время жизни; они существуют независимо от любых экземпляров данного типа, которые вы распространяете. На самом деле они представляют собой не что иное, как глобальные хранимые переменные, просто имена помещаются в тип. Поэтому они могут быть свободно добавлены без влияния на код, который уже был скомпилирован без их знания.

Однако стоит отметить, что в настоящее время существуют три ограничения на определение статических хранимых свойств.

1. Вы не можете определить хранимое свойство static для общего типа

Для этого потребуется отдельное хранилище свойств для каждой отдельной специализации типовых заполнителей. Например, с помощью:

struct S<T> {

    static var foo: Int {
        return 5
    }

    static let bar = "" // error: Static stored properties not supported in generic types
}

Так же, как foo вызывается по индивидуальной специализации S, например S<Int>.foo и S<Float>.foo, а не на S (фактически, S в настоящее время даже не является типом, это требует, чтобы T); bar будет (вероятно) одинаковым. Он будет называться, например, S<Int>.bar, а не S.bar.

Это важная деталь, потому что метатет, который вызывается статическим членом, передается получателю в качестве неявного аргумента self. Это доступно в выражениях инициализации статического свойства; поэтому позволяет им использовать другие статические методы.

Следовательно, возможность вызова одного и того же инициализатора статического свойства в разных специализациях типового типа потенциально может создавать разные значения свойств для каждого (рассмотрим простой случай static let baz = T.self). Поэтому для каждого из них требуется отдельное хранилище.

Однако, что все сказано, нет никакой реальной причины, почему компилятор/среда выполнения не могут этого сделать, и это вполне может сделать в будущей версии языка. Хотя один из аргументов против этого состоит в том, что в некоторых случаях он может вызвать запутанное поведение.

Например, рассмотрим:

import Foundation

struct S<T> {
    static let date = Date()
}

Если среда выполнения неявно генерирует новое хранилище для date каждый раз, когда к нему обращаются по новой специализации S<T>, тогда S<Float>.date не будет равняться S<Int>.date; которые могут быть запутывающими и/или нежелательными.

2. Вы не можете определить хранимое свойство static в расширении протокола

В основном это следует из предыдущей точки. Хранимое свойство static в расширении протокола требует отдельного хранилища для каждого соответствующего типа этого протокола (но опять же, нет причин, по которым компилятор/время выполнения не могут этого сделать).

Это необходимо для протоколов, поскольку члены static в расширениях протокола не являются членами самого типа протокола. Они являются членами по конкретным типам, которые соответствуют протоколу.

Например, если мы имеем:

protocol P {}

extension P {

    static var foo: Int {
        return 5
    }

    static let bar = "" // error: Static stored properties not supported in generic types
                        // (not really a great diagnostic)
}

struct S : P {}
struct S1 : P {}

Мы не можем получить доступ к foo по самому типу протокола, мы не можем сказать P.foo. Мы можем сказать только S.foo или S1.foo. Это важно, потому что foo getter может вызывать требования к статическому протоколу на self; однако это невозможно, если self - P.self (то есть сам тип протокола), поскольку протоколы не соответствуют самим себе.

То же самое (вероятно) следует за хранимыми свойствами static, такими как bar.

3. Вы не можете определить хранимое свойство class

Я не верю, что никаких проблем с таким объявлением в самом классе класса не возникнет (он просто будет эквивалентен вычисленному class свойству, сохраненному хранимым свойством static).

Однако это было бы проблематично в расширениях, потому что расширения не могут добавлять новых членов в класс vtable Swift (хотя они могут добавить к аналогу Obj-C, если это применимо). Поэтому в большинстве случаев они не будут динамически отправляться (таким образом, было бы эффективно final, и поэтому static). Хотя, как говорится, в настоящее время разрешены вычисленные значения class в расширениях, поэтому это может быть допустимо в интересах согласованности.