Initialize subclasses from json - json

I am looking for a good way of persisting arbitrary subclasses.
I am writing objects asDictionary to json upon save, and init(json) them back upon load. The structure is Groups that have Units of different kind. The Group only knows its units as something implementing UnitProtocol.
The subclasses UA etc of Unit has the exact same data as a Unit. So data wise, the asDictionary and init(json) fits well in Unit. The subclasses only differ in logic. So when restoring them from file, I believe it has to be the exact subclass that is initialized.
(Bad) Solutions I thought of
Letting every group know that it can have Units of different subclasses by not holding them as [UnitProtocol] only, but also as [UA], [UB], etc, that can saved separately, restored by their respective sub inits, and be merged into a [UnitProtocol] upon init.
Store subunits with their classname and create a Unit.Init(json) that somehow is able to pass down the initialization depending on subtype.
?? Still thinking, but I believe there has to be something I can learn here to do this in a maintainable way without breaking the single responsibility policy.

For init class from json I used this technic :
//For row in json
for row in json {
//namespace it use for init class
let namespace = (Bundle.main.infoDictionary!["CFBundleExecutable"] as! String).replacingOccurrences(of: " ", with: "_")
//create instance of your class
if let myClass = NSClassFromString("\(namespace).\(row.name)") as? NameProtocol.Type{
//init your class
let classInit : NameProtocol = myClass.init(myArgument: "Hey")
//classInit is up for use
}
}

Store every Unit json with its classname
func asDictionary() -> Dictionary<String, Any> {
let className = String(describing: type(of: self))
let d = Dictionary<String, Any>(
dictionaryLiteral:
("className", className),
("someUnitData", someUnitData),
// and so on, for all the data of Unit...
And init from json with #horusdunord's solution:
for unitJson in unitsJson {
let className = (unitJson as! [String: Any])["className"] as? String
let namespace = (Bundle.main.infoDictionary!["CFBundleExecutable"] as! String).replacingOccurrences(of: " ", with: "_")
if let unitSubclass = NSClassFromString("\(namespace).\(className ?? "N/A")") as? UnitProtocol.Type {
if let unit = unitSubclass.init(json: unitJson as! [String : Any]) {
units.append(unit)
} else {
return nil
}
} else {
return nil
}
}
The trick here is that casting the class unitSubclass to UnitProtocol then allows you to call its init(json) declared in that protocol but implemented in the particular subclass, or in its superclass Unit, if the properties are all the same.

Related

Kotlinx.Serialization using OkHTTPClient return always Failure

Hello I have a problem with my JSON. I am using OkHTTPClient to get JSON from web - to get objects from JSON using kotlinx.serialization via method which contains this and return value from method should be Result :
private suspend inline fun <reified T> OkHttpClient.get(webUrl: HttpUrl): Result<T> =
try {
//Builder defined here ... but skipping this line of code
val data = Json { ignoreUnknownKeys = true }.decodeFromString<T (result.body!!.string())
Result.Success(data)
} catch (e: Exception) {
Result.Failure(e)
}
suspend fun getFact(): Result<Fact> =
client.httpGet("myURL".toHttpUrl())
Json from myURL:
{"status":"success","data":[{"fact":"This is random information i need to get"}],"message":"Retrieved Fact"}
My serializer and Serializable data classes:
#Serializable
data class Fact(
#Serializable(with = FactListSerializer::class)
val data: String) java.io.Serializable
object FactListSerializer : JsonTransformingSerializer<List<String>>(ListSerializer(String.serializer())) {
override fun transformDeserialize(element: JsonElement): JsonElement {
return if (element is JsonArray) {
JsonArray(listOf(element)).first()
} else {
element
}
}
}
To be honest I am not sure what I am doing, but I am getting this error all the time when I print val fact = api.getFact():
Fact: Failure(error=kotlinx.serialization.json.internal.JsonDecodingException: Expected JsonPrimitive at 0, found {"fact":"This is random information i need to get"}
What I need to return is only first element of array fact, because JSON obtain always only 1 fact inside array. So I don't want to return from Serializer/Json List but only Fact object.
But as you see I am obtaining always Result Fauilure, don't know why. My goal is to obtain Result Success and obtaining from that JSON object Fact (only one), but I am not sure if I am doing it correct (obviously not) and even if it is even possible to return from JSONArray only one object (element of type Fact).
So what I expect is something like this:
Fact: Success(value=Fact(fact=This is random information i need to get))
I think the deserializer definition should be changed on 3 levels. The example of how to use JsonTransformingDeserializer in the docs actually describes most of what you need.
JsonArray(listOf(element)).first() should just be element.first(). Here you're building a JsonArray containing your initial JsonArray as only element, and then taking the first, so you basically get back the exact same element.
The type parameter T of JsonTransformingSerializer is supposed to be the type of the property it's applied to, so you should at least get a warning in the code because yours is defined to work on List<String> but is applied to a String property. It should be JsonTransformingSerializer<String>(String.serializer()).
You not only need to unwrap the data array, you also need to extract the value of the fact key within the element of that array.
So with all these changes, it should give something like this:
object FactListSerializer : JsonTransformingSerializer<String>(String.serializer()) {
override fun transformDeserialize(element: JsonElement): JsonElement {
val unwrappedData = if (element is JsonArray) element.first() else element
return unwrappedData.jsonObject["fact"] ?: error("missing 'fact' key in 'data' array")
}
}

How to use an init(from decoder:) when reading bundled JSON data

I have have a JSON dictionary array and need to decode it to make a data type (Hitter), the Hitter object needs to hold the raw JSON data and I need to add properties to the Hitter throughout the life of the app. I've discovered I need to use init(from decoder:) but how do I call the init properly...I will be storing the Hitter in the League struct.
struct League {
var Hitters: Set<Hitter>
func loadHitters() {
...//ingest json from bundle,
//store decoded Hitter objects in temp array,
//then append each item from temp array into League.Hitters set.
}
}
To be clear, my Hitter struct already has the init set up, I need help using the decoder from this point.
EDIT:
I've discovered the solution and it requires the use of retrieving the JSON keys and linking them to your CodingKeys enum. Here's my shortened Hitter class:
struct Hitter: Player, Decodable {
//partial list of properties in JSON data
let strPos: String
let OBP: Float
let wRAA: Float //weightedRunsAboveAverage
//partial list of properties in JSON data - end
//partial list of additional properties
var valAboveReplacement: Float = 0.0
var wOBP: Float {
return OBP * Float(PA)
}
//partial list of additional properties - end
}
I declare the CodingKeys and init(decoder:) in an extension for compartmentalization's sake
extension Hitter {
enum CodingKeys: String, CodingKey {
case strPos, OBP, wRAA
}
convenience init(from decoder: Decoder) {
//container links all the CodingKeys and JSONDecoder keys for proper referencing. Returns the data stored in this decoder as represented in a container keyed by the given key type
let container = try decoder.container(keyedBy: CodingKeys.self)
let strPos = try container.decode(String.self, forKey: .strPos)
let OBP = try container.decode(Float.self, forKey: .OBP)
let wRAA = try container.decode(Float.self, forKey: .wRAA)
//pass my decoded values via a standard initializer
self.init(strPos: strPos, OBP: OBP, wRAA: wRAA)
}
}
This seems to work perfectly fine so long as I explicitly link the JSON format and the CodingKeys via a container.
If you design a struct, you get an memberwise initializer for free — assuming you don't define others initializers yourself But Struct auto inititializers are internal. You have to generate memeberwise initializer when you writing a module to make it public
"Default Memberwise Initializers for Structure Types The default
memberwise initializer for a structure type is considered private if
any of the structure’s stored properties are private. Otherwise, the
initializer has an access level of internal.
As with the default initializer above, if you want a public structure
type to be initializable with a memberwise initializer when used in
another module, you must provide a public memberwise initializer
yourself as part of the type’s definition."
Swift Docs "Swift Programming Language"

Do all attributes with a custom type in core-data have to be a relationship?

This is a followup question from the comments in the following How to map nested complex JSON objects and save them to core data?.
Imagine that I already have this code for my app.
class Passenger{
var name: String
var number: String
var image : UIImage
// init method
}
class Trip {
let tripNumber : Int
let passenger : Passenger
init(tripNumber: Int, passenger: Passenger) {
self.tripNumber = tripNumber
self.passenger = passenger
}
}
Now I've decided to add persistence for my app. I just want to have a table of Trips. I want to show the passengers under trips, but don't need a table to query passengers directly. It's just a custom object/property of trip. Every time I access passenger it would be through Trips.
So is there a way that I can create a new subclass of NSManagedObject named 'TripEntity' and store my passengers — WITHOUT 1. creating another NSManagedObject subclass for 'Passenger' 2. Creating a relationship with an inverse relationship between Passenger and Trip? Simply put I just want it to be an attribute. Conceptually to me it's also just an attribute. It's not really a relationship...
Or is that once you're using Core-data then every custom type needs to be explicitly a subclass of NSManagedObject? Otherwise it won't get persisted. I'm guessing this also what object graph means. That your graph needs to be complete. Anything outside the graph is ignored...
I'm asking this because the JSON object that I actually want to store is gigantic and I'm trying to reduce the work needed to be done.
You can add passengers to one Trip entity but as the attribute types are restricted you have to use a transformable type which can be quite expensive to archive and unarchive the objects.
The most efficient way if the source data is JSON is to create Core Data entities for Passenger and Trip and add inverse relationships. Then make all NSManagedObject classes adopt Codable and add init(from decoder and encode(to encoder: methods to each class.
For example let's assume that the Trip class has a to-many relationship to Passenger it could look like
#NSManaged public var tripNumber: Int32
#NSManaged public var passengers: Set<Passenger>
and in the Passenger class there is an attribute trip
#NSManaged public var trip: Trip?
this is the required Decodable initializer in Trip. The decoder can decode arrays as well as sets.
private enum CodingKeys: String, CodingKey { case tripNumber, passengers }
public required convenience init(from decoder: Decoder) throws {
guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError("Context Error") }
let entity = NSEntityDescription.entity(forEntityName: "Trip", in: context)!
self.init(entity: entity, insertInto: context)
let values = try decoder.container(keyedBy: CodingKeys.self)
tripNumber = try values.decode(Int32.self, forKey: .tripNumber)
passengers = try values.decode(Set<Passenger>.self, forKey: .passengers)
passengers.forEach{ $0.trip = self }
}
The inverse relationship is set via the forEach line.
You have to add an extension of JSONDecoder to be able to pass the current NSManagedObjectContext in the userInfo object.
The corresponding encoder is – pretty straightforward –
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(tripNumber, forKey: .tripNumber)
try container.encode(passengers, forKey: .passengers)
}
NSManagedObject classes adopting Codable are very comfortable for pre-populating the database from JSON data or making JSON backups.
Any custom property needs to be something that can be represented as one of the Core Data property types. That includes obvious things like strings and numeric values. It also includes "binary", which is anything that can be transformed to/from NSData (Data in Swift).
From your description, the best approach is probably to
Adopt NSCoding for your Passenger class.
Make the passenger property a Core Data "transformable" type. (as an aside, should this be an array of passengers? You have a single related passenger, but your question describes it as if there's more than one).
After doing #2, set the "custom class" field for the passenger property to be the name of your class-- Passenger.
If you do these two things, Core Data will automatically invoke the NSCoding methods to convert between your Passenger class and a binary blob that can be saved in Core Data.

Swift Exceptions to Exception handling

After perusing through forums and Swift documentation (not completely, I admit), it appears that instead of try-catch mechanisms, in Swift we are encouraged to write code that is more safe from exceptions. In light of that, I have a question about a sample API, and would like to learn how to more safely handle this situation:
For example, I can create the following class using the NSDecimalNumberHandler:
class MathWhiz {
init() {
let defaultBehavior: NSDecimalNumberHandler =
NSDecimalNumberHandler.defaultDecimalNumberHandler()
}
func add(op1: String, op2: String) ->NSDecimalNumber {
return NSDecimalNumber.decimalNumberWithString(op1).decimalNumberByAdding(NSDecimalNumber.decimalNumberWithString(op2))
}
}
If I use the following, I get a number:
let brain = MathWhiz()
brain.add("1", op2: "1e127")
However, if I cause an overflow exception,:
brain.add("1", op2: "1e128")
I will crash the program as expected.
So, my question is, the API raises exceptions, but I don't handle them here. There are other posts out there of people pointing out that Swift does not have exception handling, but this question is seeking a nice way to handle this problem in the way that the language creators were thinking it should be done.
Is there a recommended way to handle this without having to write my own code to check for overflow, underflow, loss of precision, ect...? I am wanting the NSDecimalNumberHandler to do that for me.
If you are designing a function (or method) in Swift, you have at least 3 choices for dealing with errors:
Choice 1: Return an Optional Type
If your function might fail, and this happens on a regular basis, then consider returning an optional type variable. For example, in your case, your method add could return an NSDecimalNumber? instead of a plain NSDecimalNumber. In that case, your method would check for everything that could go wrong, and return nil in those situations. Overflow and underflow would return nil, and all other cases would return an NSDecimalNumber. The callers would have to check for and unwrap the optional NSDecimalNumber like this:
let brain = MathWhiz()
if let sum = brain.add("1", op2: "1e127") {
println("the result was \(sum)")
} else
println("something went wrong with MathWhiz add")
}
Choice 2: Return an Enumerated Type
If you want to return more information about the thing that went wrong, you could create an enumerated type with a value for each error and one for success that embeds the answer. For example, you could do:
enum MathWhizResult {
case Overflow
case Underflow
case Success(NSDecimalNumber)
}
Then add would be defined to return MathWhizResult:
func add(op1: String, op2: String) -> MathWhizResult
In the case of an error, add would return .Overflow or .Underflow. In the case of success, add would return Success(result). The caller would have to check the enumeration and unpack the result. A switch could be used for this:
switch (brain.add("1", op2: "1e128")) {
case .Overflow
println("darn, it overflowed")
case .Underflow
println("underflow condition happened")
case .Success(let answer)
println("the result was \(answer)"
}
Choice 3: Choose not to handle errors explicitly
Unpacking the result in the first two choices might be too much overhead for an error that is very rare. You could chose to just return a result, and let the caller deal with the possibility of an underflow or overflow condition. In that case, they would have to check for these conditions themselves before calling add. The benefit is, it they know that their program will never cause an underflow or overflow (because they are dealing with single digit numbers for instance), they are not burdened with unpacking the result.
I created a small app to demonstrate how you could do this with NSDecimalNumbers. I created a Single View Application in Xcode. In the ViewController in the StoryBoard I added 3 TextFields (one each for operand 1, operand 2, and the result) and a Button that I labelled +.
ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet var operand1 : UITextField!
#IBOutlet var operand2 : UITextField!
#IBOutlet var result : UITextField!
var brain = MathWhiz()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func addButton(sender : UIButton) {
var op1 = operand1.text
var op2 = operand2.text
// Perform the add with the contents of the operand fields.
// Print the answer, or "No Result" if add returns nil.
if let answer = brain.add(op1, op2: op2)?.description {
result.text = answer
} else {
result.text = "No Result"
}
}
}
MathWhiz.swift
import UIKit
// Declare that we implement NSDecimalNumberBehaviors so that we can handle
// exceptions without them being raised.
class MathWhiz: NSDecimalNumberBehaviors {
var badException = false
// Required function of NSDecimalNumberBehaviors protocol
func roundingMode() -> NSRoundingMode {
return .RoundPlain
}
// Required function of NSDecimalNumberBehaviors protocol
func scale() -> CShort {
return CShort(NSDecimalNoScale)
}
// Required function of NSDecimalNumberBehaviors protocol
// Here we process the exceptions
func exceptionDuringOperation(operation: Selector, error: NSCalculationError, leftOperand: NSDecimalNumber, rightOperand: NSDecimalNumber) -> NSDecimalNumber? {
var errorstr = ""
switch(error) {
case .NoError:
errorstr = "NoError"
case .LossOfPrecision:
errorstr = "LossOfPrecision"
case .Underflow:
errorstr = "Underflow"
badException = true
case .Overflow:
errorstr = "Overflow"
badException = true
case .DivideByZero:
errorstr = "DivideByZero"
badException = true
}
println("Exception called for operation \(operation) -> \(errorstr)")
return nil
}
// Add two numbers represented by the strings op1 and op2. Return nil
// if a bad exception occurs.
func add(op1: String, op2: String) -> NSDecimalNumber? {
let dn1 = NSDecimalNumber(string: op1)
let dn2 = NSDecimalNumber(string: op2)
// Init badException to false. It will be set to true if an
// overflow, underflow, or divide by zero exception occur.
badException = false
// Add the NSDecimalNumbers, passing ourselves as the implementor
// of the NSDecimalNumbersBehaviors protocol.
let dn3 = dn1.decimalNumberByAdding(dn2, withBehavior: self)
// Return nil if a bad exception happened, otherwise return the result
// of the add.
return badException ? nil : dn3
}
}
Well, you're using an ObjC API. So just handle the exceptions inside ObjC. Write an ObjC class that handles the exceptions and returns values for Swift code to consume.
One possibility would be to write MathWhiz as an ObjC class and return an inout NSError parameter (ie do it the way Core Data does, take an **NSError) and fill it with the proper value when you hit a recoverable error. Then you can eat exceptions from NSDecimalNumber and convert them into NSError values.
You could also write an entire NSDecimalNumber wrapper for Swift consumption that you then use in place of NSDecimalNumber in your Swift code. Perhaps you could overload operator + and its siblings for this class, and then work out how to represent the various possible errors without exceptions.
update answer for swift 2.0
As you mentioned, now Swift 2.0 support with try, throw, and catch keywords.
here is official announcement.
Error handling model: The new error handling model in Swift 2.0 will
instantly feel natural, with familiar try, throw, and catch keywords.
Best of all, it was designed to work perfectly with the Apple SDKs and
NSError. In fact, NSError conforms to a Swift’s ErrorType. You’ll
definitely want to watch the WWDC session on What’s New in Swift to
hear more about it.
e.g
func loadData() throws { }
func test() {
do {
try loadData()
} catch {
print(error)
}
}

What are the differences between functions and methods in Swift?

I always thought functions and methods were the same, until I was learning Swift through the "Swift Programming Language" eBook. I found out that I cannot use greet("John", "Tuesday") to call a function that I declared inside a class, as shown in the eBook in the screen shot below:
I received a error saying that "Missing argument label 'day:' in call" as per this screen shot:
Here is the code:-
import Foundation
import UIKit
class ViewController2: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//var dailyStatement = greet("John", "Tuesday")
var dailyStatement = greet("John", day: "Tuesday")
println(dailyStatement)
}
func greet(name: String, day: String) -> String {
return "Hello \(name), today is \(day)."
}
}
After some research, I found this post: Difference between a method and a function, and it seems to me that the function that I declared inside a class is actually called a method. So, the syntax that I use to call the method is different compared to the syntax that I use to call a function.
I never realized this difference when I was programming in Objective-C.
What are the differences between functions and methods in Swift?
When do we use functions and when do we use methods in Swift?
After a few hours of reading and experimenting, here are the things that I found out:-
Functions in Swift
Functions are self-contained chunks of code that perform a specific
task. You give a function a name that identifies what it does, and
this name is used to “call” the function to perform its task when
needed.
Resource: Official Apple Documentation on Functions in Swift
Function Parameter Names
However, these parameter names are only used within the body of the
function itself, and cannot be used when calling the function. These
kinds of parameter names are known as local parameter names, because
they are only available for use within the function’s body.
It means that by default, all the parameters for Function are local parameters.
But, sometimes we want to indicate the purpose of each parameter. So, we can actually define an external parameter name for each parameter. Example Code:
func someFunction(externalParameterName localParameterName: Int) {
// function body goes here, and can use localParameterName
// to refer to the argument value for that parameter
}
Another way to make the external parameter name is using hash symbol (#) to shorten the name.
func someFunction(#localParameterName: Int) {
// function body goes here, and can use localParameterName
// to refer to the argument value for that parameter
}
To call the above functions with external parameter, you may use
someFunction(localParameterName:10)
Methods in Swift
Methods are functions that are associated with a particular type.
Classes, structures, and enumerations can all define instance methods,
which encapsulate specific tasks and functionality for working with an
instance of a given type.
Resource: Official Apple Documentation on Methods in Swift
However, the default behavior of local names and external names is
different for functions and methods.
Specifically, Swift gives the first parameter name in a method a local
parameter name by default, and gives the second and subsequent
parameter names both local and external parameter names by default.
Code below shows the differences for default and non-default parameters for method in Swift.
import Foundation
import UIKit
class ViewController2: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Default methods calling
var dailyStatement = greet("Rick", day: "Tuesday")
println(dailyStatement)
//First parameter is also an external parameter
var dailyStatement2 = greet2(name:"John", day: "Sunday")
println(dailyStatement2)
}
//Default: First Parameter is the local parameter, the rest are external parameters
func greet (name: String, day: String) -> String {
return "Hello \(name), today is \(day)."
}
//Use Hash symbol to make the First parameter as external parameter
func greet2 (#name: String, day: String) -> String {
return "Hello \(name), today is \(day)."
}
}
I might miss some important details. Hope someone can provide a better answer.
As you said yourself, methods are functions, but in a class. In objective-c you never realized this, because we were only coding in classes. Every function that we wrote was a method of a class (ViewController or some other class we created).
In Swift we have the ability to create functions that are not inside some class. The main reason for doing this is to write functions that are not tied to any class, and can be used wherever we need them. So if you have a function that is related to a class you write it inside the class and you can access is from every instance of the class:
class Square {
var length: Double
func area() -> Double {
return length * length
}
}
But if you need to access the function from everywhere, then you don't write it inside a class. For example:
func squared(number: Int) -> Int {
return number * number
}
About your syntax issues between functions and methods: You guessed it right, methods and functions are called a little bit differently. That is because in Objective-C we had long method names and we liked them because we could read what the methods were doing and what the parameters were for. So the first parameter in a method is in most cases described by the function name itself. And the other parameters shouldn't only be some numbers or strings or instances, they should be described as well, so Swift writes the name of the variable automatically. If you want to describe it by yourself you can do that as well:
class Something {
func desc(firstString string1: String, secondString string2:String) {...}
}
Well, #Ricky's answer says it pretty much. I was confused what exactly they are. So here is my thought:
Functions could be defined outside of classes or inside of classes/structs/enums, while Methods have to be defined inside of and part of classes/structs/enums.
We could define a Function outside of any Type's definition and could use it within Methods of any Type's definition.
Just my understanding and illustration here, hope this helps someone else or you may edit if you feel there is an improvement needed OR let me know if anything is wrong:
//This is a Function which prints a greeting message based on the category defined in an 'enum'
func greet(yourName name: String, category: GreetingsCategory) {
switch category {
case .Person:
print("Hello, " + name + " Today is Tuesday")
case .Vehicle:
print("Hello, " + name + " your Vehicle is a Car")
}
}
//This is an 'enum' for greetings categories
enum GreetingsCategory: String {
case Person
case Vehicle
}
//Type: Person
class Person {
//This is a method which acts only on Person type
func personGreeting() {
greet(yourName: "Santosh", category: .Person)
}
}
//Type: Vehicle
class Vehicle {
//This is a method which acts only on Vehicle type
func vehicleGreeting() {
greet(yourName: "Santosh", category: .Vehicle)
}
}
//Now making use of our Function defined above by calling methods of defferent types.
let aPerson = Person()
aPerson.personGreeting()
//prints : Hello, Santosh Today is Tuesday
let aVehicle = Vehicle()
aVehicle.vehicleGreeting()
//prints: Hello, Santosh your Vehicle is a Car
//We can also call the above function directly
greet(yourName: "Santosh", category: .Person)
Mainly the names are used interchangeably without people having a real intent of distinguishing them. But ultimately they do have a difference.
someFile.swift:
func someFunc{
//some code
}
class someClass{
func someMethod{
//some code
}
}
Note: someClass != someFile
someMethod works only on its associated type which is 'someClass'. However the same can't be said for someFunc. someFunc is only in the someClass.Swift because semantically it is better suited to be written in that file. It could have been written in any other class as long as it's marked with private
And obviously the method can access self. With functions, there is no self.. For more see: What's the difference between a method and a function?
Here is a simple answer on the difference between functions and methods:
Some folks use “function” and “method” interchangeably, but there’s a
small difference: both of them are reusable chunks of code, but
methods belong to classes, structs, and enums, whereas functions do
not.
So:
func thisIsAFunction() {
}
struct Person {
func thisIsAMethod() {
}
}
Because methods always belong to a data type, they have a concept of
self that functions do not.
source: https://www.hackingwithswift.com/example-code/language/whats-the-difference-between-a-function-and-a-method
Lots of great answers, but let me use Xcode to show something visually from the UIKit module:
That is a function because it's written at the global level. It's not a method. Methods are scoped to a class.
Screenshot to show that it's at the global level.
The following function is at the global level:
public func UIApplicationMain(_ argc: Int32, _ argv: UnsafeMutablePointer<UnsafeMutablePointer<Int8>>!,
_ principalClassName: String?, _ delegateClassName: String?) -> Int32
Icons for the different symbols. (Class, Method, Property, Protocol, Function, Extensions are all different symbols)
The function has an icon like 𝓯
The method has an icon of M
functional principle as a part of functional language
function is a first-class type (first-class citizen) in Swift. Higher order functions
assign to a variable
pass as an argument
return
Function
Function is a block of code that is created for executing some task. Function consists of name, optional parameters(name, type), optional return type, body.
func name(parameterName1: Int, parameterName2: String) -> Bool {
//statements
return true
}
Function type - function’s parameter type + return type[Java about]. For example it is used as function's parameter
//Function type for the sample above
(Int, String) -> Bool
Method
Method - is a function which is associated with a type - class, structure, enum [About]:
Instance method - method which belongs to instance
MyClass().foo()
Type method - method which belongs to type itself. class or static is used[About]
MyClass.foo()
Closure
As official doc says that Closure in Swift has three next forms:
global function(with name, without capturing) - is a function that is declared in a global scope(out of class scope). Usually it is defined as a first level of .swift file and does not have a big memory foot print
nested function(with name, with capturing enclosing function variables) - function inside other function
closure expression(without name, with capturing enclosing context)
Closure(closure expression) - anonymous function - is a block of code(functionality). Closure is a type of function without name. Closure is a function in terms of Functional programming. It can support capturing concept(if it doesn't capture it is lambda). It is similar to block in Objective-C.
[Closure vs Lambda]
They can be used for:
non-escaping closure - sync operations - click events, sort...
escaping closure - async operations - e.g.completion handler - it is a callback/notification which is called when task is done
class ClassA {
var variable = "Hello"
func fooA() {
print(variable)
let b = ClassB() //1
b.fooB(completionHandler: { [weak self] (input) -> String? in //2 pass closure //3 capture list or any calls from closure to outher scope
guard let self = self else { return nil }
self.variable = "World" //capturing self.variable
return String(input)
})
}
}
class ClassB {
var myCompletionHandler: ((Int) -> String?)? = nil //5
func fooB(completionHandler: #escaping (Int) -> String?) { //4
self.myCompletionHandler = completionHandler //6
let result = completionHandler(7)
}
func fooB2(completionHandler: #escaping (Int) -> String?) { //if you re-pass function to #escaping function you must mark it by #escaping too
self.fooB(completionHandler: completionHandler)
}
}
func testClosure() {
ClassA().fooA()
}
(Int) -> String? //Function type
//Closure Expression
{ [weak self] (input) -> String? in
//logic
}
[non-escaping vs escaping closure]
[#autoclosure]
[Init/customize stored property by closure]
[JVM Memory model]