Cannot assign to property: 'self' is immutable

Bonjour à  tous,

Je me considère encore comme un débutant en Swift... et j'ai un problème avec ce code:


import Cocoa
import SceneKit

class DxDyShader: NSObject {
var shadable: SCNShadable
var program: SCNProgram {
let prg = SCNProgram()
prg.delegate = self
return prg
}

init(shadable: SCNShadable) {
self.shadable = shadable
self.shadable.program = self.program // Cannot assign to property: 'self' is immutable
}
}

extension DxDyShader: SCNProgramDelegate {
func program(program: SCNProgram, handleError error: NSError) {
NSLog("Could not compile shader")
}
}
Quelques précisions:
- SCNShadable est un protocole de Scene Kit
- Ma classe DxDyShader hérite de NSObject, sinon elle ne peut pas implémenter SCNProgramDelegate.

Mes questions:
1) Pouvez-vous m'expliquer ce message d'erreur (dans la méthode init()) ?
2) Comment écriviriez-vous ce code ?

Merci !

Réponses

  • Ah les joies du mix Swift/Obj-C. Premièrement program est de type SCNProgram? mais comme il s'agit d'un protocol et que l'ivar program est notée optional on se retrouve avec SCNProgram??. Ouais, ça commence bien...


     


    L'idéal est de faire comme en Obj-C en vérifiant si ton objet implémente cette partie optionnelle du protocol et de faire appel au setter :



    class DxDyShader: NSObject {
    var shadable: SCNShadable
    var program: SCNProgram? {
    if shadable.responds(to: #selector(getter: shadable.program)) {
    return shadable.program!
    } else {
    return nil
    }
    }

    init(shadable: SCNShadable) {
    self.shadable = shadable
    super.init()
    if shadable.responds(to: #selector(getter: self.shadable.program)) {
    let prg = SCNProgram()
    prg.delegate = self
    self.shadable.perform(#selector(setter: self.shadable.program), with: prg)
    }
    }
    }

    Ce code est écrit en Swift 3.0, je précise.


     


    Pour program comme on ne sait pas si shadable l'implémente on peut au mieux renvoyer un optional donc SCNProgram?. Au passage on va chercher la valeur directement dans self.shadable histoire de coder un peu propre. (Pas que tu ne code pas propre, mais un développeur aguerri comme toi verra où je veux en venir).


     


  • CéroceCéroce Membre, Modérateur
    Merci beaucoup Pyroh d'avoir répondu. Bon en gros, on code comme en ObjC, mais en Swift, quoi ;-)

    J'ai une journée de code derrière moi, alors je regarderai ça demain matin, quand je serai plus frais.
  • CéroceCéroce Membre, Modérateur
    Bon, bon... Je commence à  trouver de la doc sur le web, par exemple ici, avec l'ami Aligator qui répond:
    http://stackoverflow.com/questions/26083377/swift-setting-an-optional-property-of-a-protocol

    Donc, pour résumer:
    - le message d'erreur est totalement à  côté de la plaque
    - le problème est que le protocole SCNShadable a sa propriété .program qui est marquée @optional dans le protocole.

    C'est un problème d'interoperatibilité entre Swift et ObjC.
    J'essaie de faire fonctionner puis je posterai ici.

    @Pyroh: je déteste l'idée d'utiliser le runtime ObjC pour contourner le problème. Certes, ça passe, mais on perd aussi le contrôle effectué par le compilateur. J'aimerais éviter autant que possible; c'est tout de même un apport majeur de Swift.
  • Je me disais aussi que le compilo était un peu aux fraises avec son message d'erreur...


    Maintenant le soucis ici c'est pas Swift en lui même mais son inter-opérabilité avec Objective-C.


     


    Dans la mesure où tu ne peux pas déclarer quoi que ce soit d'optionnel dans un protocol Swift il est normal qu'il faille se reposer sur le runtime Obj-C pour gérer une de ses spécificités... 


  • CéroceCéroce Membre, Modérateur
    septembre 2016 modifié #6
    A priori, on peut écrire quelque chose comme ce qui suit:
     
    import Cocoa
    import SceneKit

    class DxDyShader {
    var shadable: SCNShadableMandatoryProgram
    var program: SCNProgram {
    return SCNProgram()
    }

    init(shadable: SCNShadable) {
    self.shadable = shadable as! SCNShadableMandatoryProgram
    self.shadable.program = self.program
    }


    }

    @objc protocol SCNShadableMandatoryProgram: SCNShadable {
    var program: SCNProgram {get set}
    }
    En gros, je crée un protocole dérivé de SCNShadable, mais où la propriété 'program' est obligatoire. Et je caste le shadable vers ce protocole. C'est la technique exposée par Aligator.
    Je n'ai pas testé sur Swift 3, et j'ignore si le problème se pose toujours.

    P.S.: Qui disait que Swift était facile ?
  • CéroceCéroce Membre, Modérateur
    Pour finir, le problème a disparu parce que ce n'est plus le program qui s'accroche au shadable, mais on le fait ailleurs; c'est bien plus logique.

    Mon code complet donne ça:

    import Cocoa
    import SceneKit

    class DxDyShader {
    let program: SCNProgram = {
    let program = SCNProgram()

    do {
    program.vertexShader = try NSBundle.mainBundle().stringResource(named: "Standard", fileExtension:"vsh")
    } catch {
    NSLog("Could not load vertex shader")
    }

    do {
    program.fragmentShader = try NSBundle.mainBundle().stringResource(named: "DxDy", fileExtension:"fsh")
    } catch {
    NSLog("Could not load fragment shader")
    }

    DxDyShader.bindAttributes(program)
    return program
    }()

    let programDelegate: ProgramDelegate = {
    return ProgramDelegate()
    }()

    private static func bindAttributes(program: SCNProgram) {
    program.setSemantic(SCNGeometrySourceSemanticVertex, forSymbol: "a_position", options: nil)
    program.setSemantic(SCNGeometrySourceSemanticTexcoord, forSymbol: "a_texCoord", options: nil)
    program.setSemantic(SCNModelViewProjectionTransform, forSymbol: "u_modelViewProjectionMatrix", options: nil)
    }

    init() {
    self.program.delegate = self.programDelegate
    }

    }

    class ProgramDelegate: NSObject, SCNProgramDelegate {
    func program(program: SCNProgram, handleError error: NSError) {
    NSLog("Could not compile shader")
    }
    }

    enum StringResourceError: ErrorType {
    case ResourceNotFound, TextCannotBeDecoded
    }

    extension NSBundle {
    func stringResource(named name:String, fileExtension: String) throws -> String {
    guard let url = self.URLForResource(name, withExtension: fileExtension) else {
    throw StringResourceError.ResourceNotFound
    }

    do {
    let text = try NSString(contentsOfURL: url, encoding: NSUTF8StringEncoding)
    return text as String
    } catch {
    throw StringResourceError.TextCannotBeDecoded
    }
    }
    }
    Au passage, je viens de découvrir la différence entre "computed properties" et "function properties". Ce sont bien ces dernières que je veux!
Connectez-vous ou Inscrivez-vous pour répondre.