📜 ⬆️ ⬇️

Symmetric difference in Swift and Objective-C capabilities

image


In this article, I’ll talk about the difference in capabilities offered by iOS developers to Swift and Objective-C. Of course, developers who were interested in Apple's new language have already seen quite a few such articles, so I decided to focus on the differences that really affect the development process and the architecture of the application. That is, the differences that you should know to use the language as efficiently as possible. I tried to make the most complete list that meets these criteria.


In addition, talking about the new features that Swift brought to the development, I tried not to forget to mention what he had lost compared to Objective-C.


For each item, I tried to briefly outline the essence of the differences, without going into details, and make code examples on the contrary detailed. In them, I commented on all the nuances, even those that are not directly related to the difference under consideration.


At the time of this writing, the current version of Swift is 3.0.1.



1. Classes, structures and enumerations


Classes in Swift do not have one common ancestor, like NSObject in Objective-C. Moreover, classes may not have an ancestor at all.


Structures in Swift are almost as functional as classes. Like classes, they can have static and ordinary properties and methods, initializers, subscripts, extensions, and can implement protocols. They differ from classes in that they are passed by value and have no inheritance.


//  ,   . /*    .  ,      struct  class,    . */ struct Rocket { //      Stage. //   var  ,  let - . var stages: [Stage] /*    Int.     . */ static let maxAllowedStages = 4 /* ,        .     launch() -> Void  launch() -> ()  Void    typealias  ()  ()   .          . */ func launch() { // ... } //    ([Stage]) -> Double static func calculateHeight(for stages: [Stage]) -> Double { /*  reduce     Swift.      .        ,      .  ,        ,    . */ return stages.reduce(0) { (accumulator, stage) -> Double in return accumulator + stage.height } } /* Failable ,   ,      nil.         init. */ init?(stages: [Stage]) { if stages.count > Rocket.maxAllowedStages { return nil } self.stages = stages } /*  (subscript)             . rocket[1] = stage */ /*       ,     . */ /*      ,      . */ subscript (index: Int) -> Stage { get { return stages[index] } set(newValue) { stages[index] = newValue } } } /*  ,        :        . */ protocol TransportableByTrain { func placeOnTrain() -> [Carriage] init(train: [Carriage]) } /*      ,    , , , ,     . */ extension Rocket: TransportableByTrain { func placeOnTrain() -> [Carriage] { return stages.map { stage in return Carriage(content: stage) } } init(train: [Carriage]){ let stages = train.map { $0.content as! Stage } self.init(stages: stages)! } } 

Enumerations in Swift may not have values.


 //   rawValue. enum LaunchState { case preparing, ready, launching, failed, succeeded } 

But, if there are values, they can be not only integers, but also real numbers, and strings, and symbols. Instances of enumerations are not automatically rawValue to the types of internal values, so you should use the rawValue property to access them.


 //   rawValue. enum LaunchEvent: Int { case poweredOn = 1, fuelLoaded, oxidizerLoaded, countAutoSequenceStarted, goForlaunchVerification, preLaunchChecks, pressurizePropellantTanks, ignitionSequenceStart, liftoff } let lastEvent = LaunchEvent.liftoff lastEvent.rawValue // 9 /*   `rawValue`   failable    . */ let firstEvent = LaunchEvent(rawValue: 1) //LaunchSequence.poweredOn let nonexistentEvent = LaunchEvent(rawValue: 0) //nil 

If the enumeration does not have rawValue , then each case enumeration may have its own associated values. There may be several of them, and they can be of any type.


 //    . enum LaunchError { case compromisedHullIntegrity(stage: Stage) case engineMalfunction(engine: Engine, malfunction: Malfunction) case unrecognizedError } 

Enumerations, as well as structures, are passed by value. And they have the same possibilities given above, except for the stored properties. Properties of enumerations can only be computed (computed properties).


 //     . extension LaunchEvent { static var sequence: [LaunchEvent] { return Array(1...9).map { LaunchEvent(rawValue: $0)! } } } 

Read more about listings: [1]


This rich functionality of structures and enumerations allows us to use them instead of classes where values ​​are more appropriate than objects. The purpose of this separation is to simplify the architecture of the application. More on complexity management: [2]


2. Types of function, methods and closures


In Swift, functions, methods, and closures are first class citizens, that is, they have types and can be stored in variables and passed as a parameter to a function. The types of functions, methods, and closures are determined only by the returned and accepted values. That is, if a variable of a certain type is declared, then it can save both a function and a method or a closure. Instances of these types are passed by reference.


This unification of entities has led to a simplification of their use. In Objective-C, the transfer of an object and a selector or the transfer of a block solved, in principle, the same problem. In Swift, such an API will require something with a certain received and returned values, and what exactly will be passed there: a function, a method or a closure; irrelevant.


 /*  reduce,     calculateHeight   . (Result, (Result, Element) throws -> Result) rethrows -> Result       ,    : throw  rethrows;  generic  Result    Element     Double  Stage.     ,   reduce   . (Double, (Double, Stage) -> Double) -> Double    :   Double,   - ,  Double  Stage,   Double.   ,   ,   Double. */ //          : let totalHeight = stages.reduce(0, { (accumulator: Double, stage: Stage) -> Double in return accumulator + stage.height }) /*      . -,    reduce        -,     . -,   -    ,      . */ let totalHeight = stages.reduce(0) { accumulator, stage in return accumulator + stage.height } /*    .       .        .         $0, $1     .      ,      return. */ let totalHeight = stages.reduce(0) { $0 + $1.height } /*  ,   swift     ,   ,   .     +     Double  Stage,        [Stage]     [Double],       reduce,       (Double, Double) -> Double.   Double  + ,          . */ let totalHeight = stages.map{ $0.height }.reduce(0, +) 

3. Default parameters


Parameters of functions and methods can have default values.
Using default parameters instead of several functions / methods reduces the amount of code, and less code reduces fewer bugs.


 enum Destination { case lowEarthOrbit, geostationaryEarthOrbit, transLunarInjection } class RocketFactory { /*  -         '='. */ func makeRocket(destination: Destination = Destination.lowEarthOrbit, payloadMass: Double = 6450) -> Rocket { //... } } let rocketFactory = RocketFactory() //    -    . let soyuz = rocketFactory.makeRocket() let protonM = rocketFactory.makeRocket(destination: Destination.geostationaryEarthOrbit) let saturnV = rocketFactory.makeRocket(destination: Destination.transLunarInjection, payloadMass: 48600) 

4. Optionals


No type variable can be nil . Swift uses a special type of Optional , into which other types are “wrapped” if there is a need to represent the absence of a value.


Optional are enumerations with two cases: none and some . Optional.some(Wrapped) contains the value of the wrapped type as an associated value.
Optional.none equivalent to the literal nil .
In Optional , both the reference type and the passed by value can be wrapped.


 struct Launchpad { //    optional . var rocket: Rocket? /*        : var rocket: Optional<Rocket> */ } 

In order to access the properties and methods of optional values, you first need to expand these optional values, that is, make sure that they are not nil . Of course, this can be achieved by working with optional values ​​as with ordinary enumerations, for example using a switch, but Swift has more convenient constructions for this: if let , guard let else ; operators ? ! ?? .


 /*  ,     optional  -   '!'.    optional    ,   runtime error   ,   nil. */ launchpad.rocket!.launch() func start() { /*    optional     if let. */ if let rocket = launchpad.rocket { /*  ,         optional . */ rocket.launch() } else { //    else      optional . abortStart() } } /*  ,      ,  optional   nil,   optional     guard let else. */ func start2() { /*    if let       optional    ,   else      nil    . */ guard let rocket = launchpad.rocket else { abortStart() return } rocket.launch() } /*        optional ,   .   '?'.              .       optional. */ launchpad.rocket?.launch() var possibleLaunchpad: Launchpad? //      optional . possibleLaunchpad?.rocket?.launch() possibleLaunchpad?.rocket?.stages //Return type: [Stages]? /*     optional    '??'.    .  - optional ,   -  optional   .     nil,     ,    . */ let certainRocket = possibleLaunchpad?.rocket ?? rocketFactory.makeRocket() 

Such restrictions make it difficult to hit an unexpected nil value, which makes Swift code more reliable.


5. Nested types


In Swift, you can declare nested types, that is, classes, structures, and enumerations can be declared within each other.


 /*         ,     - . */ struct Launch { enum State { case preparing, ready, launching, failed, succeeded } //          . var state: State = .preparing } /*    ,   ,      . */ let launchState: Launch.State 

Functions can also be declared inside other functions. But in fact, internal functions are closures, and they can capture the context of an external function.


6. Tuples


More new types in Swift are tuples. Tuples allow you to combine multiple values ​​of any type into one composite value. Tuples are passed by value.


 /*   ,    ,      ,    . */ var launchEventMark: (event: LaunchEvent, timeMark: Int) = (.ignitionSequenceStart, 6600) launchEventMark.event launchEventMark.timeMark // ,        . launchEventMark.0 launchEventMark.1 /*  ,          ,   ,             . */ var anotherMark: (LaunchEvent, Int) = launchEventMark anotherMark.0 anotherMark.event // error: type has no member 'event' 

7. Getters, setters and property observers


Unlike Objective-C, Swift getter and setter can only be defined for calculated properties. Of course, you can use methods or a calculated property for stored properties like getter and setter.


 //  getter'  setter'    . class ThrustController { init(minThrust: Double, maxThrust: Double, currentThrust: Double) { self.minThrust = minThrust self.maxThrust = maxThrust thrust = currentThrust } var minThrust: Double var maxThrust: Double private var _thrust = 0.0 var thrust: Double { get { return _thrust } set { if newValue > maxThrust { _thrust = maxThrust } else if newValue < minThrust { // _thrust = maxThrust } else { _thrust = newValue } } /* -,    setter'    newValue,       : */ // set(thrustInput) { ... } } } /* , ,      ,         ,     . */ 

For tasks whose solution requires tracking a change in the value of a property, a new mechanism has appeared - property observers. They can be defined for any stored property. They are of two types: willSet (called before changing the value of the property) and didSet (called immediately after setting the new value).


 protocol ThrustObserver: class { func thrustWillChange(from oldValue: Double, to newValue: Double) func thrustDidChange(from oldValue: Double, to newValue: Double) } class ThrustMeter { weak var observer: ThrustObserver? var thrust: Double = 0.0 { willSet { observer?.thrustWillChange(from: thrust, to: newValue) } didSet { observer?.thrustDidChange(from: oldValue, to: thrust) } //      set,  newValue  oldValue    . // willSet(newThrust) { ... } // didSet(oldThrust) { ... } } } 

It should be noted that for property observers structures are called not only to directly change the value of the property for which they are declared, but also to change the nested properties of any depth.


For lazy initialization, which in Objective-C can be implemented through getter, Swift has a lazy property modifier.


8. Variability of properties and collections


In Swift, type properties can be constants. Moreover, if the type of the property declared as a constant is a class, that is, the type passed by reference, then only the link itself will be immutable. That is, it will not be possible to assign a new object to this property, and it is possible to change the properties of this object. For types that are passed by value, any change will be unacceptable.


 class ThrustMeterClass { var thrust: Double init(thrust: Double) { self.thrust = thrust } } struct ThrustMeterStruct { var thrust = 0.0 } let thrustMeterClass = ThrustMeterClass(thrust: 0) thrustMeterClass = ThrustMeterClass(thrust: 50) //Error thrustMeterClass.thrust = 50 //OK let thrustMeterStruct = ThrustMeterStruct(thrust: 0) thrustMeterStruct = ThrustMeterStruct(thrust: 50) //Error thrustMeterStruct.thrust = 50 //Error 

Since all collections in Swift are structures, their variability is not determined by the type, as in Objective-C, but the way of the declaration is a constant or a variable. There are three collections in the standard library: an array, a set, and a dictionary.


 let immutableArray = [1, 2, 3] var mutableArray = [1, 2, 3] 

9. Protocols with associated types


While conventional protocols are virtually indistinguishable from analogs from Objective-C, protocols with associated types are a completely new design in Swift. Protocols can declare associated types and use them as placeholder in their requirements for methods and properties. And that which implements this protocol should already indicate which real type will be used.


 //        . protocol Fuel {} protocol Oxidizer {} //   ,   . struct Hydrazine: Fuel {} struct ChlorineTrifluoride: Oxidizer {} struct Kerosene: Fuel {} struct Oxygen: Oxidizer {} //    . protocol Bipropellant { /*           . */ associatedtype TFuel: Fuel associatedtype TOxidizer: Oxidizer func burn(_ fuel: TFuel, with oxidizer: TOxidizer) } //      . struct KoxPropellant: Bipropellant { /*  ,          ,    typealias.                  burn. */ typealias TOxidizer = Oxygen typealias TFuel = Kerosene func burn(_ fuel: Kerosene, with oxidizer: Oxygen) { print("Burn of kerosene with oxygen.") } } /*             Objective-C. */ protocol Hypergolic: Bipropellant {} struct HctPropellant: Bipropellant, Hypergolic { typealias TOxidizer = ChlorineTrifluoride typealias TFuel = Hydrazine func burn(_ fuel: Hydrazine, with oxidizer: ChlorineTrifluoride) { print("Burn of hydrazine with chlorine trifluoride.") } } 

While conventional protocols can be used as a specific type:


 struct AnyFuelTank { /*        ,   Fuel. */ var content: Fuel } var fuelTank = AnyFuelTank(content: kerosene) fuelTank.content = hydrazine 

Protocols with associated types cannot be used this way.


 struct RocketEngine { //    . var propellant: Bipropellant /* Error: Protocol 'Bipropellant' can only be used as a generic constraint because it has Self or associated type requirements */ } 

As can be seen from the error message, protocols with associated values ​​can only be used as a restriction on the generic type.


 struct RocketEngine<TBipropellant: Bipropellant> { var propellant: TBipropellant } 

Some thoughts on why this is so and how to live with it can be found here: [3] .


In general, about protocols with associated values ​​are well described in this series of articles: [4] .


10. Protocol extensions


Extensions (extensions) of classes, structures and enumerations in Swift are basically similar to categories and extensions from Objective-C, that is, they allow you to add behavior to a type, even if you do not have access to its source code. Type extensions allow you to add calculated properties, methods, initializers, subscripts, nested types and implement protocols.


Protocol extensions are a new feature in Swift. They allow the types that implement this protocol to provide the default implementation of properties and methods. That is, the protocol extension does not describe the requirements, but the specific implementation that the types that implement this protocol will receive. Of course, types can override this implementation. Such a default implementation allows replacing optional protocol requirements that exist in Swift only within the framework of compatibility with Objective-C.


 //  ,   ,    . extension Bipropellant { func burn(_ fuel: TFuel, with oxidizer: TOxidizer) { print("Common burn.") } } 

In addition, in the extension of the protocol, you can implement methods that are not in the requirements of the protocol, and these methods will also receive types that implement the protocol. But you should use protocol extensions in this way with caution due to static binding. Read more here: [5] .


Moreover, it is possible to specify protocol extensions so that not all types implementing the protocol receive a default implementation. Conditions may require that the type be inherited from a particular class or implement certain protocols. Conditions can be imposed on the type that implements the protocol and on the associated types. If different extensions provide an implementation of the same method, and the type satisfies the conditions of several extensions, then it will receive the implementation whose extension condition was more specific. If not, then the type will not receive any implementation.


 /*      ,  ,        . */ extension Bipropellant where Self.TOxidizer == Oxygen { func burn(_ fuel: TFuel, with oxidizer: TOxidizer) { print("Burn with oxygen as oxidizer.") } } /*      .    . */ extension Bipropellant where Self: Hypergolic, Self.TFuel == Hydrazine { func burn(_ fuel: TFuel, with oxidizer: TOxidizer) { print("Self-ignited burn of hydrazine.") } } 

 /*      KoxPropellant  HctPropellant     burn,         Bipropellant. */ let koxPropellant = KoxPropellant() koxPropellant.burn(kerosene, with: oxygen) // Burn of kerosene with oxygen. let hctPropelant = HctPropellant() hctPropelant.burn(hydrazine, with: chlorineTrifluoride) // Burn of hydrazine with chlorine trifluoride. //        burn,      . koxPropellant.burn(kerosene, with: oxygen) // Burn with oxygen as oxidizer. hctPropelant.burn(hydrazine, with: chlorineTrifluoride) // Self-ignited burn of hydrazine. 

In general, protocol extensions can be found in WWDC15 “Protocol-Oriented Programming in Swift” by Dave Abrahams [6]


11. Generics


Unlike Objective-C, Swift generic can include not only classes, but also structures, enumerations, and functions.


In Swift, the conditions on a generic type can be imposed on the same conditions as in Objective-C, that is, inherit from a particular class or implement certain protocols. In addition to them, if in the conditions there is a requirement to implement a protocol with associated types, then similar conditions can be imposed on them.


 //   Tank      . struct Tank<TContent> { var content: TContent } //    . struct FuelTank<TFuel: Fuel> { var content: TFuel } //       . struct OxygenEngine<TBipropellant: Bipropellant> where TBipropellant.TOxidizer == Oxygen { var fuel: TBipropellant.TFuel var oxidizer: Oxygen } 

12. Namespace


In Objective-C, to avoid name conflicts, you have to use prefixes in class names and protocols.


In Swift, each module has its own namespace, and in the case of intersection of type names, you can refer to the necessary one through the name of its module. There is no opportunity to declare your namespace to separate types within a single module.


13. Access control


Objective-C . , — .


Swift , .


Swift 3:



14.


Objective-C : NSException NSError . NSException @try , @catch , @finally ; NSError NSError* . Cocoa NSException , NSException , , , NSError .


Swift do-try-catch , NSError . finally . defer , scope, , , .


NSException , , - Objective-C, , Swift .


 protocol Technology {} /*       ,    Error.     ,       throw  catch. */ //     . enum ConstructingError: Error { case notEnoughFunding(shortage: Double) case neccessaryTehcnologyIsNotAvailable(technology: Technology) case impossibleWithModernTechnology } class ThrowingRocketFactory { var funds: Double = 0.0 func estimateCosts() -> Double { return 0.0 } /* ,    ,      throws  . */ func makeRocket(for destination: Destination, withPayloadMass payloadMass: Double) throws -> Rocket { //... if funds <= 0 { throw ConstructingError.notEnoughFunding(shortage: estimateCosts()) } //... } //... } let factory = ThrowingRocketFactory() let destination = Destination.lowEarthOrbit let payloadMass = 0.0 //          try. //   ,         . /* 1)    .   ,     ,    throws. */ func getRocket(forDestination destination: Destination, payloadMass: Double) throws -> Rocket { let rocketFactory = ThrowingRocketFactory() let rocket = try rocketFactory.makeRocket(for: destination, withPayloadMass: payloadMass) return rocket } /* 2)   .          do,    catch     . */ do { let rocket = try factory.makeRocket(for: destination, withPayloadMass: payloadMass) } catch ConstructingError.notEnoughFunding(let shortage) { print("Find money: \(shortage)") } catch ConstructingError.neccessaryTehcnologyIsNotAvailable(let technology) { print("Find alternatives for: \(technology)") } catch { print("Impossible to create such rocket.") } /* 3)       optional,     . */ if let rocket = try? factory.makeRocket(for: destination, withPayloadMass: payloadMass) { //... } /* 4)    .      ,        runtime. */ let rocket = try! factory.makeRocket(for: destination, withPayloadMass: payloadMass) 

 /*    defer ,          ,       . */ extension ThrowingRocketFactory { func openCommunications() {/* ... */} func closeCommunications() {/* ... */} } do { factory.openCommunications() defer { /*    defer       scope,   ,  do.        -        . */ factory.closeCommunications() } let rocket = try factory.makeRocket(for: destination, withPayloadMass: payloadMass) } catch { // ... } 

15.


Swift, Objective-C, , . API Unmanaged . : retain() , release() , autorelease() ; , , Swift'. : takeRetainedValue() — , takeUnretainedValue() — .


Unmanaged : [7]


16.


Swift . atomic nonatomic Objective-C. , , Grand Central Dispatch NSOperation Cocoa.


17.


Objective-C Swift . Swift (build configurations). ( -D <#flag#> ) , — os() : OSX , iOS , watchOS , tvOS , Linux ; — arch() : x86_64 , arm , arm64 , i386 ; — swift() : >= . : #if , #elseif , #else , #endif .
Apple docs.


18.


Objective-C Swift ? , runtime Swift . , OCMock Swift , NSObject .


Swift Objective-C ? , Objective-C. Swift Objective-C, NSObject . Swift .
Apple docs.


Objective-C


Objective-C [8] :


  1. runtime. , , .
  2. - , . , , , , .
  3. . , runtime .

, Cocoa . Swift , iOS, Cocoa , .


Swift open source , Apple , Cocoa. . , , , Cocoa , , - Apple Cocoa - , Swift. , Objective-C ? , , , Swift + Cocoa, Swift.


19. “Target/action” messaging


“Target/action” messaging . responder chain UI Interface Builder 1988 , .


Swift + Cocoa. responder UIResponder UIKit, , , .


Swift . responder chain runtime . [9]


20. Key-Value Coding


, .


Swift + Cocoa. Swift KVC , NSObject .


Swift?
read-only, (reflection) Mirror . [10]


21. Key-Value Observing


“” - .


Swift + Cocoa. KVO , NSObject , dynamic , .


Swift , , , , , property observers. : , Observable-Swift , , Cocoa KVO .


22. NotificationCenter


, .


Swift + Cocoa. API . ( ) , Objective-C ( NSObject @objc ). , API , - NSObjectProtocol , . Cocoa .


Swift. “” , Swift . , NS , , NotificationCenter Swift Foundation, Cocoa .


23. UndoManager


, .


Swift + Cocoa. 3 : , NSInvocation . . : , NotificationCenter : Objective-C. NSInvocation , , , , NSObject . iOS 9.


Swift. UndoManager , , NotificationCente r, Swift, UndoManager Swift Foundation.



, API , , , Swift API, .


, , Objective-C, Swift . Swift . , — -. , , Objective-C KVC, CoreData, bindings, HOM, UndoManager .. /. , Swift , . [11]


Swift


Swift? ?


1. . Swift , Objective-C. , . . : [12] [13] .


2. ? , , Swift , Objective-C. , , iOS , Cocoa.


, . Swift Bool , .


Conclusion


Swift Objective-C .


-, source stability. , 3.0, , , Swift , . , . , Availability API. [14]


-, Swift ABI . , .


, , Swift , , .


Swift 3 ABI Swift 4, 2017 . [15]


, , Swift Objective-C, , , , .



Links


1. Advanced & Practical Enum usage in Swift
2. Controlling Complexity in Swift — or — Making Friends with Value Types
3. Protocols with Associated Types
4. Swift: Associated Types
5. The Ghost of Swift Bugs Future
6. Protocol-Oriented Programming in Swift
7. Unmanaged
8. A Definition of Dynamic Programming in the Cocoa World
9. Pimp My Code, Book 2: Swift and Dynamism
10. The Swift Reflection API and what you can do with it
11. What's Missing in the Discussion about Dynamic Swift
12. The Type System is Your Friend
13. Enums as constants
14. Availability by Swift version
15. Looking back on Swift 3 and ahead to Swift 4


')

Source: https://habr.com/ru/post/308264/


All Articles