Zwykle widzę to pytanie zadane w inny sposób, na przykład Czy każdy bluszcz musi być własnością? (i podoba mi się odpowiedź bbum na to pytanie).
Używam właściwości prawie wyłącznie w moim kodzie. Jednak co jakiś czas współpracuję z kontrahentem, który od dawna rozwija się na iOS i jest tradycyjnym programistą gier. Pisze kod, który nie deklaruje prawie żadnych właściwości i opiera się na bluszczach. Zakładam, że robi to, ponieważ 1.) przyzwyczaił się do tego, ponieważ właściwości nie zawsze istniały aż do celu C 2.0 (październik 2007) i 2.) dla minimalnego wzrostu wydajności wynikającego z braku przechodzenia przez getter / setter.
Chociaż pisze kod, który nie przecieka, nadal wolałbym, żeby używał właściwości zamiast ivars. Rozmawialiśmy o tym i on mniej więcej nie widzi powodu, aby używać właściwości, ponieważ nie używaliśmy KVO i ma doświadczenie w zajmowaniu się problemami z pamięcią.
Moje pytanie jest bardziej ... Dlaczego miałbyś kiedykolwiek chcieć użyć okresu ivar - doświadczonego lub nie. Czy naprawdę istnieje tak wielka różnica w wydajności, że użycie ivar byłoby uzasadnione?
Również dla wyjaśnienia, zastępuję metody ustawiające i pobierające w razie potrzeby i używam ivar, który koreluje z tą właściwością wewnątrz metody pobierającej / ustawiającej. Jednak poza getter / setter lub init zawsze używam self.myProperty
składni.
Edytuj 1
Doceniam wszystkie dobre odpowiedzi. Jednym z nich, który wydaje się niepoprawny, jest to, że w przypadku ivar uzyskuje się hermetyzację, podczas gdy w przypadku właściwości nie. Po prostu zdefiniuj właściwość w kontynuacji klasy. To ukryje nieruchomość przed osobami postronnymi. Możesz również zadeklarować właściwość readonly w interfejsie i przedefiniować ją jako readwrite w implementacji, na przykład:
// readonly for outsiders
@property (nonatomic, copy, readonly) NSString * name;
i mieć w klasie kontynuację:
// readwrite within this file
@property (nonatomic, copy) NSString * name;
Aby był całkowicie „prywatny”, zadeklaruj go tylko w kontynuacji klasy.
Odpowiedzi:
Kapsułkowanie
Jeśli ivar jest prywatny, inne części programu nie mogą go tak łatwo uzyskać. Mając zadeklarowaną własność, sprytni ludzie mogą łatwo uzyskać dostęp i mutować za pomocą akcesorów.
Występ
Tak, w niektórych przypadkach może to mieć znaczenie. Niektóre programy mają ograniczenia polegające na tym, że nie mogą używać komunikatów objc w niektórych częściach programu (myśląc w czasie rzeczywistym). W innych przypadkach możesz chcieć uzyskać do niego bezpośredni dostęp w celu zwiększenia szybkości. W innych przypadkach dzieje się tak, ponieważ komunikaty objc działają jak zapora optymalizacyjna. Wreszcie, może zmniejszyć liczbę operacji zliczania referencji i zminimalizować szczytowe użycie pamięci (jeśli zostanie wykonane poprawnie).
Typy nietrywialne
Przykład: jeśli masz typ C ++, bezpośredni dostęp jest czasem lepszym podejściem. Typu nie można skopiować lub kopiowanie może nie być łatwe.
Wielowątkowość
Wiele z twoich ivars jest współzależnych. Musisz zapewnić integralność danych w kontekście wielowątkowym. Dlatego możesz preferować bezpośredni dostęp do wielu członków w krytycznych sekcjach. Jeśli pozostaniesz przy dostępach do danych współzależnych, Twoje zamki muszą zazwyczaj być ponownie wprowadzane, co często kończy się o wiele więcej przejęć (czasami znacznie więcej).
Poprawność programu
Ponieważ podklasy mogą przesłonić każdą metodę, możesz w końcu zauważyć, że istnieje semantyczna różnica między pisaniem do interfejsu a odpowiednim zarządzaniem stanem. Bezpośredni dostęp do poprawności programu jest szczególnie powszechny w stanach częściowo skonstruowanych - w inicjalizatorach i w
dealloc
programie najlepiej jest używać bezpośredniego dostępu. Można również znaleźć to powszechne w implementacjach akcesor, konstruktora wygody,copy
,mutableCopy
implementacjach i archiwizacji / serializacji.Jest to również częstsze, gdy przechodzimy od nastawienia wszystkiego, co ma publiczny dostęp do odczytu i zapisu, do takiego, które dobrze ukrywa szczegóły / dane implementacji. Czasami trzeba poprawnie obejść skutki uboczne, które może wprowadzić zastąpienie podklasy, aby zrobić właściwą rzecz.
Rozmiar binarny
Domyślne zadeklarowanie wszystkiego, co jest readwrite, zwykle skutkuje powstaniem wielu metod pomocniczych, których nigdy nie potrzebujesz, gdy zastanawiasz się przez chwilę nad wykonaniem programu. Więc doda trochę tłuszczu do twojego programu i czasu ładowania.
Minimalizuje złożoność
W niektórych przypadkach po prostu nie jest konieczne dodawanie + typ + utrzymywanie całego tego dodatkowego szkieletu dla prostej zmiennej, takiej jak prywatna bool, która jest zapisywana w jednej metodzie i czytana w innej.
Nie oznacza to wcale, że używanie właściwości lub akcesoriów jest złe - każdy z nich ma ważne zalety i ograniczenia. Podobnie jak w przypadku wielu języków obiektowych i podejść do projektowania, należy również preferować akcesory o odpowiedniej widoczności w ObjC. Będą chwile, kiedy będziesz musiał zboczyć. Z tego powodu myślę, że często najlepiej jest ograniczyć bezpośredni dostęp do implementacji, która deklaruje ivar (np. Zadeklarować
@private
).ponownie Edycja 1:
Większość z nas nauczyła się na pamięć dynamicznego wywoływania ukrytego akcesorium (o ile znamy jego nazwę…). W międzyczasie większość z nas nie pamiętała, jak prawidłowo uzyskać dostęp do ivar, które nie są widoczne (poza KVC). Kontynuacja klasy pomaga , ale wprowadza luki w zabezpieczeniach.
To obejście jest oczywiste:
Teraz spróbuj tylko z ivar i bez KVC.
źródło
@private
, kompilator powinien zabronić członkom dostępu poza klasą i metodami instancji - czy to nie jest to, co widzisz?Dla mnie to zazwyczaj wydajność. Uzyskanie dostępu do ivar obiektu jest tak szybkie, jak uzyskanie dostępu do elementu struktury w języku C przy użyciu wskaźnika do pamięci zawierającej taką strukturę. W rzeczywistości obiekty Objective-C są w zasadzie strukturami C umieszczonymi w dynamicznie przydzielonej pamięci. Zwykle jest to tak szybkie, jak można uzyskać kod, nawet ręcznie zoptymalizowany kod asemblera nie może być szybszy.
Dostęp do ivar poprzez getter / ustawienie wymaga wywołania metody Objective-C, które jest znacznie wolniejsze (co najmniej 3-4 razy) niż „normalne” wywołanie funkcji C, a nawet normalne wywołanie funkcji C byłoby już wielokrotnie wolniejsze niż dostęp do elementu członkowskiego struktury. W zależności od atrybutów twojej właściwości, implementacja ustawiająca / pobierająca generowana przez kompilator może obejmować inne wywołanie funkcji C do funkcji
objc_getProperty
/objc_setProperty
, ponieważ będą one musiałyretain
/copy
/autorelease
obiekty w razie potrzeby i dalej wykonywać blokowanie spinowe dla właściwości atomowych, jeśli to konieczne. Może to łatwo stać się bardzo kosztowne i nie mówię o 50% wolniejszym.Spróbujmy tego:
Wynik:
Jest to 4,28 razy wolniejsze i był to nieatomowy prymitywny int, właściwie najlepszy przypadek ; większość innych przypadków jest jeszcze gorsza (spróbuj
NSString *
właściwości atomowej !). Jeśli więc możesz żyć z faktem, że każdy dostęp do ivar jest 4-5 razy wolniejszy niż mógłby być, używanie właściwości jest w porządku (przynajmniej jeśli chodzi o wydajność), jednak istnieje wiele sytuacji, w których taki spadek wydajności jest całkowicie niedopuszczalne.Aktualizacja 2015-10-20
Niektórzy twierdzą, że to nie jest prawdziwy problem świata, powyższy kod jest czysto syntetyczny i nigdy nie zauważysz tego w prawdziwej aplikacji. Okej, spróbujmy więc próbki z prawdziwego świata.
Poniższy kod definiuje
Account
obiekty. Konto ma właściwości, które opisują imię i nazwisko (NSString *
), płeć (enum
) i wiek (unsigned
) jego właściciela, a także saldo (int64_t
). Obiekt konta mainit
metodę icompare:
metodę.compare:
Metoda jest zdefiniowana jako: Kobieta zlecenia przed mężczyzna, nazwy zamówić alfabetycznie młodzi zlecenia przed starym, zamówienia równowagi niskiego do wysokiego.W rzeczywistości istnieją dwie klasy kont
AccountA
iAccountB
. Jeśli spojrzysz na ich implementację, zauważysz, że są one prawie całkowicie identyczne, z jednym wyjątkiem:compare:
metoda.AccountA
obiekty uzyskują dostęp do swoich właściwości za pomocą metody (getter), podczas gdyAccountB
obiekty uzyskują dostęp do własnych właściwości przez ivar. To naprawdę jedyna różnica! Obaj uzyskują dostęp do właściwości drugiego obiektu do porównania przez getter (dostęp do niego przez ivar nie byłby bezpieczny! A co, jeśli drugi obiekt jest podklasą i przesłonił funkcję pobierającą?). Zauważ również, że dostęp do własnych właściwości jako ivars nie przerywa hermetyzacji (ivars nadal nie są publiczne).Konfiguracja testu jest naprawdę prosta: utwórz 1 losowe konta Mio, dodaj je do tablicy i posortuj tę tablicę. Otóż to. Oczywiście istnieją dwie tablice, jedna dla
AccountA
obiektów i jedna dlaAccountB
obiektów, a obie tablice są wypełnione identycznymi kontami (to samo źródło danych). Sprawdzamy, ile czasu zajmuje sortowanie tablic.Oto wynik kilku przebiegów, które wykonałem wczoraj:
Jak widać, sortowanie tablicy
AccountB
obiektów jest zawsze znaczące szybciej niż sortowanie tablicyAccountA
obiektów.Ktokolwiek twierdzi, że różnice w czasie wykonywania do 1,32 sekundy nie mają znaczenia, lepiej nigdy nie programować interfejsu użytkownika. Jeśli chcę na przykład zmienić kolejność sortowania dużej tabeli, takie różnice czasu mają ogromne znaczenie dla użytkownika (różnica między akceptowalnym a powolnym interfejsem użytkownika).
Również w tym przypadku przykładowy kod jest jedyną prawdziwą pracą wykonywaną tutaj, ale jak często twój kod jest tylko małą przekładnią skomplikowanego mechanizmu zegarowego? A jeśli każdy bieg spowalnia cały proces w ten sposób, co to ostatecznie oznacza dla szybkości całego mechanizmu zegarowego? Zwłaszcza jeśli jeden etap pracy zależy od wydajności innego, co oznacza, że wszystkie nieefektywności będą się sumować. Większość nieefektywności sama w sobie nie stanowi problemu, to sama ich suma staje się problemem dla całego procesu. A taki problem to nic, co programista łatwo pokaże, ponieważ profiler służy do znajdowania krytycznych punktów zapalnych, ale żadna z tych nieefektywności nie jest sama w sobie gorącymi punktami. Czas procesora jest po prostu średnio rozłożony między nimi, ale każdy z nich ma tylko tak malutki ułamek, że wydaje się całkowitą stratą czasu na jego optymalizację. I to prawda,
A nawet jeśli nie myślisz w kategoriach czasu procesora, ponieważ uważasz, że marnowanie czasu procesora jest całkowicie dopuszczalne, w końcu „to za darmo”, to co z kosztami hostingu serwera spowodowanymi zużyciem energii? A co z czasem pracy baterii urządzeń mobilnych? Gdybyś napisał dwukrotnie tę samą aplikację mobilną (np. Własną mobilną przeglądarkę internetową), raz wersja, w której wszystkie klasy uzyskują dostęp do własnych właściwości tylko przez gettery, a raz, w której wszystkie klasy uzyskują do nich dostęp tylko przez ivars, używanie pierwszej z nich na pewno będzie wyczerpane Bateria jest znacznie szybsza niż ta druga, mimo że są one funkcjonalne, a użytkownik może nawet poczuć się nieco szybszy.
Oto kod twojego
main.m
pliku (kod opiera się na włączeniu ARC i pamiętaj, aby użyć optymalizacji podczas kompilacji, aby zobaczyć pełny efekt):źródło
unsigned int
które nigdy nie jest zachowywane / publikowane, niezależnie od tego, czy używasz ARC, czy nie. Samo utrzymanie / zwolnienie jest kosztowne, więc różnica będzie mniejsza, ponieważ zarządzanie utrzymaniem dodaje statyczny narzut, który zawsze istnieje, bezpośrednio używając setter / getter lub ivar; jednak nadal zaoszczędzisz na jednym dodatkowym wywołaniu metody, jeśli uzyskasz bezpośredni dostęp do ivar. W większości przypadków nic wielkiego, chyba że robisz to kilka tysięcy razy na sekundę. Apple mówi, że domyślnie używaj getters / setters, chyba że jesteś w metodzie init / dealloc lub masz wąskie gardło.copy
utworzy kopii jej wartości za każdym razem, gdy uzyskasz do niej dostęp. Funkcja pobierająca właściwość jest podobna do metody pobierającej właściwość / . Zasadniczo jest to kod . Tylko seter kopiuje wartość i będzie to mniej więcej tak wyglądało , podczas gdy seter / wygląda tak:copy
strong
retain
return [[self->value retain] autorelease];
[self->value autorelease]; self->value = [newValue copy];
strong
retain
[self->value autorelease]; self->value = [newValue retain];
Najważniejszym powodem jest koncepcja OOP polegająca na ukrywaniu informacji : jeśli ujawnisz wszystko za pomocą właściwości, a tym samym pozwolisz zewnętrznym obiektom na podglądanie wewnętrznych elementów innego obiektu, wykorzystasz te wewnętrzne, a tym samym skomplikujesz zmianę implementacji.
Wzrost „minimalnej wydajności” może szybko się podsumować i stać się problemem. Wiem z doświadczenia; Pracuję nad aplikacją, która naprawdę ogranicza iDevices do granic możliwości i dlatego musimy unikać niepotrzebnych wywołań metod (oczywiście tylko wtedy, gdy jest to rozsądnie możliwe). Aby pomóc w osiągnięciu tego celu, unikamy również składni z kropką, ponieważ utrudnia ona sprawdzenie liczby wywołań metod na pierwszy rzut oka: na przykład, ile wywołań metod wywołuje wyrażenie
self.image.size.width
? Z drugiej strony możesz od razu to stwierdzić[[self image] size].width
.Ponadto, przy prawidłowym nazewnictwie ivar, KVO jest możliwe bez właściwości (IIRC, nie jestem ekspertem KVO).
źródło
Semantyka
@property
może wyrazić, że ivars nie mogą:nonatomic
icopy
.@property
czego nie można:@protected
: publiczne w podklasach, prywatne na zewnątrz.@package
: publiczny w frameworkach na 64 bitach, prywatny na zewnątrz. Tak samo jak@public
w przypadku 32 bitów. Zobacz 64-bitową kontrolę dostępu dla klas i instancji firmy Apple .id __strong *_objs
.Występ
Krótka historia: ivary są szybsze, ale nie ma to znaczenia dla większości zastosowań.
nonatomic
właściwości nie używają blokad, ale direct ivar jest szybszy, ponieważ pomija wywołanie akcesorów. Aby uzyskać szczegółowe informacje, przeczytaj następujący e-mail z lists.apple.com.źródło
Właściwości a zmienne instancji to kompromis, ostatecznie wybór sprowadza się do aplikacji.
Hermetyzacja / ukrywanie informacji To jest dobra rzecz (TM) z punktu widzenia projektowania, wąskie interfejsy i minimalne powiązania sprawiają, że oprogramowanie jest łatwe w utrzymaniu i zrozumiałe. W Obj-C dość trudno jest ukryć cokolwiek, ale zmienne instancji zadeklarowane w implementacji są tak blisko, jak to tylko możliwe.
Wydajność Chociaż „przedwczesna optymalizacja” jest złą rzeczą (TM), pisanie źle działającego kodu tylko dlatego, że można, jest co najmniej równie złe. Trudno jest argumentować, że wywołanie metody jest droższe niż ładowanie lub przechowywanie, aw kodzie wymagającym dużej mocy obliczeniowej koszt szybko się sumuje.
W języku statycznym z właściwościami, takimi jak C #, wywołania ustawiające / pobierające często mogą być zoptymalizowane przez kompilator. Jednak Obj-C jest dynamiczny i usuwanie takich wywołań jest znacznie trudniejsze.
Abstrakcja Argumentem przeciwko zmiennym instancji w Obj-C było tradycyjnie zarządzanie pamięcią. Zmienne instancji MRC wymagają, aby wywołania zatrzymania / zwolnienia / automatycznego wydania były rozproszone w całym kodzie, właściwości (syntetyzowane lub nie) utrzymują kod MRC w jednym miejscu - zasada abstrakcji, która jest Dobrą Rzeczą (TM). Jednak w przypadku GC lub ARC ten argument znika, więc abstrakcja zarządzania pamięcią nie jest już argumentem przeciwko zmiennym instancji.
źródło
Właściwości udostępniają zmienne innym klasom. Jeśli potrzebujesz zmiennej, która jest tylko względna w stosunku do tworzonej klasy, użyj zmiennej instancji. Oto mały przykład: klasy XML do analizowania RSS i tym podobnych przechodzą przez kilka metod delegatów i tym podobne. Praktyczne jest posiadanie wystąpienia NSMutableString do przechowywania wyników każdego innego przebiegu analizy. Nie ma powodu, dla którego klasa zewnętrzna musiałaby kiedykolwiek uzyskać dostęp do tego ciągu lub manipulować nim. Więc po prostu zadeklaruj to w nagłówku lub prywatnie i uzyskaj do niego dostęp w całej klasie. Ustawienie dla niej właściwości może być przydatne tylko w celu upewnienia się, że nie ma problemów z pamięcią, używając self.mutableString do wywołania metody pobierającej / ustawiającej.
źródło
Kompatybilność wsteczna była dla mnie czynnikiem. Nie mogłem używać żadnych funkcji Objective-C 2.0, ponieważ tworzyłem oprogramowanie i sterowniki drukarek, które musiały działać w systemie Mac OS X 10.3 jako część wymagań. Wiem, że Twoje pytanie wydawało się być skierowane na iOS, ale pomyślałem, że nadal będę podawać powody, dla których nie używam właściwości.
źródło