I Swift är protokoll och protokollextensioner kraftfulla verktyg för att skapa flexibel och effektiv kod. De hjälper oss att definiera gemensamma egenskaper och funktionalitet för olika typer, utan att behöva duplicera kod. För att förstå detta på djupet, låt oss gå igenom ett exempel som belyser skillnaderna mellan strukturer och klasser samt hur vi kan använda protokoll och deras extensioner för att undvika kodupprepning.

När vi definierar en protokolltyp som Dog, kan vi använda både strukturer och klasser för att implementera den. Men det finns en viktig skillnad mellan dessa två typer. Strukturer tillhandahåller en standardiniterare, vilket gör att vi inte behöver skriva en anpassad initierare om vi inte vill, medan klasser kräver att vi skriver en initierare för att sätta värdena för deras egenskaper. Detta blir särskilt viktigt när vi har flera olika typer som alla måste följa samma protokoll.

Låt oss titta på en protokolldefinition för Dog:

swift
protocol Dog {
var name: String { get set } var color: String { get set } func speak() -> String }

Innan vi kan använda detta protokoll på olika typer måste vi implementera speak()-metoden för varje typ som konformerar till protokollet. I det här fallet behöver vi skapa en metod för varje struktur eller klass som implementerar Dog-protokollet. Om vi till exempel skapar en struktur för en JackRussel, en klass för en WhiteLab och en annan struktur för en Mutt, skulle vi behöva skriva speak()-metoden för varje typ:

swift
struct JackRussel: Dog { var name: String var color: String func speak() -> String { return "Woof" } } class WhiteLab: Dog { var name: String var color: String
init(name: String, color: String) {
self.name = name self.color = color } func speak() -> String { return "Woof" } } struct Mutt: Dog { var name: String var color: String func speak() -> String { return "Woof Woof" } }

Det här fungerar, men det leder till ineffektivitet. Om vi behöver ändra beteendet hos speak()-metoden måste vi gå in och ändra varje implementation för varje typ, vilket kan vara tidskrävande och utsätta oss för risken att skapa fel.

Här kommer protokollextensioner till undsättning. Genom att definiera en extension för vårt protokoll kan vi tilldela en standardimplementering av speak() som alla typer som konformerar till protokollet får automatiskt, utan att vi behöver skriva samma kod flera gånger:

swift
protocol Dog {
var name: String { get set } var color: String { get set } } extension Dog { func speak() -> String { return "Woof Woof" } }

I det här fallet har vi tagit bort speak() från själva protokollet och istället definierat metoden i extensionen. Nu får varje typ som implementerar Dog-protokollet automatiskt en standardimplementation av speak()-metoden. När vi skapar instanser av JackRussel, WhiteLab och Mutt får vi ett standardbeteende för speak():

swift
let dash = JackRussel(name: "Dash", color: "Brown and White")
let lily = WhiteLab(name: "Lily", color: "White")
let maple = Mutt(name: "Buddy", color: "Brown")
print(dash.speak()) // "Woof Woof" print(lily.speak()) // "Woof Woof" print(maple.speak()) // "Woof Woof"

Men det bästa är att vi fortfarande kan åsidosätta metoden i en specifik typ om vi vill ha ett annat beteende, som i fallet med Mutt:

swift
struct Mutt: Dog { var name: String var color: String func speak() -> String { return "I am hungry" } }

Så här ser vi hur protokollextensioner gör koden mer flexibel och underlättar kodunderhåll.

Vidare, när vi använder vissa typer i Swift, kan vi ställa oss inför två distinkta användningar av Any och any. Any med stort "A" används för att representera en variabel som kan hålla vilken typ som helst, medan any med litet "a" används för att markera existentiella typer som är mer dynamiska och kan vara ineffektiva, särskilt vid stora mängder data.

Det finns också tillfällen då Swift kan generera implementationer automatiskt för vissa protokoll, till exempel Equatable, Hashable och Comparable, så länge typerna inte använder klasser utan bara lagrade egenskaper eller associerade värden som också är konforma med dessa protokoll. Detta sparar mycket tid och minskar mängden kod som behöver skrivas manuellt.

När vi använder protokoll och protokollextensioner på rätt sätt, kan vi skapa kod som är både effektiv och lätt att underhålla.

Hur skapar man egna operatorer och använder avancerade operatorer i Swift?

I Swift kan vi utöka funktionaliteten hos våra egna typer genom att definiera och använda operatorer. Detta gör att vi kan skapa skräddarsydda operationer som är mer intuitiva och anpassade till våra specifika behov. För att förklara hur detta fungerar, låt oss börja med att skapa en egen typ som vi kan använda i våra exempel, som heter MyPoint.

swift
struct MyPoint { var x = 0 var y = 0 }

Strukturen MyPoint definierar en tvådimensionell punkt i ett koordinatsystem. När vi har vår egna typ, kan vi lägga till operatorer för att manipulera dessa punkter. Här lägger vi till tre operatorer: addition operator (+), addition tilldelningsoperator (+=), och en invers operator (-). Varje operator definieras som en statisk metod i vår typ.

swift
extension MyPoint { static func + (left: MyPoint, right: MyPoint) -> MyPoint {
return MyPoint(x: left.x + right.x, y: left.y + right.y)
}
static func += (left: inout MyPoint, right: MyPoint) { left.x += right.x left.y += right.y } static prefix func -(point: MyPoint) -> MyPoint { return MyPoint(x: -point.x, y: -point.y) } }

Dessa operatorer fungerar som vanliga operatorer, men är kopplade till vår typ MyPoint. Här definieras additionen och additionen med tilldelning som infixoperatorer, vilket innebär att de behöver två operander, medan inversen är en prefixoperator och arbetar på en enda operand.

Låt oss nu se på hur dessa operatorer används:

swift
let firstPoint = MyPoint(x: 1, y: 4)
let secondPoint = MyPoint(x: 5, y: 10)
var combined = firstPoint + secondPoint combined += firstPoint let inverse = -combined

Första operatorn adderar två punkter, vilket ger en ny punkt combined med värdena x = 6 och y = 14. Nästa operator använder addition tilldelning och lägger till värdena från firstPoint till combined, vilket gör att combined nu har värdena x = 7 och y = 18. Den sista operatorn tar den inverterade versionen av combined, vilket resulterar i en punkt där x = -7 och y = -18.

Förutom dessa standardoperatorer kan vi även skapa egna anpassade operatorer. Anpassade operatorer erbjuder oss flexibilitet att skapa nya operatorer utöver de som redan finns i Swift. För att skapa egna operatorer behöver vi deklarera dem globalt och definiera deras typ (infix, prefix eller postfix) innan vi använder dem i våra typer.

Vi kommer att skapa två nya operatorer för att multiplicera två punkter och för att kvadrera en punkt. För att deklarera dessa operatorer globalt använder vi följande kod:

swift
infix operator
prefix operator ••

Därefter definierar vi själva operatorernas funktionalitet:

swift
extension MyPoint { static func (left: MyPoint, right: MyPoint) -> MyPoint {
return MyPoint(x: left.x * right.x, y: left.y * right.y)
}
static prefix func •• (point: MyPoint) -> MyPoint { return MyPoint(x: point.x * point.x, y: point.y * point.y) } }

Nu kan vi använda dessa operatorer precis som andra operatorer:

swift
let multiplied = firstPoint secondPoint
let squared = ••secondPoint

I det här fallet multiplicerar vi två MyPoint-instanser med operatorn , vilket ger oss en punkt med värdena x = 5 och y = 40. Med operatorn •• kvadrerar vi den andra punkten, vilket ger oss en punkt med värdena x = 25 och y = 100.

Det är viktigt att förstå att vi inte är begränsade till att använda de standardoperatorer som följer med Swift. Vi kan skapa egna operatorer för att bättre passa vårt specifika användningsfall och göra koden mer uttrycksfull och enklare att förstå. När vi definierar operatorer för våra egna typer måste vi tänka på vilken typ av operator vi behöver och när den ska användas, så att den blir så intuitiv som möjligt.

Förutom att skapa och använda dessa operatorer, kan vi också dra nytta av avancerade bitoperatorer som AND, OR, XOR och NOT, samt vänsterskift- och högerskiftoperatorer, för att manipulera bitar i variabler. Dessa operatorer ger oss möjlighet att arbeta på en mer låg nivå och har en viktig roll i optimering och hantering av data. Därför är det viktigt att ha en förståelse för hur dessa bitoperatorer fungerar, särskilt i applikationer där prestanda är avgörande.

När vi arbetar med operatorer, särskilt de som vi skapar själva, bör vi också tänka på kodens läsbarhet och underhållbarhet. Det är lätt att bli lockad att skapa för många komplexa operatorer, men för att inte förvirra andra utvecklare och för att bevara kodens tydlighet, bör vi alltid sträva efter att hålla kodbasen så enkel och transparent som möjligt.