можно ли сделать метод -init закрытым в objective-c?

<p>мне нужно скрыть (сделать личным) метод <code>-init</code> моего класса в objective-c.</p><p>как я могу это сделать?</p>

<p>objective-c, как и smalltalk, не имеет понятия "частные" и "публичные" методы. любое сообщение может быть отправлено любому объекту в любое время.</p><p>что вы можете сделать, это бросить <code>nsinternalinconsistencyexception</code>, если ваш метод <code>-init</code> вызван:</p><pre><code>- (id)init { [self release]; @throw [nsexception exceptionwithname:nsinternalinconsistencyexception reason:@"-init is not a valid initializer for the class foo" userinfo:nil]; return nil; } </code></pre><p>другая альтернатива - которая, вероятно, намного лучше на практике - это заставить <code>-init</code> делать что-то разумное для вашего класса, если это вообще возможно.</p><p>если вы пытаетесь сделать это, потому что вы пытаетесь "гарантировать" использование одноэлементного объекта, не беспокойтесь. в частности, не беспокойтесь о методе "override <code>+allocwithzone:</code>, <code>-init</code>, <code>-retain</code>, <code>-release</code>" для создания синглетонов. это практически всегда не нужно и просто добавляет усложнение без реального существенного преимущества.</p><p>вместо этого просто напишите свой код таким образом, чтобы ваш метод <code>+sharedwhatever</code> соответствовал тому, как вы осуществляете доступ к одиночному файлу, и документируйте это как способ получения экземпляра одиночного файла в своем заголовке. это должно быть все, что вам нужно в подавляющем большинстве случаев.</p>

<h3><code>ns\_unavailable</code></h3><pre><code>- (instancetype)init ns\_unavailable; </code></pre><p>это короткая версия недоступного атрибута. впервые он появился в macos <a href="https://developer.apple.com/library/content/releasenotes/general/macosxlionapidiffs/foundation.html">10.7</a> и <a href="https://developer.apple.com/library/content/releasenotes/general/ios50apidiff/index.html">ios 5</a> . он определен в nsobjcruntime.h как <code>#define ns\_unavailable unavailable\_attribute</code>.</p><p>существует версия, которая <a href="https://developer.apple.com/library/content/documentation/swift/conceptual/buildingcocoaapps/mixandmatch.html#//apple\_ref/doc/uid/tp40014216-ch10-id178">отключает метод только для swift клиентов</a> , а не для кода objc:</p><pre><code>- (instancetype)init ns\_swift\_unavailable; </code></pre><h3><code>unavailable</code></h3><p>добавьте в заголовок атрибут <a href="http://clang.llvm.org/docs/languageextensions.html#messages-on-deprecated-and-unavailable-attributes"><code>unavailable</code></a> , чтобы сгенерировать <strong> ошибку компилятора </strong> при любом вызове init.</p><pre><code>-(instancetype) init \_\_attribute\_\_((unavailable("init not available"))); </code></pre><p><img src="https://i.stack.imgur.com/axcpr.png" alt="compile time error"></p><p>если у вас нет причины, просто введите <code>\_\_attribute\_\_((unavailable))</code> или даже <code>\_\_unavailable</code>:</p><pre><code>-(instancetype) \_\_unavailable init; </code></pre><h3><code>doesnotrecognizeselector:</code></h3><p>используйте <a href="http://developer.apple.com/library/mac/#documentation/cocoa/reference/foundation/classes/nsobject\_class/reference/reference.html"><code>doesnotrecognizeselector:</code></a> , чтобы вызвать nsinvalidargumentexception. <em> "система времени выполнения вызывает этот метод всякий раз, когда объект получает сообщение aselector, на которое он не может ответить или переслать." </em></p><pre><code>- (instancetype) init { [self release]; [super doesnotrecognizeselector:\_cmd]; return nil; } </code></pre><h3><code>nsassert</code></h3><p>используйте <a href="https://developer.apple.com/library/mac/#documentation/cocoa/reference/foundation/miscellaneous/foundation\_functions/reference/reference.html"><code>nsassert</code></a> , чтобы вызвать nsinternalinconsistencyexception и показать сообщение:</p><pre><code>- (instancetype) init { [self release]; nsassert(false,@"unavailable, use initwithblah: instead"); return nil; } </code></pre><h3><code>raise:format:</code></h3><p>используйте <a href="https://developer.apple.com/library/mac/documentation/cocoa/reference/foundation/classes/nsexception\_class/reference/reference.html#//apple\_ref/occ/clm/nsexception/raise:format:"><code>raise:format:</code></a> , чтобы создать собственное исключение:</p><pre><code>- (instancetype) init { [self release]; [nsexception raise:nsgenericexception format:@"disabled. use +[[%@ alloc] %@] instead", nsstringfromclass([self class]), nsstringfromselector(@selector(initwithstatedictionary:))]; return nil; } </code></pre><p><code>[self release]</code> необходим, потому что объект уже был <code>alloc</code>ated. при использовании arc компилятор вызовет его для вас. в любом случае не о чем беспокоиться, когда вы собираетесь намеренно прекратить исполнение.</p><h3><code>objc\_designated\_initializer</code></h3><p>если вы намереваетесь отключить <code>init</code> для принудительного использования назначенного инициализатора, для этого есть атрибут:</p><pre><code>-(instancetype)myowninit ns\_designated\_initializer; </code></pre><p>это генерирует предупреждение, если никакой другой метод инициализатора не вызывает <code>myowninit</code> внутри. подробности будут опубликованы в <a href="https://developer.apple.com/library/ios/releasenotes/objectivec/modernizationobjc/adoptingmodernobjective-c/adoptingmodernobjective-c.html#//apple\_ref/doc/uid/tp40014150">принятие modern objective-c</a> после следующего выпуска xcode (я думаю).</p>

<p>apple начала использовать следующее в своих заголовочных файлах, чтобы отключить конструктор init:</p><pre><code>- (instancetype)init ns\_unavailable; </code></pre><p>это правильно отображается как ошибка компилятора в xcode. в частности, это установлено в нескольких из их заголовочных файлов healthkit (hkunit - один из них).</p>

<p>если вы говорите о методе -init по умолчанию, то вы не можете. он унаследован от nsobject, и каждый класс ответит на него без предупреждений.</p><p>вы можете создать новый метод, скажем -initmyclass, и поместить его в закрытую категорию, как предлагает мэтт. затем определите метод -init по умолчанию, чтобы вызвать исключение, если оно вызывается, или (лучше) вызвать ваш закрытый -initmyclass с некоторыми значениями по умолчанию.</p><p>одна из главных причин, по которой люди, похоже, хотят скрыть init, - это <a href="http://developer.apple.com/documentation/cocoa/conceptual/cocoafundamentals/cocoaobjects/chapter\_3\_section\_10.html">singleton objects</a> . если это так, то вам не нужно скрывать -init, просто вместо этого вернуть одиночный объект (или создать его, если он еще не существует).</p>

<p>поместите это в заголовочный файл</p><pre><code>- (id)init unavailable\_attribute; </code></pre>

<p>проблема в том, что вы не можете сделать его "приватным/невидимым", потому что метод init отправляется на id (так как alloc возвращает id), а не на yourclass</p><p>обратите внимание, что с точки зрения компилятора (средства проверки) идентификатор может потенциально реагировать на все, что когда-либо вводилось (он не может проверить, что действительно входит в идентификатор во время выполнения), поэтому вы можете скрыть init только тогда, когда ничего не будет (publicly = in header) используйте метод init, тогда как компиляция узнает, что id не может ответить на init, поскольку нигде нет init (в вашем источнике, всех библиотеках и т. д.)</p><p>таким образом, вы не можете запретить пользователю проходить инициализацию и быть разбитым компилятором ... но что вы можете сделать, это запретить пользователю получить реальный экземпляр, вызвав init</p><p>просто путем реализации init, который возвращает nil и имеет (приватный/невидимый) инициализатор, имя которого кто-то другой не получит (как initonce, initwithspecial ...)</p><pre><code>static someclass * sinstance = nil; - (id)init { // possibly throw smth. here return nil; } - (id)initonce { self = [super init]; if (self) { return self; } return nil; } + (someclass *) shared { if (nil == sinstance) { sinstance = [[someclass alloc] initonce]; } return sinstance; } </code></pre><p>примечание: что кто-то может сделать это</p><pre><code>someclass * c = [[someclass alloc] initonce]; </code></pre><p>и он фактически вернет новый экземпляр, но если initonce нигде в нашем проекте не будет публично объявлен (в заголовке), он выдаст предупреждение (id может не отвечать ...), и в любом случае, человеку, использующему это, потребуется точно знать, что настоящим инициализатором является initonce</p><p>мы могли бы предотвратить это еще дальше, но в этом нет необходимости</p>

<p>это зависит от того, что вы подразумеваете под "сделать личным". в objective-c вызов метода для объекта лучше описать как отправку сообщения этому объекту. в языке нет ничего, что запрещало бы клиенту вызывать любой данный метод объекта; лучшее, что вы можете сделать, это не объявлять метод в заголовочном файле. если клиент, тем не менее, вызывает "закрытый" метод с правильной подписью, он все равно будет выполняться во время выполнения.</p><p>тем не менее, наиболее распространенный способ создания закрытого метода в objective-c - это создать <a href="http://developer.apple.com/documentation/cocoa/conceptual/objectivec/articles/chapter\_4\_section\_1.html">category</a> в файле реализации и объявить все "скрытые" методы в нем. помните, что это не помешает запуску вызовов <code>init</code>, но компилятор выдаст предупреждения, если кто-нибудь попытается это сделать.</p><p>myclass.m</p><pre><code>@interface myclass (privatemethods) - (nsstring*) init; @end @implementation myclass - (nsstring*) init { // code... } @end </code></pre><p>на эту тему есть неплохой <a href="http://forums.macrumors.com/showthread.php?t=470056">тема</a> на macrumors.com.</p>

<p>вы можете объявить любой метод недоступным, используя <code>ns\_unavailable</code>.</p><p>таким образом, вы можете поместить эти строки ниже вашего @interface</p><pre><code>- (instancetype)init ns\_unavailable; + (instancetype)new ns\_unavailable; </code></pre><hr><p>еще лучше определить макрос в заголовке вашего префикса</p><pre><code>#define no\_init \ - (instancetype)init ns\_unavailable; \ + (instancetype)new ns\_unavailable; </code></pre><p>а также</p><pre><code>@interface yourclass : nsobject no\_init // your properties and messages @end </code></pre>

<p>я должен упомянуть, что размещение утверждений и поднятие исключений для методов скрытия в подклассе имеет неприятную ловушку для благонамеренного.</p><p>я бы порекомендовал использовать <code>\_\_unavailable</code> как <a href="https://stackoverflow.com/a/5772821/2400328">джано объяснил для своего первого примера</a> .</p><p>методы могут быть переопределены в подклассах. это означает, что если метод в суперклассе использует метод, который просто вызывает исключение в подклассе, он, вероятно, не будет работать должным образом. другими словами, вы просто сломали то, что раньше работало. это верно и для методов инициализации. вот пример такой довольно распространенной реализации:</p><pre><code>- (superclass *)initwithparameters:(type1 *)arg1 optional:(type2 *)arg2 { ...bla bla... return self; } - (superclass *)initwithlessparameters:(type1 *)arg1 { self = [self initwithparameters:arg1 optional:default\_arg2]; return self; } </code></pre><p>представьте, что происходит с -initwithlessparameters, если я делаю это в подклассе:</p><pre><code>- (subclass *)initwithparameters:(type1 *)arg1 optional:(type2 *)arg2 { [self release]; [super doesnotrecognizeselector:\_cmd]; return nil; } </code></pre><p>это подразумевает, что вы должны стремиться использовать закрытые (скрытые) методы, особенно в методах инициализации, если только вы не планируете переопределять методы. но это другая тема, так как вы не всегда имеете полный контроль над реализацией суперкласса. (это заставляет меня усомниться в использовании \_\_attribute ((objc\_designated\_initializer)) как плохой практике, хотя я не использовал его подробно.)</p><p>это также подразумевает, что вы можете использовать утверждения и исключения в методах, которые должны быть переопределены в подклассах. ("абстрактные" методы как в <a href="https://stackoverflow.com/questions/1034373/creating-an-abstract-class-in-objective-c">создание абстрактного класса в objective-c</a> )</p><p>и не забывайте о + новом методе класса.</p>