How to extend a function of a protocol in swift 3? - swift3

I have following code:
protocol Protocol_1 {
var prop_1: String { get set }
var prop_2: Int { get set }
}
protocol Protocol_2 {
var prop_3: Double { get set }
var prop_4: Bool { get set }
}
extension Protocol_1 {
mutating func set<T>(value: T, forKey key: String)
{
switch key {
case "prop_1":
if value is String {
prop_1 = value as! String
}
case "prop_2":
if value is Int {
prop_2 = value as! Int
}
default:
break
}
}
}
extension Protocol_2 {
mutating func set<T>(value: T, forKey key: String)
{
switch key {
case "prop_3":
if value is Double {
prop_3 = value as! Double
}
case "prop_4":
if value is Bool {
prop_4 = value as! Bool
}
default:
break
}
}
}
struct MyStruct : Protocol_1, Protocol_2 {
var prop_1: String
var prop_2: Int
var prop_3: Double
var prop_4: Bool
}
var myStruct = MyStruct(prop_1: "hello", prop_2: 0, prop_3: 3.5, prop_4: true)
myStruct.set(value: "bye", forKey: "prop_1")
Now the playground gives me an error because it is not clear what set function should be called. Playground execution failed: error: ProtocolsPlayground.playground:59:1: error: ambiguous use of 'set(value:forKey:)'
myStruct.set(value: "bye", forKey: "prop_1")
This is clear but how can I achieve such function extension or is there a work around? Especially if the Protocol_1 is not editable.

I don't know of any way to extend an existing function or another protocol's function using a protocol.
What I would suggest is that you separate the mechanism that assigns properties by name from the two protocols using a third protocol that does that specifically and separately.
Here's one way to approach this:
Define a class that will handle getting and setting properties based on a mapping between names (keys) and variable references:
class PropertyMapper
{
static var sharedGetter = PropertyMapperGet()
static var sharedSetter = PropertyMapperSet()
var value : Any = 0
var success = false
var key = ""
func map<T>(_ key:String, _ variable:inout T) {}
func clear()
{
value = 0
success = false
key = ""
}
}
class PropertyMapperGet:PropertyMapper
{
func get(forKey:String)
{
key = forKey
success = false
}
override func map<T>(_ key:String, _ variable:inout T)
{
if !success && self.key == key
{
value = variable
success = true
}
}
}
class PropertyMapperSet:PropertyMapper
{
override func map<T>(_ key:String, _ variable:inout T)
{
if !success && self.key == key,
let newValue = value as? T
{
variable = newValue
success = true
}
}
func set(value newValue:Any, forKey:String)
{
key = forKey
value = newValue
success = false
}
}
Then, you can define a protocol for all struct and classes that will have the ability to assign their properties by name (key):
protocol MappedProperties
{
mutating func mapProperties(_ :PropertyMapper)
}
extension MappedProperties
{
mutating func get(_ key:String) -> Any?
{
let mapper = PropertyMapper.sharedGetter
defer { mapper.clear() }
mapper.get(forKey:key)
mapProperties(mapper)
return mapper.success ? mapper.value : nil
}
#discardableResult
mutating func set(value:Any, forKey key:String) -> Bool
{
let mapper = PropertyMapper.sharedSetter
defer { mapper.clear() }
mapper.set(value:value, forKey:key)
mapProperties(mapper)
return mapper.success
}
}
Your protocols can require that the struct that adopt them offer the named assignments. (see farther down for making the property mapping part of your protocol)
protocol Protocol_1:MappedProperties
{
var prop_1: String { get set }
var prop_2: Int { get set }
}
protocol Protocol_2:MappedProperties
{
var prop_3: Double { get set }
var prop_4: Bool { get set }
}
Your struct will need to implement the property mapping in order to adopt your protocols. The key/variable mapping is performed in a centralized function for the whole struct and needs to provide keys for all the variables in both protocols.
struct MyStruct : Protocol_1, Protocol_2
{
var prop_1: String
var prop_2: Int
var prop_3: Double
var prop_4: Bool
mutating func mapProperties(_ mapper:PropertyMapper)
{
mapper.map("prop_1", &prop_1)
mapper.map("prop_2", &prop_2)
mapper.map("prop_3", &prop_3)
mapper.map("prop_4", &prop_4)
}
}
This allows the struct to assign properties by name (key):
var myStruct = MyStruct(prop_1: "hello", prop_2: 0, prop_3: 3.5, prop_4: true)
myStruct.set(value: "bye", forKey: "prop_1")
To refine this further and make the property mapping part of your protocols, you can add a mapping function to the protocol so that the structs that adopt it don't have to know the details of this mapping.
They will still need to implement the mapping protocol's function but they can simply use functions provided by your protocols to do the job.
For example( I only showed Protocol_1 but you can do it with both of them):
extension Protocol_1
{
mutating func mapProtocol_1(_ mapper:PropertyMapper)
{
mapper.map("prop_1", &prop_1)
mapper.map("prop_2", &prop_2)
}
}
With the function provided by the protocol, the struct doesn't need to know which properties are mapped. This should make maintenance of the structs and protocols less error prone and avoid duplications.
struct MyStruct : Protocol_1, Protocol_2
{
var prop_1: String
var prop_2: Int
var prop_3: Double
var prop_4: Bool
mutating func mapProperties(_ mapper:PropertyMapper)
{
mapProtocol_1(mapper)
mapper.map("prop_3", &prop_3)
mapper.map("prop_4", &prop_4)
}
}

Related

Update a View after an in app purchase was done

In the meantime a try to implement in app purchases in my app that is in the AppStore.
So I done this with Glassfy and everything works fine.
final class IAPManager {
static let shared = IAPManager()
private init() {}
enum Product: String {
case comfirstmember
var sku: String {
"numberOne"
}
}
#AppStorage("boughtFirst") var boughtFirst = false
func configure() {
Glassfy.initialize(apiKey: "31876r5654fgz4f95f0e6e6bh5d")
}
func getProduct(completion: #escaping (Glassfy.Sku) -> Void) {
Glassfy.sku(id: "numberOne") { sku, error in
guard let sku = sku, error == nil else {
return
}
completion(sku)
}
}
func purchase(sku: Glassfy.Sku) {
Glassfy.purchase(sku: sku) { transaction, error in
guard let t = transaction, error == nil else {
return
}
if t.permissions["numberOne"]?.isValid == true {
NotificationCenter.default.post(
name: Notification.Name("numberOne"),
object: nil
)
self.boughtFirst = true
}
}
}
func getPermissions() {
Glassfy.permissions { permissions, error in
guard let permissions = permissions, error == nil else {
return
}
if permissions["numberOne"]?.isValid == true {
NotificationCenter.default.post(
name: Notification.Name("numberOne"),
object: nil
)
self.boughtFirst = true
}
}
}
func restorePurchases() {
Glassfy.restorePurchases { permissions, error in
guard let permissions = permissions, error == nil else {
return
}
if permissions["numberOne"]?.isValid == true {
NotificationCenter.default.post(
name: Notification.Name("numberOne"),
object: nil
)
self.boughtFirst = true
}
}
}
}
But now I need to update a View after the purchase was successfully done buy the user to display the Content that he purchased.
NavigationView {
VStack {
if boughtFirst == false {
BuyExtraContent()
}
if boughtFirst == true {
AllView()
}
}
}
I want do this as easy as possible just with AppStorage.
But if I place the Boolean in the func purchase to switch from false to true nothings changes.
So my question is how I can update a view after a successful purchase.
P.S. I´m a bit lost in SwiftUI so please explain it not to complex :)
use a state variable for this .
you used boughtFirst as a bool variable. make sure it's an observable .
try to call with observable object and when your variable will change the view will automatically notify and update by itself.
I am not sure you followed this strategy or not.

raw Value of optional type not unwrapped

I am trying to unwrap the raw value of an enum type in func toDictionary() , but I get an error.
How can I fix this?
enum ChatFeatureType: String {
case tenants
case leaseholders
case residents
}
class Chat {
var featureType: ChatFeatureType?
init(featureType: ChatFeatureType? = nil
self.featureType = featureType
}
//download data from firebase
init(dictionary : [String : Any]) {
featureType = ChatFeatureType(rawValue: dictionary["featureType"] as! String)!
}
func toDictionary() -> [String : Any] {
var someDict = [String : Any]()
// I get error on the line below: Value of optional type 'ChatFeatureType?' not unwrapped; did you mean to use '!' or '?'?
someDict["featureType"] = featureType.rawValue ?? ""
}
}
As featureType is an optional you have to add ? or ! as the error says
someDict["featureType"] = featureType?.rawValue ?? ""
But be aware that your code reliably crashes when you create an instance of Chat from a dictionary and the key does not exist because there is no case "".
Actually the purpose of an enum is that the value is always one of the cases. If you need an unspecified case add none or unknown or similar.
This is a safe version
enum ChatFeatureType: String {
case none, tenants, leaseholders, residents
}
class Chat {
var featureType: ChatFeatureType
init(featureType: ChatFeatureType = .none)
self.featureType = featureType
}
//download data from firebase
init(dictionary : [String : Any]) {
featureType = ChatFeatureType(rawValue: dictionary["featureType"] as? String) ?? .none
}
func toDictionary() -> [String : Any] {
var someDict = [String : Any]()
someDict["featureType"] = featureType.rawValue
return someDict
}
}

Getting ambiguous reference to member subscript

I am updating my code to swift3.0 but getting ambiguous refrence to member? What wrong i might be doing. Here is the method I am getting error in.
open class func parseJsonTenantList(_ list: [NSDictionary]?, strElementName: String, attrName1: String, attrNameValue2: String) -> [TenantRegister]
{
var renantList: [TenantRegister] = []
var key: String?
if let dict : [NSDictionary] = list {
var value: String?
for i in 0..<dict.count {
/// if attribute name doesn't match then it returns nil
if let s1: AnyObject = dict[i].value(forKey: attrName1)
{
key = s1 as? String
}
if let s2: AnyObject = dict[i].value(forKey: attrNameValue2)
{
value = s2 as? String
}
if (!(String.stringIsNilOrEmpty(value) && String.stringIsNilOrEmpty(key)))
{
let t: TenantRegister = TenantRegister()
t.name = key
t.tenantId = Guid(value!)
renantList.append(t)
}
}
}
return renantList
}
The issue is you are using NSDictionary, to solved your problem simply cast the list to Swift's native type [[String:Any]] and then use subscript with it instead of value(forKey:)
if let dict = list as? [[String:Any]] {
var value: String?
for i in 0..<dict.count {
/// if attribute name doesn't match then it returns nil
if let s1 = dict[i][attrName1] as? String
{
key = s1
}
if let s2 = dict[i][attrNameValue2] as? String
{
value = s2
}
if (!(String.stringIsNilOrEmpty(value) && String.stringIsNilOrEmpty(key)))
{
let t: TenantRegister = TenantRegister()
t.name = key
t.tenantId = Guid(value!)
renantList.append(t)
}
}
}
In Swift use native type Dictionary [:] and Array [] instead of NSDictionary and NSArray to overcome this type of issues.

Swift 3 Variable self-removes

I have very-very strange things.
In my simple function I create variable which contains dictionary of settings parameters. It is set as 'let', so inner loop just reads it.
In a random moment of loop time it crashes with "unresolved settings".
It seems like smth makes it nil. Who does it?
private static func preferencesFilter(userIDs: [Int], access: String) -> [User] {
self.sharedInstance.delegate?.updateActionLabel(label: "Filter")
var result = [VKUser]()
let settings = self.parseSettings()
let progressFraction = 1.00 / Float(userIDs.count)
var n = 0
for userID in userIDs {
if sharedInstance.stopped {
return []
}
n += 1
let user = VKUser.getUser(id: userID, access_token: access_token)
if settings["gender"] != nil {
if user.sex == settings["gender"] as! String {
if (user.born?.isBetweeen(date1: settings["minAge"] as! Date, date2: settings["maxAge"] as! Date))! {
if settings["country"] != nil {
if user.country == settings["country"] as! String {
result.append(user)
}
}
else {
result.append(user)
}
}
}
}
else {
if (user.born?.isBetweeen(date1: settings["minAge"] as! Date, date2: settings["maxAge"] as! Date))! {
if settings["country"] != nil {
if user.country == settings["country"] as! String {
result.append(user)
}
}
else {
result.append(user)
}
}
}
self.sharedInstance.delegate?.updateProgress(value: Float(n) * progressFraction)
}
return result
}
I refactored your code into something more swift like:
private static func preferencesFilter(userIDs: [Int], access_token: String) -> [User]? {
guard userIDs.count > 0 else {
return [User]() // no input, return empty list
}
let settings = self.parseSettings()
guard let minAge = settings["minAge"] as? Date,
let maxAge = settings["maxAge"] as? Date
else {
return nil
}
let country = settings["country"] as? String // specified or nil
let gender = settings["gender"] as? String // specified or nil
sharedInstance.delegate?.updateActionLabel(label: "Filter")
var result = [VKUser]()
let progressFraction = 1.00 / Float(userIDs.count)
var n = 0
for userID in userIDs {
if !sharedInstance.stopped {
n += 1
let user = VKUser.getUser(id: userID, access_token: access_token)
var shouldInclude = true
if user.sex != gender { // wrong sex or no required gender specified
shouldInclude = false
}
if user.country != country { // wrong country or no required country specified
shouldInclude = false
}
if let born = user.born {
if !born.isBetweeen(date1: minAge, date2: maxAge) {
shouldInclude = false
}
} else { // no user.born date, cant check if in range
shouldInclude = false
}
if shouldInclude {
result.append(user)
}
sharedInstance.delegate?.updateProgress(value: Float(n) * progressFraction)
}
}
return result
}
Is that what you intended to write? How is that running for you?
Can you change this into a non-static method? Makes more sense to me.
You can see it returns an optional now, since the method might fail with a nil. Your calling code should handle that correctly.

Reading objects from webservice actionscript

I need to read objects and save them in array. I did that on c# but can't figure out how to do that on actionscript.
c# example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TestingWSDLLOad.ServiceReference2;
namespace TestingWSDLLOad
{
class Program
{
static void Main(string[] args)
{
ServiceReference2.Service1Client testas = new ServiceReference2.Service1Client();
SortedList<int, PlayListItem> playList = new SortedList<int, PlayListItem>();
int cc = 0;
foreach (var resultas in testas.GetPlayList(394570))
{
PlayListItem ss = new PlayListItem(resultas.Id, resultas.VideoId, resultas.Path);
playList.Add(cc, ss);
cc++;
}
Console.WriteLine(playList[0].Id);
Console.ReadKey();
}
}
public class PlayListItem
{
public int VideoId { get; private set; }
public string Path { get; private set; }
public int Id { get; private set; }
public PlayListItem(int id, int videoId, string path)
{
Id = id;
VideoId = videoId;
Path = path;
}
}
}
I know how to get a simple result from wsdl using actionscript, but don't know how to get objects with parameteres and save them.
Service has a method GetPlaylist(int value) which returns an array of objects (id, videoId, path). How to handle this and save them ?
Here is my as3:
package {
public class data extends CasparTemplate {
var flvControl:FLVPlayback;
var refreshTimer:Timer;
var videoList:Array;
var videoNew:Array;
var videoMaxIds:Array;
var videoNewMaxIds:Array;
var videoIndex:uint;
var videoIdFrom:uint;
var loopAtEnd:Boolean;
var _playListItems:Array;
var _playList:PlayListItem;
var gotNewPlaylist:Boolean;
var webService:WebService;
var serviceOperation:AbstractOperation;
public function data()
{
_playListItems = new Array();
flvControl = new FLVPlayback();
videoNew = new Array();
videoNewMaxIds = new Array();
videoIndex = 0;
videoIdFrom = videoMaxIds[videoIndex];
loopAtEnd = true;
gotNewPlaylist = false;
refreshTimer = new Timer(20000);
refreshTimer.addEventListener(TimerEvent.TIMER, getNewPlaylist);
refreshTimer.start();
flvControl.addEventListener(VideoEvent.COMPLETE, completeHandler);
flvControl.addEventListener(VideoEvent.STATE_CHANGE, vidState);
flvControl.setSize(720, 576);
flvControl.visible = true;
//addChild(flvControl);
var url:String = "http://xxx/yyy.svc?wsdl";
webService = new WebService();
webService.loadWSDL(url);
webService.addEventListener(LoadEvent.LOAD, BuildServiceRequest);
}
function BuildServiceRequest(evt:LoadEvent):void
{
webService.removeEventListener(LoadEvent.LOAD, BuildServiceRequest);
//serviceOperation.addEventListener(ResultEvent.RESULT, displayResult);
for (var resultas in webService.getOperation("GetPlaylist(394575)"))
{
trace(resultas.Id);
}
//serviceOperation = webService.getOperation("GetPlaylist");
//serviceOperation.arguments[{videoId: "394575"}];
}
private function displayResult(e:ResultEvent):void
{
trace("sss");
}
// Handle the video completion (load the next video)
function completeHandler(event:VideoEvent):void
{
if (gotNewPlaylist)
{
videoList = videoNew;
videoMaxIds = videoNewMaxIds;
videoNew = null;
videoNewMaxIds = null;
gotNewPlaylist = false;
videoIndex = 0;
} else
videoIndex++;
nextVideo();
}
private function vidState(e:VideoEvent):void {
var flvPlayer:FLVPlayback = e.currentTarget as FLVPlayback;
if (flvPlayer.state==VideoState.CONNECTION_ERROR) {
trace("FLVPlayer Connection Error! -> path : "+flvPlayer.source);
videoIndex++;
nextVideo();
} else if (flvPlayer.state==VideoState.DISCONNECTED) {
videoIndex++;
nextVideo();
}
}
function nextVideo():void
{
trace("Video List:"+videoList.toString());
if( videoIndex == videoList.length ){
if( loopAtEnd )
{
videoIndex = 0;
} else { return; }
}
flvControl.source = videoList[videoIndex];
if (videoIdFrom < videoMaxIds[videoIndex])
videoIdFrom = videoMaxIds[videoIndex];
trace(videoIdFrom);
}
}
}
internal class PlayListItem
{
private var _videoId:int;
private var _path:String;
private var _id:int;
public function get VideoId():int { return _videoId; }
public function get Path():String { return _path; }
public function get Id():int { return _id; }
public function set VideoId(setValue:int):void { _videoId = setValue };
public function set Path(setValue:String):void { _path = setValue };
public function set Id(setValue:int):void { _id = setValue };
public function PlayListItem(id:int, videoId:int, path:String)
{
_videoId = videoId;
_id = id;
_path = path;
}// end function
}
I think you were on the right track with your commented-out code. Be aware that the getOperation() will return an AbstractOperation, which in my mind is simply a pointer to the remote function. You can set arguments on the object, or simply pass the arguments when you call send(). I know some people have had issues with the argument property approach, so simply passing your arguments in the send function may be the smart way to go.
The following replace BuildServiceRequest and displayResult
private function BuildServiceRequest(evt:LoadEvent):void {
webService.removeEventListener(LoadEvent.LOAD, BuildServiceRequest);
serviceOperation.addEventListener(ResultEvent.RESULT, displayResult);
serviceOperation = webService.getOperation("GetPlaylist");
serviceOperation.send(394575);
}
private function displayResult(e:ResultEvent):void {
// Store the token as our array.
_playListItems = e.token;
var msg:String;
// Loop through the array
for each (var entry:Object in _playListItems) {
msg = "";
// For every key:value pair, compose a message to trace
for (var key:String in entry) {
msg += key + ":" + entry[key] + " ";
}
trace(msg);
}
}